blob: a01d064eda6652f00c29617365b0b393726708d7 [file] [log] [blame] [edit]
/*
* Copyright 2024 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "source-map.h"
#include "print-test.h"
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"
using namespace wasm;
class SourceMapTest : public PrintTest {
std::vector<char> buffer;
protected:
Module wasm;
std::unique_ptr<SourceMapReader> reader;
void SetUp() override {
PrintTest::SetUp();
reader.reset();
parseWast(wasm, "(module)");
}
void parseMap(std::string& sourceMap) {
buffer = {sourceMap.begin(), sourceMap.end()};
reader.reset(new SourceMapReader(buffer));
reader->parse(wasm);
}
void ExpectDbgLocEq(size_t location,
BinaryLocation file,
BinaryLocation line,
BinaryLocation col,
std::optional<BinaryLocation> sym) {
auto loc_str = std::stringstream() << "location: " << location;
SCOPED_TRACE(loc_str.str());
auto loc = reader->readDebugLocationAt(location);
ASSERT_TRUE(loc.has_value());
EXPECT_EQ(loc->fileIndex, file);
EXPECT_EQ(loc->lineNumber, line);
EXPECT_EQ(loc->columnNumber, col);
EXPECT_EQ(loc->symbolNameIndex, sym);
}
void ExpectParseError(std::string& mapString, const char* expectedError) {
SCOPED_TRACE(mapString);
EXPECT_THROW(parseMap(mapString), MapParseException);
try {
parseMap(mapString);
} catch (MapParseException ex) {
EXPECT_THAT(ex.errorText, ::testing::HasSubstr(expectedError));
}
}
};
// Check that debug location parsers can handle single-segment mappings.
TEST_F(SourceMapTest, SourceMappingSingleSegment) {
// A single-segment mapping starting at offset 0.
std::string sourceMap = R"(
{
"version": 3,
"sources": [],
"sourcesContent": [],
"names": [],
"mappings": "A"
}
)";
parseMap(sourceMap);
auto loc = reader->readDebugLocationAt(0);
EXPECT_FALSE(loc.has_value());
}
TEST_F(SourceMapTest, BadSourceMaps) {
// Test that a malformed JSON string throws rather than asserting.
std::string sourceMap = R"(
{
"version": 3,
"sources": ["foo.c"],
"mappings": ""
malformed
}
)";
ExpectParseError(sourceMap, "malformed value in JSON object");
// Valid JSON, but missing the version field.
sourceMap = R"(
{
"sources": [],
"names": [],
"mappings": "A"
}
)";
ExpectParseError(sourceMap, "Source map version missing");
// Valid JSON, but a bad "sources" field.
sourceMap = R"(
{
"version": 3,
"sources": 123,
"mappings": ""
}
)";
ExpectParseError(sourceMap, "Source map sources missing or not an array");
sourceMap = R"(
{
"version": 3,
"sources": ["foo.c"],
"mappings": "C;A"
}
)";
parseMap(sourceMap);
// Mapping strings are parsed incrementally, so errors don't show up until a
// sufficiently far-advanced location is requested to reach the problem.
EXPECT_THROW(reader->readDebugLocationAt(1), MapParseException);
}
TEST_F(SourceMapTest, SourcesAndNames) {
std::string sourceMap = R"(
{
"version": 3,
"sources": ["foo.c", "bar.c"],
"names": ["foo", "bar"],
"mappings": ""
}
)";
parseMap(sourceMap);
EXPECT_EQ(wasm.debugInfoFileNames.size(), 2);
EXPECT_EQ(wasm.debugInfoFileNames[0], "foo.c");
EXPECT_EQ(wasm.debugInfoFileNames[1], "bar.c");
EXPECT_EQ(wasm.debugInfoSymbolNames.size(), 2);
EXPECT_EQ(wasm.debugInfoSymbolNames[0], "foo");
EXPECT_EQ(wasm.debugInfoSymbolNames[1], "bar");
}
TEST_F(SourceMapTest, OptionalFields) {
// The "names" field is optional.
std::string sourceMap = R"(
{
"version": 3,
"sources": [],
"mappings": "A"
}
)";
parseMap(sourceMap);
}
// This map is taken from test/fib-dbg.wasm
TEST_F(SourceMapTest, Fibonacci) {
// Test mapping parsing and debug locs
std::string sourceMap = R"(
{
"version":3,
"sources":["fib.c"],
"names":[],
"mappings": "moBAEA,4BAKA,QAJA,OADA,OAAA,uCAKA"
}
)";
parseMap(sourceMap);
// Location before the first record has no value
auto loc = reader->readDebugLocationAt(642);
EXPECT_FALSE(loc.has_value());
// TODO: These column numbers show as 0 but emsymbolizer.py prints them as 1.
// Figure out which is right.
// First location
ExpectDbgLocEq(643, 0, 3, 0, std::nullopt);
// locations in between records have the same value as the previous one.
for (size_t l = 644; l < 671; l++) {
ExpectDbgLocEq(l, 0, 3, 0, std::nullopt);
}
// Subsequent ones are on record boundaries
ExpectDbgLocEq(671, 0, 8, 0, std::nullopt);
ExpectDbgLocEq(679, 0, 4, 0, std::nullopt);
ExpectDbgLocEq(686, 0, 3, 0, std::nullopt);
ExpectDbgLocEq(693, 0, 3, 0, std::nullopt);
ExpectDbgLocEq(732, 0, 8, 0, std::nullopt);
// Entries after the last record have the same value as the last one.
ExpectDbgLocEq(733, 0, 8, 0, std::nullopt);
// Should we return empty values for locations that are past the end of the
// 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 <stdio.h> 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 <stdio.h> int main()\\n{ printf(\\\"Gr\\u00fc\\u00df "
"Gott, Welt!\\\"); return 0;}");
}