blob: 2197ae613bb24e238ee61344003d1a0b645d391d [file] [log] [blame]
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// dart2wasmOptions=--extra-compiler-option=-DTEST_COMPILATION_DIR=$TEST_COMPILATION_DIR
import 'dart:js_interop';
import 'dart:typed_data';
import 'dart:convert';
import 'package:source_maps/parser.dart';
void f() {
g();
}
void g() {
throw 'hi';
}
void main() {
// Read source map of the current program.
final compilationDir = const String.fromEnvironment("TEST_COMPILATION_DIR");
final sourceMapFileContents =
readfile('$compilationDir/source_map_simple_test.wasm.map');
final mapping = parse(utf8.decode(sourceMapFileContents)) as SingleMapping;
// Get some simple stack trace.
String? stackTraceString;
try {
f();
} catch (e, st) {
stackTraceString = st.toString();
}
// Print the stack trace to make it easy to update the test.
print("-----");
print(stackTraceString);
print("-----");
final stackTraceLines = stackTraceString!.split('\n');
// Stack trace should look like:
//
// at Error._throwWithCurrentStackTrace (wasm://wasm/00118b26:wasm-function[144]:0x163e0)
// at f (wasm://wasm/00118b26:wasm-function[996]:0x243dd)
// at main (wasm://wasm/00118b26:wasm-function[988]:0x241dc)
// at main tear-off trampoline (wasm://wasm/00118b26:wasm-function[990]:0x24340)
// at _invokeMain (wasm://wasm/00118b26:wasm-function[104]:0x15327)
// at Module.invoke (/usr/local/google/home/omersa/dart/sdk/test/test.mjs:317:26)
// at main (/usr/local/google/home/omersa/dart/sdk/sdk/pkg/dart2wasm/bin/run_wasm.js:421:21)
// at async action (/usr/local/google/home/omersa/dart/sdk/sdk/pkg/dart2wasm/bin/run_wasm.js:350:37)
//
// The first 5 frames are in Wasm, but "main tear-off trampoline" shouldn't
// have a source mapping as it's compiler generated.
// (function name, line, column) of the frames we check.
//
// Information we don't check are "null": we don't want to check line/column
// of standard library functions to avoid breaking the test with unrelated
// changes to the standard library.
const List<(String, int?, int?)?> frameDetails = [
("_throwWithCurrentStackTrace", null, null),
("g", 18, 3),
("f", 14, 3),
("main", 31, 5),
null, // compiler generated, not mapped
("_invokeMain", null, null),
];
for (int frameIdx = 0; frameIdx < frameDetails.length; frameIdx += 1) {
final line = stackTraceLines[frameIdx];
final hexOffsetMatch = stackTraceHexOffsetRegExp.firstMatch(line);
if (hexOffsetMatch == null) {
throw "Unable to parse hex offset from stack frame $frameIdx";
}
final hexOffsetStr = hexOffsetMatch.group(1)!; // includes '0x'
final offset = int.tryParse(hexOffsetStr);
if (offset == null) {
throw "Unable to parse hex number in frame $frameIdx: $hexOffsetStr";
}
final span = mapping.spanFor(0, offset);
final frameInfo = frameDetails[frameIdx];
if (frameInfo == null) {
if (span != null) {
throw "Stack frame $frameIdx should not have a source span, but it is mapped: $span";
}
continue;
}
if (span == null) {
throw "Stack frame $frameIdx does not have source mapping";
}
final funName = span.text;
if (frameInfo.$1 != funName) {
throw "Stack frame $frameIdx does not have expected name: expected ${frameInfo.$1}, found $funName";
}
if (frameInfo.$2 != null) {
if (span.start.line + 1 != frameInfo.$2) {
throw "Stack frame $frameIdx is expected to have line ${frameInfo.$2}, but it has line ${span.start.line + 1}";
}
}
if (frameInfo.$3 != null) {
if (span.start.column + 1 != frameInfo.$3) {
throw "Stack frame $frameIdx is expected to have column ${frameInfo.$3}, but it has column ${span.start.column + 1}";
}
}
}
}
/// Read the file at the given [path].
///
/// This relies on the `readbuffer` function provided by d8.
@JS()
external JSArrayBuffer readbuffer(JSString path);
/// Read the file at the given [path].
Uint8List readfile(String path) => Uint8List.view(readbuffer(path.toJS).toDart);
final stackTraceHexOffsetRegExp = RegExp(r'wasm-function.*(0x[0-9a-fA-F]+)\)$');