Skip to content

Commit aa486b5

Browse files
authored
Assert if we try to use link-time setting before link time. NFC. (#14109)
We should not be reading or writing to linker-only settings until after the compile phase. This change keeps us honest and allows us to be sure the list of compile time settings that we have is correct (or at least sufficient, it could contain false positives).
1 parent 28bd282 commit aa486b5

File tree

6 files changed

+111
-67
lines changed

6 files changed

+111
-67
lines changed

emcc.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from tools import wasm2c
5151
from tools import webassembly
5252
from tools import config
53-
from tools.settings import settings, MEM_SIZE_SETTINGS
53+
from tools.settings import settings, MEM_SIZE_SETTINGS, COMPILE_TIME_SETTINGS
5454

5555
logger = logging.getLogger('emcc')
5656

@@ -213,6 +213,15 @@ def __init__(self, args):
213213
self.forced_stdlibs = []
214214

215215

216+
def add_link_flag(state, i, f):
217+
if f.startswith('-l'):
218+
state.libs.append((i, f[2:]))
219+
if f.startswith('-L'):
220+
state.lib_dirs.append(f[2:])
221+
222+
state.link_flags.append((i, f))
223+
224+
216225
class EmccOptions:
217226
def __init__(self):
218227
self.output_file = None
@@ -1032,9 +1041,15 @@ def run(args):
10321041
## Process argument and setup the compiler
10331042
state = EmccState(args)
10341043
options, newargs, settings_map = phase_parse_arguments(state)
1044+
1045+
# For internal consistency, ensure we don't attempt or read or write any link time
1046+
# settings until we reach the linking phase.
1047+
settings.limit_settings(COMPILE_TIME_SETTINGS)
1048+
10351049
newargs, input_files = phase_setup(options, state, newargs, settings_map)
10361050

10371051
if options.post_link:
1052+
settings.limit_settings(None)
10381053
target, wasm_target = phase_linker_setup(options, state, newargs, settings_map)
10391054
process_libraries(state.libs, state.lib_dirs, [])
10401055
if len(input_files) != 1:
@@ -1043,8 +1058,7 @@ def run(args):
10431058
return 0
10441059

10451060
## Compile source code to object files
1046-
linker_inputs = []
1047-
phase_compile_inputs(options, state, newargs, input_files, linker_inputs)
1061+
linker_inputs = phase_compile_inputs(options, state, newargs, input_files)
10481062

10491063
if state.compile_only:
10501064
logger.debug('stopping after compile phase')
@@ -1055,6 +1069,9 @@ def run(args):
10551069

10561070
return 0
10571071

1072+
# We have now passed the compile phase, allow reading/writing of all settings.
1073+
settings.limit_settings(None)
1074+
10581075
if options.output_file and options.output_file.startswith('-'):
10591076
exit_with_error('invalid output filename: `%s`' % options.output_file)
10601077

@@ -1141,29 +1158,6 @@ def phase_parse_arguments(state):
11411158
if options.post_link or options.oformat == OFormat.BARE:
11421159
diagnostics.warning('experimental', '--oformat=base/--post-link are experimental and subject to change.')
11431160

1144-
if options.emrun:
1145-
options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n'
1146-
options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n'
1147-
# emrun mode waits on program exit
1148-
settings.EXIT_RUNTIME = 1
1149-
1150-
if options.cpu_profiler:
1151-
options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n'
1152-
1153-
if options.memory_profiler:
1154-
settings.MEMORYPROFILER = 1
1155-
1156-
if options.thread_profiler:
1157-
options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n'
1158-
1159-
if options.memory_init_file is None:
1160-
options.memory_init_file = settings.OPT_LEVEL >= 2
1161-
1162-
# TODO: support source maps with js_transform
1163-
if options.js_transform and settings.GENERATE_SOURCE_MAP:
1164-
logger.warning('disabling source maps because a js transform is being done')
1165-
settings.GENERATE_SOURCE_MAP = 0
1166-
11671161
explicit_settings_changes, newargs = parse_s_args(newargs)
11681162
settings_changes += explicit_settings_changes
11691163

@@ -1206,14 +1200,6 @@ def phase_setup(options, state, newargs, settings_map):
12061200
# arguments that expand into multiple processed arguments, as in -Wl,-f1,-f2.
12071201
input_files = []
12081202

1209-
def add_link_flag(i, f):
1210-
if f.startswith('-l'):
1211-
state.libs.append((i, f[2:]))
1212-
if f.startswith('-L'):
1213-
state.lib_dirs.append(f[2:])
1214-
1215-
state.link_flags.append((i, f))
1216-
12171203
# find input files with a simple heuristic. we should really analyze
12181204
# based on a full understanding of gcc params, right now we just assume that
12191205
# what is left contains no more |-x OPT| things
@@ -1259,21 +1245,21 @@ def add_link_flag(i, f):
12591245
else:
12601246
input_files.append((i, arg))
12611247
elif arg.startswith('-L'):
1262-
add_link_flag(i, arg)
1248+
add_link_flag(state, i, arg)
12631249
newargs[i] = ''
12641250
elif arg.startswith('-l'):
1265-
add_link_flag(i, arg)
1251+
add_link_flag(state, i, arg)
12661252
newargs[i] = ''
12671253
elif arg.startswith('-Wl,'):
12681254
# Multiple comma separated link flags can be specified. Create fake
12691255
# fractional indices for these: -Wl,a,b,c,d at index 4 becomes:
12701256
# (4, a), (4.25, b), (4.5, c), (4.75, d)
12711257
link_flags_to_add = arg.split(',')[1:]
12721258
for flag_index, flag in enumerate(link_flags_to_add):
1273-
add_link_flag(i + float(flag_index) / len(link_flags_to_add), flag)
1259+
add_link_flag(state, i + float(flag_index) / len(link_flags_to_add), flag)
12741260
newargs[i] = ''
12751261
elif arg == '-Xlinker':
1276-
add_link_flag(i + 1, newargs[i + 1])
1262+
add_link_flag(state, i + 1, newargs[i + 1])
12771263
newargs[i] = ''
12781264
newargs[i + 1] = ''
12791265
elif arg == '-s':
@@ -1307,10 +1293,6 @@ def add_link_flag(i, f):
13071293
# for key in settings_map:
13081294
# if key not in COMPILE_TIME_SETTINGS:
13091295
# diagnostics.warning('unused-command-line-argument', "linker setting ignored during compilation: '%s'" % key)
1310-
else:
1311-
ldflags = emsdk_ldflags(newargs)
1312-
for f in ldflags:
1313-
add_link_flag(sys.maxsize, f)
13141296

13151297
if state.has_dash_c or state.has_dash_S or state.has_dash_E or '-M' in newargs or '-MM' in newargs:
13161298
if state.has_dash_c:
@@ -1362,6 +1344,33 @@ def phase_linker_setup(options, state, newargs, settings_map):
13621344
# Add `#!` line to output JS and make it executable.
13631345
options.executable = True
13641346

1347+
ldflags = emsdk_ldflags(newargs)
1348+
for f in ldflags:
1349+
add_link_flag(state, sys.maxsize, f)
1350+
1351+
if options.emrun:
1352+
options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n'
1353+
options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n'
1354+
# emrun mode waits on program exit
1355+
settings.EXIT_RUNTIME = 1
1356+
1357+
if options.cpu_profiler:
1358+
options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n'
1359+
1360+
if options.memory_profiler:
1361+
settings.MEMORYPROFILER = 1
1362+
1363+
if options.thread_profiler:
1364+
options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n'
1365+
1366+
if options.memory_init_file is None:
1367+
options.memory_init_file = settings.OPT_LEVEL >= 2
1368+
1369+
# TODO: support source maps with js_transform
1370+
if options.js_transform and settings.GENERATE_SOURCE_MAP:
1371+
logger.warning('disabling source maps because a js transform is being done')
1372+
settings.GENERATE_SOURCE_MAP = 0
1373+
13651374
# options.output_file is the user-specified one, target is what we will generate
13661375
if options.output_file:
13671376
target = options.output_file
@@ -2244,7 +2253,7 @@ def get_full_import_name(name):
22442253

22452254

22462255
@ToolchainProfiler.profile_block('compile inputs')
2247-
def phase_compile_inputs(options, state, newargs, input_files, linker_inputs):
2256+
def phase_compile_inputs(options, state, newargs, input_files):
22482257
def is_link_flag(flag):
22492258
if flag.startswith('-nostdlib'):
22502259
return True
@@ -2321,7 +2330,7 @@ def get_clang_command_asm(src_file):
23212330
# with -MF! (clang seems to not recognize it)
23222331
logger.debug(('just preprocessor ' if state.has_dash_E else 'just dependencies: ') + ' '.join(cmd))
23232332
shared.check_call(cmd)
2324-
return
2333+
return []
23252334

23262335
# Precompiled headers support
23272336
if state.has_header_inputs:
@@ -2334,8 +2343,9 @@ def get_clang_command_asm(src_file):
23342343
cmd += ['-o', options.output_file]
23352344
logger.debug("running (for precompiled headers): " + cmd[0] + ' ' + ' '.join(cmd[1:]))
23362345
shared.check_call(cmd)
2337-
return
2346+
return []
23382347

2348+
linker_inputs = []
23392349
seen_names = {}
23402350

23412351
def uniquename(name):
@@ -2392,6 +2402,8 @@ def compile_source_file(i, input_file):
23922402
logger.debug('using object file: ' + input_file)
23932403
linker_inputs.append((i, input_file))
23942404

2405+
return linker_inputs
2406+
23952407

23962408
@ToolchainProfiler.profile_block('calculate system libraries')
23972409
def phase_calculate_system_libraries(state, linker_arguments, linker_inputs, newargs):

tools/ports/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def read_ports():
2727
assert hasattr(port, a), 'port %s is missing %s' % (port, a)
2828
if not hasattr(port, 'process_dependencies'):
2929
port.process_dependencies = lambda x: 0
30+
if not hasattr(port, 'linker_setup'):
31+
port.linker_setup = lambda x, y: 0
3032
if not hasattr(port, 'deps'):
3133
port.deps = []
3234

tools/ports/regal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def clear(ports, settings, shared):
143143
shared.Cache.erase_lib(get_lib_name(settings))
144144

145145

146-
def process_dependencies(settings):
146+
def linker_setup(ports, settings):
147147
settings.FULL_ES2 = 1
148148

149149

tools/ports/sdl2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def clear(ports, settings, shared):
9090
shared.Cache.erase_lib(get_lib_name(settings))
9191

9292

93-
def process_dependencies(settings):
93+
def linker_setup(ports, settings):
9494
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$autoResumeAudioContext', '$dynCall']
9595

9696

tools/settings.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,7 @@
2121
'DEFAULT_PTHREAD_STACK_SIZE'
2222
)
2323

