Skip to content

Commit 1fc8e67

Browse files
committed
Enable LLD_REPORT_UNDEFINED by default
This makes undefined symbol errors more precise by including the name of the object that references the undefined symbol. Its also paves the way (in my mind anyway) for finally fixing reverse dependencies in a salable way. See #15982. That PR uses an alternative script for the pre-processing of dependencies but also fundamentally relies on processing JS libraries both before and after linking. The cost is about 300ms per link operation due to double processing of the JS libraries, but results are cached so in practice this only happens the first time a given link command is run (see #18326).
1 parent 3e1b838 commit 1fc8e67

File tree

8 files changed

+52
-49
lines changed

8 files changed

+52
-49
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.28 (in development)
2222
-----------------------
23+
- `LLD_REPORT_UNDEFINED` is now enabled by default. This makes undefined symbol
24+
errors more precise by including the name of the object that references the
25+
undefined symbol. The old behaviour (of allowing all undefined symbols at
26+
wasm-ld time and reporting them later when processing JS library files) is
27+
still available using `-sLLD_REPORT_UNDEFINED=0`. (#16003)
2328
- musl libc updated from v1.2.2 to v1.2.3. (#18270)
2429
- The default emscripten config file no longer contains `EMSCRIPTEN_ROOT`. This
2530
setting has long been completely ignored by emscripten itself. For

emcc.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,6 +1870,7 @@ def phase_linker_setup(options, state, newargs):
18701870
if 'EXPORTED_FUNCTIONS' in user_settings:
18711871
if '_main' not in settings.USER_EXPORTED_FUNCTIONS:
18721872
settings.EXPECT_MAIN = 0
1873+
settings.IGNORE_MISSING_MAIN = 1
18731874
else:
18741875
assert not settings.EXPORTED_FUNCTIONS
18751876
settings.EXPORTED_FUNCTIONS = ['_main']
@@ -1898,10 +1899,7 @@ def phase_linker_setup(options, state, newargs):
18981899
if not settings.PURE_WASI and '-nostdlib' not in newargs and '-nodefaultlibs' not in newargs:
18991900
default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK))
19001901

1901-
if settings.LLD_REPORT_UNDEFINED or settings.STANDALONE_WASM:
1902-
# Reporting undefined symbols at wasm-ld time requires us to know if we have a `main` function
1903-
# or not, as does standalone wasm mode.
1904-
# TODO(sbc): Remove this once this becomes the default
1902+
if settings.STANDALONE_WASM:
19051903
settings.IGNORE_MISSING_MAIN = 0
19061904

19071905
# For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also

