| // 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:convert'; | |
| import 'package:dart2wasm/util.dart'; | |
| import 'package:source_maps/parser.dart'; | |
| import 'utils.dart'; | |
| void f() { | |
| g(); | |
| } | |
| void g() { | |
| throw 'hi'; | |
| } | |
| runtimeFalse() => int.parse('1') == 0; | |
| // `expectedFrames` is (file, line, column, name) 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?, String?)?> 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) || | |
| (expected.$4 != null && actual.$4 != expected.$4)) { | |
| throw 'Mismatch:\n Expected: $expected\n Actual: $actual'; | |
| } | |
| } | |
| } | |
| Mapping getSourceMapping(String testName) { | |
| final allMappings = MappingBundle(); | |
| // Read source map of the current program. | |
| final mainFilename = getSourceMapFilePath(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 = getSourceMapFilePath(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?, String?)?> parseStack( | |
| String testName, | |
| Mapping mapping, | |
| String stackTraceString, | |
| ) { | |
| final parsed = <(String?, int?, int?, String?)?>[]; | |
| 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[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 moduleIdString = moduleIdMatch[1]!; | |
| final moduleId = moduleNameToId(moduleIdString)!; | |
| final uri = getSourceMapFilePath(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; | |
| final symbolName = span.text; | |
| parsed.add((filename, 1 + lineNumber, 1 + columnNumber, symbolName)); | |
| } | |
| return parsed; | |
| } | |
| final stackTraceHexOffsetRegExp = RegExp(r'wasm-function.*(0x[0-9a-fA-F]+)\)$'); | |
| final stackTraceModuleNameRegExp = RegExp(r'wasm/(.*)-[0-9a-z]+:'); |