24-
# Subset of settings that apply at compile time.
25-
# (Keep in sync with [compile] comments in settings.js)
26-
COMPILE_TIME_SETTINGS = (
27-
'MEMORY64',
28-
'INLINING_LIMIT',
29-
'EXCEPTION_CATCHING_ALLOWED',
30-
'DISABLE_EXCEPTION_CATCHING',
31-
'DISABLE_EXCEPTION_THROWING',
32-
'MAIN_MODULE',
33-
'SIDE_MODULE',
34-
'RELOCATABLE',
35-
'STRICT',
36-
'EMSCRIPTEN_TRACING',
37-
'USE_PTHREADS',
38-
'SUPPORT_LONGJMP',
39-
'DEFAULT_TO_CXX',
40-
'WASM_OBJECT_FILES',
41-
24+
PORTS_SETTINGS = (
4225
# All port-related settings are valid at compile time
4326
'USE_SDL',
4427
'USE_LIBPNG',
@@ -62,11 +45,45 @@
6245
'USE_MPG123',
6346
'USE_GIFLIB',
6447
'USE_FREETYPE',
48+
'SDL2_MIXER_FORMATS',
49+
'SDL2_IMAGE_FORMATS',
6550
)
6651

52+
# Subset of settings that apply at compile time.
53+
# (Keep in sync with [compile] comments in settings.js)
54+
COMPILE_TIME_SETTINGS = (
55+
'MEMORY64',
56+
'INLINING_LIMIT',
57+
'DISABLE_EXCEPTION_CATCHING',
58+
'DISABLE_EXCEPTION_THROWING',
59+
'MAIN_MODULE',
60+
'SIDE_MODULE',
61+
'RELOCATABLE',
62+
'STRICT',
63+
'EMSCRIPTEN_TRACING',
64+
'USE_PTHREADS',
65+
'SUPPORT_LONGJMP',
66+
'DEFAULT_TO_CXX',
67+
'WASM_OBJECT_FILES',
68+
69+
# Internal settings used during compilation
70+
'EXCEPTION_CATCHING_ALLOWED',
71+
'EXCEPTION_HANDLING',
72+
'LTO',
73+
'OPT_LEVEL',
74+
'DEBUG_LEVEL',
75+
76+
# This is legacy setting that we happen to handle very early on
77+
'RUNTIME_LINKED_LIBS',
78+
# TODO: should not be here
79+
'AUTO_ARCHIVE_INDEXES',
80+
'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE',
81+
) + PORTS_SETTINGS
82+
6783

6884
class SettingsManager:
6985
attrs = {}
86+
allowed_settings = []
7087
legacy_settings = {}
7188
alt_names = {}
7289
internal_settings = set()
@@ -76,6 +93,7 @@ def __init__(self):
7693
self.legacy_settings.clear()
7794
self.alt_names.clear()
7895
self.internal_settings.clear()
96+
self.allowed_settings.clear()
7997

8098
# Load the JS defaults into python.
8199
settings = open(path_from_root('src', 'settings.js')).read().replace('//', '#')
@@ -118,13 +136,24 @@ def dict(self):
118136
def keys(self):
119137
return self.attrs.keys()
120138

139+
def limit_settings(self, allowed):
140+
self.allowed_settings.clear()
141+
if allowed:
142+
self.allowed_settings.extend(allowed)
143+
121144
def __getattr__(self, attr):
145+
if self.allowed_settings:
146+
assert attr in self.allowed_settings, f"internal error: attempt to read setting '{attr}' while in limited settings mode"
147+
122148
if attr in self.attrs:
123149
return self.attrs[attr]
124150
else:
125-
raise AttributeError("no such setting: '%s'" % attr)
151+
raise AttributeError(f"no such setting: '{attr}'")
126152

127153
def __setattr__(self, name, value):
154+
if self.allowed_settings:
155+
assert name in self.allowed_settings, f"internal error: attempt to write setting '{name}' while in limited settings mode"
156+
128157
if name == 'STRICT' and value:
129158
for a in self.legacy_settings:
130159
self.attrs.pop(a, None)

tools/system_libs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,7 @@ def get_ports_libs(settings):
18801880

18811881
for port in dependency_order(needed):
18821882
if port.needed(settings):
1883+
port.linker_setup(Ports, settings)
18831884
# ports return their output files, which will be linked, or a txt file
18841885
ret += [f for f in port.get(Ports, settings, shared) if not f.endswith('.txt')]
18851886

@@ -1900,7 +1901,7 @@ def add_ports_cflags(args, settings): # noqa: U100
19001901

19011902
needed = get_needed_ports(settings)
19021903

1903-
# Now get (i.e. build) the ports independency order. This is important because the
1904+
# Now get (i.e. build) the ports in dependency order. This is important because the
19041905
# headers from one ports might be needed before we can build the next.
19051906
for port in dependency_order(needed):
19061907
port.get(Ports, settings, shared)

0 commit comments

Comments
 (0)