src/library.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,17 +1258,19 @@ mergeInto(LibraryManager.library, {
12581258
// built with SUPPORT_LONGJMP=1, the object file contains references of not
12591259
// longjmp but _emscripten_throw_longjmp, which is called from
12601260
// emscripten_longjmp.
1261-
_emscripten_throw_longjmp: function() { error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)'); },
12621261
get _emscripten_throw_longjmp__deps() {
12631262
return this.longjmp__deps;
12641263
},
12651264
#endif
1265+
_emscripten_throw_longjmp: function() {
1266+
error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)');
1267+
},
12661268
// will never be emitted, as the dep errors at compile time
12671269
longjmp: function(env, value) {
1268-
abort('longjmp not supported');
1270+
abort('longjmp not supported (build with -s SUPPORT_LONGJMP)');
12691271
},
1270-
setjmp: function(env, value) {
1271-
abort('setjmp not supported');
1272+
setjmp: function(env) {
1273+
abort('setjmp not supported (build with -s SUPPORT_LONGJMP)');
12721274
},
12731275
#endif
12741276

src/settings.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,12 +1917,13 @@ var USE_OFFSET_CONVERTER = false;
19171917
// This is enabled automatically when using -g4 with sanitizers.
19181918
var LOAD_SOURCE_MAP = false;
19191919

1920-
// If set to 1, the JS compiler is run before wasm-ld so that the linker can
1921-
// report undefined symbols within the binary. Without this option the linker
1922-
// doesn't know which symbols might be defined in JS so reporting of undefined
1923-
// symbols is delayed until the JS compiler is run.
1920+
// If set to 0, delay undefined symbol report until after wasm-ld runs. This
1921+
// avoids running the the JS compiler prior to wasm-ld, but reduces the amount
1922+
// of information in the undefined symbol message (Since JS compiler cannot
1923+
// report the name of the object file that contains the reference to the
1924+
// undefined symbol).
19241925
// [link]
1925-
var LLD_REPORT_UNDEFINED = false;
1926+
var LLD_REPORT_UNDEFINED = true;
19261927

19271928
// Default to c++ mode even when run as `emcc` rather then `emc++`.
19281929
// When this is disabled `em++` is required when compiling and linking C++

test/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,8 @@ def expect_fail(self, cmd, expect_traceback=False, **args):
10431043
self.assertContained('Traceback', proc.stderr)
10441044
elif not WINDOWS or 'Access is denied' not in proc.stderr:
10451045
self.assertNotContained('Traceback', proc.stderr)
1046+
if EMTEST_VERBOSE:
1047+
sys.stderr.write(proc.stderr)
10461048
return proc.stderr
10471049

10481050
# excercise dynamic linker.

test/test_core.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,8 +4149,8 @@ def test_dylink_basics_no_modify(self):
41494149
self.do_basic_dylink_test()
41504150

41514151
@needs_dylink
4152-
def test_dylink_basics_lld_report_undefined(self):
4153-
self.set_setting('LLD_REPORT_UNDEFINED')
4152+
def test_dylink_basics_no_lld_report_undefined(self):
4153+
self.set_setting('LLD_REPORT_UNDEFINED', 0)
41544154
self.do_basic_dylink_test()
41554155

41564156
@needs_dylink
@@ -5150,9 +5150,6 @@ def test_dylink_rtti(self):
51505150
# in the another module.
51515151
# Each module will define its own copy of certain COMDAT symbols such as
51525152
# each classs's typeinfo, but at runtime they should both use the same one.
5153-
# Use LLD_REPORT_UNDEFINED to test that it works as expected with weak/COMDAT
5154-
# symbols.
5155-
self.set_setting('LLD_REPORT_UNDEFINED')
51565153
header = '''
51575154
#include <cstddef>
51585155
@@ -6161,7 +6158,6 @@ def test_unistd_io(self):
61616158
'nodefs': (['NODEFS']),
61626159
})
61636160
def test_unistd_misc(self, fs):
6164-
self.set_setting('LLD_REPORT_UNDEFINED')
61656161
self.emcc_args += ['-D' + fs]
61666162
if fs == 'NODEFS':
61676163
self.require_node()
@@ -9416,9 +9412,8 @@ def test_undefined_main(self):
94169412
# In standalone we don't support implicitly building without main. The user has to explicitly
94179413
# opt out (see below).
94189414
err = self.expect_fail([EMCC, test_file('core/test_ctors_no_main.cpp')] + self.get_emcc_args())
9419-
self.assertContained('error: undefined symbol: main/__main_argc_argv (referenced by top-level compiled C/C++ code)', err)
9420-
self.assertContained('warning: To build in STANDALONE_WASM mode without a main(), use emcc --no-entry', err)
9421-
elif not self.get_setting('LLD_REPORT_UNDEFINED') and not self.get_setting('STRICT'):
9415+
self.assertContained('undefined symbol: main', err)
9416+
elif not self.get_setting('STRICT'):
94229417
# Traditionally in emscripten we allow main to be implicitly undefined. This allows programs
94239418
# with a main and libraries without a main to be compiled identically.
94249419
# However we are trying to move away from that model to a more explicit opt-out model. See:
@@ -9436,6 +9431,9 @@ def test_undefined_main(self):
94369431
self.do_core_test('test_ctors_no_main.cpp')
94379432
self.clear_setting('EXPORTED_FUNCTIONS')
94389433

9434+
# Marked as impure since the WASI reactor modules (modules without main)
9435+
# are not yet suppored by the wasm engines we test against.
9436+
@also_with_standalone_wasm(impure=True)
94399437
def test_undefined_main_explict(self):
94409438
# If we pass --no-entry this test should compile without issue
94419439
self.emcc_args.append('--no-entry')
@@ -9708,7 +9706,6 @@ def setUp(self):
97089706
settings={'ALLOW_MEMORY_GROWTH': 1})
97099707

97109708
# Experimental modes (not tested by CI)
9711-
lld = make_run('lld', emcc_args=[], settings={'LLD_REPORT_UNDEFINED': 1})
97129709
minimal0 = make_run('minimal0', emcc_args=['-g'], settings={'MINIMAL_RUNTIME': 1})
97139710

97149711
# TestCoreBase is just a shape for the specific subclasses, we don't test it itself

test/test_other.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,8 +2167,8 @@ def test_undefined_symbols(self, action):
21672167
print(proc.stderr)
21682168
if value or action is None:
21692169
# The default is that we error in undefined symbols
2170-
self.assertContained('error: undefined symbol: something', proc.stderr)
2171-
self.assertContained('error: undefined symbol: elsey', proc.stderr)
2170+
self.assertContained('undefined symbol: something', proc.stderr)
2171+
self.assertContained('undefined symbol: elsey', proc.stderr)
21722172
check_success = False
21732173
elif action == 'ERROR' and not value:
21742174
# Error disables, should only warn
@@ -3548,7 +3548,7 @@ def test_js_lib_missing_sig(self):
35483548
def test_js_lib_quoted_key(self):
35493549
create_file('lib.js', r'''
35503550
mergeInto(LibraryManager.library, {
3551-
__internal_data:{
3551+
__internal_data:{
35523552
'<' : 0,
35533553
'white space' : 1
35543554
},
@@ -6591,7 +6591,7 @@ def test_no_warn_exported_jslibfunc(self):
65916591
err = self.expect_fail([EMCC, test_file('hello_world.c'),
65926592
'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=alGetError',
65936593
'-sEXPORTED_FUNCTIONS=_main,_alGet'])
6594-
self.assertContained('undefined exported symbol: "_alGet"', err)
6594+
self.assertContained('error: undefined exported symbol: "_alGet" [-Wundefined] [-Werror]', err)
65956595

65966596
def test_musl_syscalls(self):
65976597
self.run_process([EMCC, test_file('hello_world.c')])
@@ -8361,7 +8361,7 @@ def test_full_js_library(self):
83618361
def test_full_js_library_undefined(self):
83628362
create_file('main.c', 'void foo(); int main() { foo(); return 0; }')
83638363
err = self.expect_fail([EMCC, 'main.c', '-sSTRICT_JS', '-sINCLUDE_FULL_LIBRARY'])
8364-
self.assertContained('error: undefined symbol: foo', err)
8364+
self.assertContained('undefined symbol: foo', err)
83658365

83668366
def test_full_js_library_except(self):
83678367
self.set_setting('INCLUDE_FULL_LIBRARY', 1)
@@ -9017,19 +9017,20 @@ def test_js_preprocess(self):
90179017

90189018
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], stderr=PIPE).stderr
90199019
self.assertContained('JSLIB: none of the above', err)
9020-
self.assertEqual(err.count('JSLIB'), 1)
9020+
self.assertNotContained('JSLIB: MAIN_MODULE', err)
9021+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90219022

90229023
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE'], stderr=PIPE).stderr
90239024
self.assertContained('JSLIB: MAIN_MODULE=1', err)
9024-
self.assertEqual(err.count('JSLIB'), 1)
9025+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90259026

90269027
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE=2'], stderr=PIPE).stderr
90279028
self.assertContained('JSLIB: MAIN_MODULE=2', err)
9028-
self.assertEqual(err.count('JSLIB'), 1)
9029+
self.assertNotContained('JSLIB: EXIT_RUNTIME', err)
90299030

90309031
err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sEXIT_RUNTIME'], stderr=PIPE).stderr
90319032
self.assertContained('JSLIB: EXIT_RUNTIME', err)
9032-
self.assertEqual(err.count('JSLIB'), 1)
9033+
self.assertNotContained('JSLIB: MAIN_MODULE', err)
90339034

90349035
def test_html_preprocess(self):
90359036
src_file = test_file('module/test_stdin.c')
@@ -9202,7 +9203,7 @@ def test_dash_s_list_parsing(self):
92029203
# stray slash
92039204
('EXPORTED_FUNCTIONS=["_a", "_b",\\ "_c", "_d"]', 'undefined exported symbol: "\\\\ "_c"'),
92049205
# missing comma
9205-
('EXPORTED_FUNCTIONS=["_a", "_b" "_c", "_d"]', 'undefined exported symbol: "_b" "_c"'),
9206+
('EXPORTED_FUNCTIONS=["_a", "_b" "_c", "_d"]', 'emcc: error: undefined exported symbol: "_b" "_c" [-Wundefined] [-Werror]'),
92069207
]:
92079208
print(export_arg)
92089209
proc = self.run_process([EMCC, 'src.c', '-s', export_arg], stdout=PIPE, stderr=PIPE, check=not expected)
@@ -10887,20 +10888,20 @@ def test_signature_mismatch(self):
1088710888
self.expect_fail([EMCC, '-Wl,--fatal-warnings', 'a.c', 'b.c'])
1088810889
self.expect_fail([EMCC, '-sSTRICT', 'a.c', 'b.c'])
1088910890

10891+
# TODO(sbc): Remove these tests once we remove the LLD_REPORT_UNDEFINED
1089010892
def test_lld_report_undefined(self):
1089110893
create_file('main.c', 'void foo(); int main() { foo(); return 0; }')
10892-
stderr = self.expect_fail([EMCC, '-sLLD_REPORT_UNDEFINED', 'main.c'])
10893-
self.assertContained('wasm-ld: error:', stderr)
10894-
self.assertContained('main_0.o: undefined symbol: foo', stderr)
10894+
stderr = self.expect_fail([EMCC, '-sLLD_REPORT_UNDEFINED=0', 'main.c'])
10895+
self.assertContained('error: undefined symbol: foo (referenced by top-level compiled C/C++ code)', stderr)
1089510896

1089610897
def test_lld_report_undefined_reverse_deps(self):
10897-
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED', '-sREVERSE_DEPS=all', test_file('hello_world.c')])
10898+
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED=0', '-sREVERSE_DEPS=all', test_file('hello_world.c')])
1089810899

1089910900
def test_lld_report_undefined_exceptions(self):
10900-
self.run_process([EMXX, '-sLLD_REPORT_UNDEFINED', '-fwasm-exceptions', test_file('hello_libcxx.cpp')])
10901+
self.run_process([EMXX, '-sLLD_REPORT_UNDEFINED=0', '-fwasm-exceptions', test_file('hello_libcxx.cpp')])
1090110902

1090210903
def test_lld_report_undefined_main_module(self):
10903-
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED', '-sMAIN_MODULE=2', test_file('hello_world.c')])
10904+
self.run_process([EMCC, '-sLLD_REPORT_UNDEFINED=0', '-sMAIN_MODULE=2', test_file('hello_world.c')])
1090410905

1090510906
# Verifies that warning messages that Closure outputs are recorded to console
1090610907
def test_closure_warnings(self):
@@ -11038,14 +11039,12 @@ def test_linker_version(self):
1103811039
def test_chained_js_error_diagnostics(self):
1103911040
err = self.expect_fail([EMCC, test_file('test_chained_js_error_diagnostics.c'), '--js-library', test_file('test_chained_js_error_diagnostics.js')])
1104011041
self.assertContained("error: undefined symbol: nonexistent_function (referenced by bar__deps: ['nonexistent_function'], referenced by foo__deps: ['bar'], referenced by top-level compiled C/C++ code)", err)
11041-
# Check that we don't recommend LLD_REPORT_UNDEFINED for chained dependencies.
11042-
self.assertNotContained('LLD_REPORT_UNDEFINED', err)
1104311042

11044-
# Test without chaining. In this case we don't include the JS library at all resulting in `foo`
11045-
# being undefined in the native code and in this case we recommend LLD_REPORT_UNDEFINED.
11043+
# Test without chaining. In this case we don't include the JS library at
11044+
# all resulting in `foo` being undefined in the native code.
1104611045
err = self.expect_fail([EMCC, test_file('test_chained_js_error_diagnostics.c')])
11047-
self.assertContained('error: undefined symbol: foo (referenced by top-level compiled C/C++ code)', err)
11048-
self.assertContained('Link with `-sLLD_REPORT_UNDEFINED` to get more information on undefined symbols', err)
11046+
self.assertContained('undefined symbol: foo', err)
11047+
self.assertNotContained('referenced by top-level compiled C/C++ code', err)
1104911048

1105011049
def test_xclang_flag(self):
1105111050
create_file('foo.h', ' ')
@@ -11483,7 +11482,7 @@ def test_split_main_module(self):
1148311482

1148411483
self.run_process([EMCC, side_src, '-sSIDE_MODULE', '-g', '-o', 'libhello.wasm'])
1148511484

11486-
self.emcc_args += ['-g']
11485+
self.emcc_args += ['-g', 'libhello.wasm']
1148711486
self.emcc_args += ['-sMAIN_MODULE=2']
1148811487
self.emcc_args += ['-sEXPORTED_FUNCTIONS=_printf']
1148911488
self.emcc_args += ['-sSPLIT_MODULE', '-Wno-experimental']
@@ -11847,7 +11846,7 @@ def test_no_main_with_PROXY_TO_PTHREAD(self):
1184711846
void foo() {}
1184811847
''')
1184911848
err = self.expect_fail([EMCC, 'lib.cpp', '-pthread', '-sPROXY_TO_PTHREAD'])
11850-
self.assertContained('error: PROXY_TO_PTHREAD proxies main() for you, but no main exists', err)
11849+
self.assertContained('crt1_proxy_main.o: undefined symbol: main', err)
1185111850

1185211851
def test_archive_bad_extension(self):
1185311852
# Regression test for https://github.com/emscripten-core/emscripten/issues/14012
@@ -11889,7 +11888,7 @@ def test_unimplemented_syscalls(self, args):
1188911888
cmd = [EMCC, 'main.c', '-sASSERTIONS'] + args
1189011889
if args:
1189111890
err = self.expect_fail(cmd)
11892-
self.assertContained('error: undefined symbol: __syscall_mincore', err)
11891+
self.assertContained('undefined symbol: __syscall_mincore', err)
1189311892
else:
1189411893
self.run_process(cmd)
1189511894
err = self.run_js('a.out.js')

tools/gen_struct_info.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,6 @@ def inspect_headers(headers, cflags):
269269
'-nostdlib',
270270
compiler_rt,
271271
'-sBOOTSTRAPPING_STRUCT_INFO',
272-
'-sLLD_REPORT_UNDEFINED',
273272
'-sSTRICT',
274273
'-sASSERTIONS=0',
275274
# Use SINGLE_FILE so there is only a single

0 commit comments

Comments
 (0)