1d392255ef
Adds a mode that puts every library (outside of the SDK) into its own wasm module. Each module is then loaded before the program executes. This allows us to get better coverage on multi-module logic. I chose to augment the SDK `_invokeMain` entry point because it allows us to push the user-defined main into a second module ensuring we always have at least 2 modules. The SDK alone is then the main module and all user-code is in deferred modules. The next step is to make a test configuration for this and decide what frequency to execute it with. Change-Id: I01bf3a5fee4604a890ef376ebd35113281a36a5c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384404 Commit-Queue: Nate Biggs <natebiggs@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
164 lines
5.1 KiB
Dart
164 lines
5.1 KiB
Dart
// 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.
|
|
|
|
import 'dart:js_interop';
|
|
import 'dart:typed_data';
|
|
import 'dart:convert';
|
|
|
|
import 'package:source_maps/parser.dart';
|
|
|
|
void f() {
|
|
g();
|
|
}
|
|
|
|
void g() {
|
|
throw 'hi';
|
|
}
|
|
|
|
runtimeFalse() => int.parse('1') == 0;
|
|
|
|
// `expectedFrames` is (String, 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.
|
|
void testMain(String testName, List<(String?, int?, int?)?> expectedFrames) {
|
|
// Use `f` and `g` in a few places to make sure wasm-opt won't inline them
|
|
// in the test.
|
|
final fTearOff = f;
|
|
final gTearOff = g;
|
|
|
|
if (runtimeFalse()) f();
|
|
if (runtimeFalse()) g();
|
|
|
|
// 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 actualFrames =
|
|
parseStack(testName, getSourceMapping(testName), stackTraceString!);
|
|
print('Got stack trace:');
|
|
for (final frame in actualFrames) {
|
|
print(' $frame');
|
|
}
|
|
print('Matching against:');
|
|
for (final frame in expectedFrames) {
|
|
print(' $frame');
|
|
}
|
|
|
|
if (actualFrames.length < expectedFrames.length) {
|
|
throw 'Less actual frames than expected';
|
|
}
|
|
|
|
for (int i = 0; i < expectedFrames.length; i++) {
|
|
final expected = expectedFrames[i];
|
|
final actual = actualFrames[i];
|
|
if (expected == null) continue;
|
|
if (actual == null) {
|
|
throw 'Mismatch:\n Expected: $expected\n Actual: <no mapping>';
|
|
}
|
|
if ((expected.$1 != null && actual.$1 != expected.$1) ||
|
|
(expected.$2 != null && actual.$2 != expected.$2) ||
|
|
(expected.$3 != null && actual.$3 != expected.$3)) {
|
|
throw 'Mismatch:\n Expected: $expected\n Actual: $actual';
|
|
}
|
|
}
|
|
}
|
|
|
|
String getFilename(String testName, int moduleId) {
|
|
final compilationDir = const String.fromEnvironment("TEST_COMPILATION_DIR");
|
|
if (moduleId == 0) {
|
|
return '$compilationDir/${testName}_test.wasm.map';
|
|
} else {
|
|
return '$compilationDir/${testName}_test_module$moduleId.wasm.map';
|
|
}
|
|
}
|
|
|
|
Mapping getSourceMapping(String testName) {
|
|
final allMappings = MappingBundle();
|
|
// Read source map of the current program.
|
|
final compilationDir = const String.fromEnvironment("TEST_COMPILATION_DIR");
|
|
final mainFilename = getFilename(testName, 0);
|
|
final mainSourceMapFile = readfile(mainFilename);
|
|
final mainSourceMap = parse(utf8.decode(mainSourceMapFile)) as SingleMapping;
|
|
mainSourceMap.targetUrl = mainFilename;
|
|
allMappings.addMapping(mainSourceMap);
|
|
|
|
int i = 1;
|
|
while (true) {
|
|
// All the modules will have consecutive names. Keep reading them until we
|
|
// fail to find one.
|
|
try {
|
|
final filename = getFilename(testName, i);
|
|
final fileContents = readfile(filename);
|
|
final sourceMap = parse(utf8.decode(fileContents)) as SingleMapping;
|
|
sourceMap.targetUrl = filename;
|
|
allMappings.addMapping(sourceMap);
|
|
i++;
|
|
} catch (e) {
|
|
break;
|
|
}
|
|
}
|
|
return allMappings;
|
|
}
|
|
|
|
List<(String?, int?, int?)?> parseStack(
|
|
String testName, Mapping mapping, String stackTraceString) {
|
|
final parsed = <(String?, int?, int?)?>[];
|
|
for (final line in stackTraceString.split('\n')) {
|
|
if (line.contains('.mjs') || line.contains('.js')) {
|
|
parsed.add(null);
|
|
continue;
|
|
}
|
|
|
|
final hexOffsetMatch = stackTraceHexOffsetRegExp.firstMatch(line);
|
|
if (hexOffsetMatch == null) {
|
|
throw 'Unable to parse hex offset in frame "$line"';
|
|
}
|
|
final hexOffsetStr = hexOffsetMatch.group(1)!; // includes '0x'
|
|
final offset = int.tryParse(hexOffsetStr);
|
|
if (offset == null) {
|
|
throw 'Unable to parse hex number in frame "$line"';
|
|
}
|
|
final moduleIdMatch = stackTraceModuleNameRegExp.firstMatch(line);
|
|
if (moduleIdMatch == null) {
|
|
throw 'Unable to parse module name in frame "$line"';
|
|
}
|
|
final moduleId = int.parse(moduleIdMatch.group(1)!);
|
|
final uri = getFilename(testName, moduleId);
|
|
final span = mapping.spanFor(0, offset, uri: uri);
|
|
if (span == null) {
|
|
print('Stack frame "$line" does not have a source mapping');
|
|
parsed.add(null);
|
|
continue;
|
|
}
|
|
final filename = span.sourceUrl!.pathSegments.last;
|
|
final lineNumber = span.start.line;
|
|
final columnNumber = span.start.column;
|
|
parsed.add((filename, 1 + lineNumber, 1 + columnNumber));
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
/// 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]+)\)$');
|
|
final stackTraceModuleNameRegExp = RegExp(r'wasm/module([0-9]+)');
|