diff --git a/src/wasm.h b/src/wasm.h index 97098fd749a..3d4ce837b94 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2406,9 +2406,15 @@ class Module { // Optional user section IR representation. std::unique_ptr dylinkSection; - // Source maps debug info. + // Source maps debug info. All of these fields are read directly in from the + // source map and are encoded as in the original JSON (UTF-8 encoded with + // with escaped quotes and slashes). The string values are uninterpreted in + // Binaryen, and they are written directly back out without re-encoding. std::vector debugInfoFileNames; std::vector debugInfoSymbolNames; + std::string debugInfoSourceRoot; + std::string debugInfoFile; + std::vector debugInfoSourcesContent; // `features` are the features allowed to be used in this module and should be // respected regardless of the value of`hasFeaturesSection`. diff --git a/src/wasm/source-map.cpp b/src/wasm/source-map.cpp index adba2504350..cce8a5967f1 100644 --- a/src/wasm/source-map.cpp +++ b/src/wasm/source-map.cpp @@ -59,6 +59,16 @@ void SourceMapReader::parse(Module& wasm) { wasm.debugInfoFileNames.push_back(v->getCString()); } + if (json.has("sourcesContent")) { + json::Ref sc = json["sourcesContent"]; + if (!sc->isArray()) { + throw MapParseException("Source map sourcesContent is not an array"); + } + for (size_t i = 0; i < sc->size(); i++) { + wasm.debugInfoSourcesContent.push_back(sc[i]->getCString()); + } + } + if (json.has("names")) { json::Ref n = json["names"]; if (!n->isArray()) { @@ -73,6 +83,22 @@ void SourceMapReader::parse(Module& wasm) { } } + if (json.has("sourceRoot")) { + json::Ref sr = json["sourceRoot"]; + if (!sr->isString()) { + throw MapParseException("Source map sourceRoot is not a string"); + } + wasm.debugInfoSourceRoot = sr->getCString(); + } + + if (json.has("file")) { + json::Ref f = json["file"]; + if (!f->isString()) { + throw MapParseException("Source map file is not a string"); + } + wasm.debugInfoFile = f->getCString(); + } + if (!json.has("mappings")) { throw MapParseException("Source map mappings missing"); } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 415fc9a8730..b2c5842f4a4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1226,25 +1226,40 @@ void WasmBinaryWriter::writeSourceMapProlog() { } } - *sourceMap << "\"sources\":["; - for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) { - if (i > 0) { - *sourceMap << ","; + auto writeOptionalString = [&](const char* name, const std::string& str) { + if (!str.empty()) { + *sourceMap << "\"" << name << "\":\"" << str << "\","; } - // TODO respect JSON string encoding, e.g. quotes and control chars. - *sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\""; - } - *sourceMap << "],\"names\":["; + }; - for (size_t i = 0; i < wasm->debugInfoSymbolNames.size(); i++) { - if (i > 0) { - *sourceMap << ","; + writeOptionalString("file", wasm->debugInfoFile); + writeOptionalString("sourceRoot", wasm->debugInfoSourceRoot); + + auto writeStringVector = [&](const char* name, + const std::vector& vec) { + *sourceMap << "\"" << name << "\":["; + for (size_t i = 0; i < vec.size(); i++) { + if (i > 0) { + *sourceMap << ","; + } + *sourceMap << "\"" << vec[i] << "\""; } - // TODO respect JSON string encoding, e.g. quotes and control chars. - *sourceMap << "\"" << wasm->debugInfoSymbolNames[i] << "\""; + *sourceMap << "],"; + }; + + writeStringVector("sources", wasm->debugInfoFileNames); + + if (!wasm->debugInfoSourcesContent.empty()) { + writeStringVector("sourcesContent", wasm->debugInfoSourcesContent); } - *sourceMap << "],\"mappings\":\""; + // TODO: This field is optional; maybe we should omit if it's empty. + // TODO: Binaryen actually does not correctly preserve symbol names when it + // rewrites the mappings. We should maybe just drop them, or else handle + // them correctly. + writeStringVector("names", wasm->debugInfoSymbolNames); + + *sourceMap << "\"mappings\":\""; } static void writeBase64VLQ(std::ostream& out, int32_t n) { diff --git a/test/gtest/source-map.cpp b/test/gtest/source-map.cpp index 5076fb8ded1..657034befcb 100644 --- a/test/gtest/source-map.cpp +++ b/test/gtest/source-map.cpp @@ -153,3 +153,38 @@ TEST_F(SourceMapTest, Fibonacci) { // program? ExpectDbgLocEq(9999, 0, 8, 0, std::nullopt); } + +TEST_F(SourceMapTest, SourceMapSourceRootFile) { + std::string sourceMap = R"( + { + "version":3, + "file": "foo.wasm", + "sources":[], + "names":[], + "mappings": "", + "sourceRoot": "/foo/bar" + } + )"; + parseMap(sourceMap); + EXPECT_EQ(wasm.debugInfoSourceRoot, "/foo/bar"); + EXPECT_EQ(wasm.debugInfoFile, "foo.wasm"); +} + +TEST_F(SourceMapTest, SourcesContent) { + // The backslash escapes appear in the JSON encoding, and are preserved in + // the internal representation. The string values are uninterpreted in + // Binaryen, and they are written directly back out without re-encoding. + std::string sourceMap = R"( + { + "version": 3, + "sources": ["foo.c"], + "sourcesContent": ["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"], + "mappings" : "" + } + )"; + parseMap(sourceMap); + ASSERT_EQ(wasm.debugInfoSourcesContent.size(), 1); + EXPECT_EQ(wasm.debugInfoSourcesContent[0], + "#include int main()\\n{ printf(\\\"Gr\\u00fc\\u00df " + "Gott, Welt!\\\"); return 0;}"); +} diff --git a/test/lit/sourcemap-sourceroot-file.wat b/test/lit/sourcemap-sourceroot-file.wat new file mode 100644 index 00000000000..12bcc693d50 --- /dev/null +++ b/test/lit/sourcemap-sourceroot-file.wat @@ -0,0 +1,14 @@ +;; RUN: wasm-opt %s.wasm -ism %s.map -osm %t -o %t2 +;; Running multiple times is needed here because the output is all on one line. +;; RUN: cat %t | filecheck %s --check-prefix=FILE +;; RUN: cat %t | filecheck %s --check-prefix=SOURCEROOT +;; RUN: cat %t | filecheck %s --check-prefix=CONTENT + + +;; This wat file is not actually part of the test (the binary file is used), +;; but no comments are allowed in JSON so the RUN and CHECK lines are here. + +;; FILE: "file":"foo.wasm", +;; SOURCEROOT: "sourceRoot":"/foo/bar", +;; CONTENT: "sourcesContent":["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"] +(module) diff --git a/test/lit/sourcemap-sourceroot-file.wat.map b/test/lit/sourcemap-sourceroot-file.wat.map new file mode 100644 index 00000000000..3f98ccd28a2 --- /dev/null +++ b/test/lit/sourcemap-sourceroot-file.wat.map @@ -0,0 +1,9 @@ +{ + "version":3, + "file": "foo.wasm", + "sources":[], + "names":[], + "mappings": "", + "sourceRoot": "/foo/bar", + "sourcesContent": ["#include int main()\n{ printf(\"Gr\u00fc\u00df Gott, Welt!\"); return 0;}"] +} diff --git a/test/lit/sourcemap-sourceroot-file.wat.wasm b/test/lit/sourcemap-sourceroot-file.wat.wasm new file mode 100644 index 00000000000..d8fc92d022f Binary files /dev/null and b/test/lit/sourcemap-sourceroot-file.wat.wasm differ