Skip to content

Commit 2172402

Browse files
authored
Add support for more source map fields (#7473)
Read the "sourcesContent", "file", and "sourceRoot" fields from incoming source maps, attach them to the wasm IR module, and write them back to the output source map. These fields are unchanged by Binaryen's updates to the mappings, so they do not need to be decoded or interpreted. Fixes #6805
1 parent ea7593b commit 2172402

7 files changed

+120
-15
lines changed

src/wasm.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2406,9 +2406,15 @@ class Module {
24062406
// Optional user section IR representation.
24072407
std::unique_ptr<DylinkSection> dylinkSection;
24082408

2409-
// Source maps debug info.
2409+
// Source maps debug info. All of these fields are read directly in from the
2410+
// source map and are encoded as in the original JSON (UTF-8 encoded with
2411+
// with escaped quotes and slashes). The string values are uninterpreted in
2412+
// Binaryen, and they are written directly back out without re-encoding.
24102413
std::vector<std::string> debugInfoFileNames;
24112414
std::vector<std::string> debugInfoSymbolNames;
2415+
std::string debugInfoSourceRoot;
2416+
std::string debugInfoFile;
2417+
std::vector<std::string> debugInfoSourcesContent;
24122418

24132419
// `features` are the features allowed to be used in this module and should be
24142420
// respected regardless of the value of`hasFeaturesSection`.

src/wasm/source-map.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ void SourceMapReader::parse(Module& wasm) {
5959
wasm.debugInfoFileNames.push_back(v->getCString());
6060
}
6161

62+
if (json.has("sourcesContent")) {
63+
json::Ref sc = json["sourcesContent"];
64+
if (!sc->isArray()) {
65+
throw MapParseException("Source map sourcesContent is not an array");
66+
}
67+
for (size_t i = 0; i < sc->size(); i++) {
68+
wasm.debugInfoSourcesContent.push_back(sc[i]->getCString());
69+
}
70+
}
71+
6272
if (json.has("names")) {
6373
json::Ref n = json["names"];
6474
if (!n->isArray()) {
@@ -73,6 +83,22 @@ void SourceMapReader::parse(Module& wasm) {
7383
}
7484
}
7585

86+
if (json.has("sourceRoot")) {
87+
json::Ref sr = json["sourceRoot"];
88+
if (!sr->isString()) {
89+
throw MapParseException("Source map sourceRoot is not a string");
90+
}
91+
wasm.debugInfoSourceRoot = sr->getCString();
92+
}
93+
94+
if (json.has("file")) {
95+
json::Ref f = json["file"];
96+
if (!f->isString()) {
97+
throw MapParseException("Source map file is not a string");
98+
}
99+
wasm.debugInfoFile = f->getCString();
100+
}
101+
76102
if (!json.has("mappings")) {
77103
throw MapParseException("Source map mappings missing");
78104
}

src/wasm/wasm-binary.cpp

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,25 +1226,40 @@ void WasmBinaryWriter::writeSourceMapProlog() {
12261226
}
12271227
}
12281228

1229-
*sourceMap << "\"sources\":[";
1230-
for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) {
1231-
if (i > 0) {
1232-
*sourceMap << ",";
1229+
auto writeOptionalString = [&](const char* name, const std::string& str) {
1230+
if (!str.empty()) {
1231+
*sourceMap << "\"" << name << "\":\"" << str << "\",";
12331232
}
1234-
// TODO respect JSON string encoding, e.g. quotes and control chars.
1235-
*sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\"";
1236-
}
1237-
*sourceMap << "],\"names\":[";
1233+
};
12381234

1239-
for (size_t i = 0; i < wasm->debugInfoSymbolNames.size(); i++) {
1240-
if (i > 0) {
1241-
*sourceMap << ",";
1235+
writeOptionalString("file", wasm->debugInfoFile);
1236+
writeOptionalString("sourceRoot", wasm->debugInfoSourceRoot);
1237+
1238+
auto writeStringVector = [&](const char* name,
1239+
const std::vector<std::string>& vec) {
1240+
*sourceMap << "\"" << name << "\":[";
1241+
for (size_t i = 0; i < vec.size(); i++) {
1242+
if (i > 0) {
1243+
*sourceMap << ",";
1244+
}
1245+
*sourceMap << "\"" << vec[i] << "\"";
12421246
}
1243-
// TODO respect JSON string encoding, e.g. quotes and control chars.
1244-
*sourceMap << "\"" << wasm->debugInfoSymbolNames[i] << "\"";
1247+
*sourceMap << "],";
1248+
};
1249+
1250+
writeStringVector("sources", wasm->debugInfoFileNames);
1251+
1252+
if (!wasm->debugInfoSourcesContent.empty()) {
1253+
writeStringVector("sourcesContent", wasm->debugInfoSourcesContent);
12451254
}
12461255

1247-
*sourceMap << "],\"mappings\":\"";
1256+
// TODO: This field is optional; maybe we should omit if it's empty.
1257+
// TODO: Binaryen actually does not correctly preserve symbol names when it
1258+
// rewrites the mappings. We should maybe just drop them, or else handle
1259+
// them correctly.
1260+
writeStringVector("names", wasm->debugInfoSymbolNames);
1261+
1262+
*sourceMap << "\"mappings\":\"";
12481263
}
12491264

12501265
static void writeBase64VLQ(std::ostream& out, int32_t n) {

test/gtest/source-map.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,38 @@ TEST_F(SourceMapTest, Fibonacci) {
153153
// program?
154154
ExpectDbgLocEq(9999, 0, 8, 0, std::nullopt);
155155
}
156+
157+
TEST_F(SourceMapTest, SourceMapSourceRootFile) {
158+
std::string sourceMap = R"(
159+
{
160+
"version":3,
161+
"file": "foo.wasm",
162+
"sources":[],
163+
"names":[],
164+
"mappings": "",
165+
"sourceRoot": "/foo/bar"
166+
}
167+
)";
168+
parseMap(sourceMap);
169+
EXPECT_EQ(wasm.debugInfoSourceRoot, "/foo/bar");
170+
EXPECT_EQ(wasm.debugInfoFile, "foo.wasm");
171+
}
172+
173+
TEST_F(SourceMapTest, SourcesContent) {
174+
// The backslash escapes appear in the JSON encoding, and are preserved in
175+
// the internal representation. The string values are uninterpreted in
176+
// Binaryen, and they are written directly back out without re-encoding.
177+
std::string sourceMap = R"(
178+
{
179+
"version": 3,
180+
"sources": ["foo.c"],
181+
"sourcesContent": ["#include <stdio.h> int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"],
182+
"mappings" : ""
183+
}
184+
)";
185+
parseMap(sourceMap);
186+
ASSERT_EQ(wasm.debugInfoSourcesContent.size(), 1);
187+
EXPECT_EQ(wasm.debugInfoSourcesContent[0],
188+
"#include <stdio.h> int main()\\n{ printf(\\\"Gr\\u00fc\\u00df "
189+
"Gott, Welt!\\\"); return 0;}");
190+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
;; RUN: wasm-opt %s.wasm -ism %s.map -osm %t -o %t2
2+
;; Running multiple times is needed here because the output is all on one line.
3+
;; RUN: cat %t | filecheck %s --check-prefix=FILE
4+
;; RUN: cat %t | filecheck %s --check-prefix=SOURCEROOT
5+
;; RUN: cat %t | filecheck %s --check-prefix=CONTENT
6+
7+
8+
;; This wat file is not actually part of the test (the binary file is used),
9+
;; but no comments are allowed in JSON so the RUN and CHECK lines are here.
10+
11+
;; FILE: "file":"foo.wasm",
12+
;; SOURCEROOT: "sourceRoot":"/foo/bar",
13+
;; CONTENT: "sourcesContent":["#include <stdio.h> int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"]
14+
(module)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"version":3,
3+
"file": "foo.wasm",
4+
"sources":[],
5+
"names":[],
6+
"mappings": "",
7+
"sourceRoot": "/foo/bar",
8+
"sourcesContent": ["#include <stdio.h> int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"]
9+
}
8 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)