diff --git a/tests/test_core.py b/tests/test_core.py index 7a849af133fd..925acfada059 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7181,6 +7181,12 @@ def encode_utf8(data): else: return data + def source_map_file_loc(name): + if shared.Settings.WASM_BACKEND: + return name + # in fastcomp, we have the absolute path, which is not good + return os.path.abspath(name) + data = json.load(open(map_filename)) if str is bytes: # Python 2 compatibility @@ -7190,7 +7196,7 @@ def encode_utf8(data): # the output file. self.assertPathsIdentical(map_referent, data['file']) assert len(data['sources']) == 1, data['sources'] - self.assertPathsIdentical(os.path.abspath('src.cpp'), data['sources'][0]) + self.assertPathsIdentical(source_map_file_loc('src.cpp'), data['sources'][0]) if hasattr(data, 'sourcesContent'): # the sourcesContent attribute is optional, but if it is present it # needs to containt valid source text. @@ -7203,7 +7209,7 @@ def encode_utf8(data): mappings = encode_utf8(mappings) seen_lines = set() for m in mappings: - self.assertPathsIdentical(os.path.abspath('src.cpp'), m['source']) + self.assertPathsIdentical(source_map_file_loc('src.cpp'), m['source']) seen_lines.add(m['originalLine']) # ensure that all the 'meaningful' lines in the original code get mapped # when optimizing, the binaryen optimizer may remove some of them (by inlining, etc.) @@ -8243,7 +8249,7 @@ def test_ubsan_full_static_cast(self, args): 'g4': ('-g4', [ "src.cpp:3:12: runtime error: reference binding to null pointer of type 'int'", 'in main ', - '/src.cpp:3:8' + 'src.cpp:3:8' ]), }) @no_fastcomp('ubsan not supported on fastcomp') diff --git a/tests/test_other.py b/tests/test_other.py index 3717cb346953..1612dabcc200 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -8894,7 +8894,8 @@ def test_wasm_sourcemap(self): '--dwarfdump-output', path_from_root('tests', 'other', 'wasm_sourcemap', 'foo.wasm.dump'), '-o', 'a.out.wasm.map', - path_from_root('tests', 'other', 'wasm_sourcemap', 'foo.wasm')] + path_from_root('tests', 'other', 'wasm_sourcemap', 'foo.wasm'), + '--basepath=' + os.getcwd()] run_process(wasm_map_cmd) output = open('a.out.wasm.map').read() # has "sources" entry with file (includes also `--prefix =wasm-src:///` replacement) @@ -8909,12 +8910,35 @@ def test_wasm_sourcemap_dead(self): '--dwarfdump-output', path_from_root('tests', 'other', 'wasm_sourcemap_dead', 't.wasm.dump'), '-o', 'a.out.wasm.map', - path_from_root('tests', 'other', 'wasm_sourcemap_dead', 't.wasm')] + path_from_root('tests', 'other', 'wasm_sourcemap_dead', 't.wasm'), + '--basepath=' + os.getcwd()] run_process(wasm_map_cmd, stdout=PIPE, stderr=PIPE) output = open('a.out.wasm.map').read() # has only two entries self.assertRegexpMatches(output, r'"mappings":\s*"[A-Za-z0-9+/]+,[A-Za-z0-9+/]+"') + @no_fastcomp() + def test_wasm_sourcemap_relative_paths(self): + def test(infile, source_map_added_dir=''): + expected_source_map_path = os.path.join(source_map_added_dir, 'a.cpp') + print(infile, expected_source_map_path) + shutil.copyfile(path_from_root('tests', 'hello_123.c'), infile) + infiles = [ + infile, + os.path.abspath(infile), + './' + infile + ] + for curr in infiles: + print(' ', curr) + run_process([PYTHON, EMCC, curr, '-g4']) + with open('a.out.wasm.map', 'r') as f: + self.assertIn('"%s"' % expected_source_map_path, str(f.read())) + + test('a.cpp') + + os.mkdir('inner') + test(os.path.join('inner', 'a.cpp'), 'inner') + def test_wasm_producers_section(self): # no producers section by default run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c')]) @@ -9668,17 +9692,17 @@ def test_lsan_leaks(self, ext): @parameterized({ 'c': ['c', [ r'in malloc.*a\.out\.wasm\+0x', - r'(?im)in f (/|[a-z]:).*/test_lsan_leaks\.c:6:21$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.c:10:16$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.c:12:3$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.c:13:3$', + r'(?im)in f (|[/a-z\.]:).*/test_lsan_leaks\.c:6:21$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:10:16$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:12:3$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:13:3$', ]], 'cpp': ['cpp', [ r'in operator new\[\]\(unsigned long\).*a\.out\.wasm\+0x', - r'(?im)in f\(\) (/|[a-z]:).*/test_lsan_leaks\.cpp:4:21$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.cpp:8:16$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.cpp:10:3$', - r'(?im)in main (/|[a-z]:).*/test_lsan_leaks\.cpp:11:3$', + r'(?im)in f\(\) (|[/a-z\.]:).*/test_lsan_leaks\.cpp:4:21$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:8:16$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:10:3$', + r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:11:3$', ]], }) @no_fastcomp('lsan not supported on fastcomp') diff --git a/tools/shared.py b/tools/shared.py index 7b0598a03bfc..0d40e44625b1 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -2871,10 +2871,14 @@ def path_to_system_js_libraries(library_name): @staticmethod def emit_wasm_source_map(wasm_file, map_file): + # source file paths must be relative to the location of the map (which is + # emitted alongside the wasm) + base_path = os.path.dirname(os.path.abspath(Settings.WASM_BINARY_FILE)) sourcemap_cmd = [PYTHON, path_from_root('tools', 'wasm-sourcemap.py'), wasm_file, '--dwarfdump=' + LLVM_DWARFDUMP, - '-o', map_file] + '-o', map_file, + '--basepath=' + base_path] check_call(sourcemap_cmd) @staticmethod diff --git a/tools/wasm-sourcemap.py b/tools/wasm-sourcemap.py index 240647747d66..6c1c9abfbfc2 100755 --- a/tools/wasm-sourcemap.py +++ b/tools/wasm-sourcemap.py @@ -11,7 +11,7 @@ """ import argparse -from collections import OrderedDict, namedtuple +from collections import OrderedDict import json import logging from math import floor, log @@ -39,6 +39,7 @@ def parse_args(): parser.add_argument('-u', '--source-map-url', nargs='?', help='specifies sourceMappingURL section contest') parser.add_argument('--dwarfdump', help="path to llvm-dwarfdump executable") parser.add_argument('--dwarfdump-output', nargs='?', help=argparse.SUPPRESS) + parser.add_argument('--basepath', help='base path for source files, which will be relative to this') return parser.parse_args() @@ -73,7 +74,13 @@ def resolve(self, name): # SourceMapPrefixes contains resolver for file names that are: # - "sources" is for names that output to source maps JSON # - "load" is for paths that used to load source text -SourceMapPrefixes = namedtuple('SourceMapPrefixes', 'sources, load') +class SourceMapPrefixes: + def __init__(self, sources, load): + self.sources = sources + self.load = load + + def provided(self): + return bool(self.sources.prefixes or self.load.prefixes) def encode_vlq(n): @@ -243,11 +250,10 @@ def read_dwarf_entries(wasm, options): return sorted(entries, key=lambda entry: entry['address']) -def build_sourcemap(entries, code_section_offset, prefixes, collect_sources): +def build_sourcemap(entries, code_section_offset, prefixes, collect_sources, base_path): sources = [] sources_content = [] if collect_sources else None mappings = [] - sources_map = {} last_address = 0 last_source_id = 0 @@ -264,7 +270,13 @@ def build_sourcemap(entries, code_section_offset, prefixes, collect_sources): column = 1 address = entry['address'] + code_section_offset file_name = entry['file'] - source_name = prefixes.sources.resolve(file_name) + # if prefixes were provided, we use that; otherwise, we emit a relative + # path + if prefixes.provided(): + source_name = prefixes.sources.resolve(file_name) + else: + file_name = os.path.relpath(os.path.abspath(file_name), base_path) + source_name = file_name if source_name not in sources_map: source_id = len(sources) sources_map[source_name] = source_id @@ -311,7 +323,7 @@ def main(): prefixes = SourceMapPrefixes(sources=Prefixes(options.prefix), load=Prefixes(options.load_prefix)) logger.debug('Saving to %s' % options.output) - map = build_sourcemap(entries, code_section_offset, prefixes, options.sources) + map = build_sourcemap(entries, code_section_offset, prefixes, options.sources, options.basepath) with open(options.output, 'w') as outfile: json.dump(map, outfile, separators=(',', ':'))