2525import contextlib
2626import os
2727import difflib
28+ import json
2829import math
2930import shutil
3031import subprocess
@@ -452,6 +453,13 @@ def pick_initial_contents():
452453STACK_LIMIT = '[trap stack limit]'
453454
454455
456+ # given a call line that includes FUZZ_EXEC_CALL_PREFIX, return the export that
457+ # is called
458+ def get_export_from_call_line (call_line ):
459+ assert FUZZ_EXEC_CALL_PREFIX in call_line
460+ return call_line .split (FUZZ_EXEC_CALL_PREFIX )[1 ].strip ()
461+
462+
455463# compare two strings, strictly
456464def compare (x , y , context , verbose = True ):
457465 if x != y and x != IGNORE and y != IGNORE :
@@ -1119,6 +1127,30 @@ def can_run_on_feature_opts(self, feature_opts):
11191127 return all_disallowed (['exception-handling' , 'simd' , 'tail-call' , 'reference-types' , 'multivalue' , 'gc' , 'multi-memories' ])
11201128
11211129
1130+ # given a wasm and a list of exports we want to keep, remove all other exports.
1131+ def filter_exports (wasm , output , keep ):
1132+ # based on
1133+ # https://github.com/WebAssembly/binaryen/wiki/Pruning-unneeded-code-in-wasm-files-with-wasm-metadce#example-pruning-exports
1134+
1135+ # build json to represent the exports we want.
1136+ graph = [{
1137+ 'name' : 'outside' ,
1138+ 'reaches' : [f'export-{ export } ' for export in keep ],
1139+ 'root' : True
1140+ }]
1141+ for export in keep :
1142+ graph .append ({
1143+ 'name' : f'export-{ export } ' ,
1144+ 'export' : export
1145+ })
1146+
1147+ with open ('graph.json' , 'w' ) as f :
1148+ f .write (json .dumps (graph ))
1149+
1150+ # prune the exports
1151+ run ([in_bin ('wasm-metadce' ), wasm , '-o' , output , '--graph-file' , 'graph.json' , '-all' ])
1152+
1153+
11221154# Fuzz the interpreter with --fuzz-exec -tnh. The tricky thing with traps-never-
11231155# happen mode is that if a trap *does* happen then that is undefined behavior,
11241156# and the optimizer was free to make changes to observable behavior there. The
@@ -1128,20 +1160,24 @@ class TrapsNeverHappen(TestCaseHandler):
11281160
11291161 def handle_pair (self , input , before_wasm , after_wasm , opts ):
11301162 before = run_bynterp (before_wasm , ['--fuzz-exec-before' ])
1131- after_wasm_tnh = after_wasm + '.tnh.wasm'
1132- run ([in_bin ('wasm-opt' ), before_wasm , '-o' , after_wasm_tnh , '-tnh' ] + opts + FEATURE_OPTS )
1133- after = run_bynterp (after_wasm_tnh , ['--fuzz-exec-before' ])
1163+
1164+ if before == IGNORE :
1165+ # There is no point to continue since we can't compare this output
1166+ # to anything, and there is a risk since if we did so we might run
1167+ # into an infinite loop (see below).
1168+ return
11341169
11351170 # if a trap happened, we must stop comparing from that.
11361171 if TRAP_PREFIX in before :
11371172 trap_index = before .index (TRAP_PREFIX )
11381173 # we can't test this function, which the trap is in the middle of
11391174 # (tnh could move the trap around, so even things before the trap
1140- # are unsafe). erase everything from this function's output and
1141- # onward, so we only compare the previous trap-free code. first,
1142- # find the function call during which the trap happened, by finding
1143- # the call line right before us. that is, the output looks like
1144- # this:
1175+ # are unsafe). we can only safely call exports before this one, so
1176+ # remove those from the binary.
1177+ #
1178+ # first, find the function call during which the trap happened, by
1179+ # finding the call line right before us. that is, the output looks
1180+ # like this:
11451181 #
11461182 # [fuzz-exec] calling foo
11471183 # .. stuff happening during foo ..
@@ -1164,23 +1200,30 @@ def handle_pair(self, input, before_wasm, after_wasm, opts):
11641200 # happens, which is something like "[fuzz-exec] calling bar", and
11651201 # it is unique since it contains the function being called.
11661202 call_line = before [call_start :call_end ]
1167- # remove everything from that call line onward.
1168- lines_pre = before .count (os .linesep )
1169- before = before [:call_start ]
1170- lines_post = before .count (os .linesep )
1171- print (f'ignoring code due to trap (from "{ call_line } "), lines to compare goes { lines_pre } => { lines_post } ' )
1172-
1173- # also remove the relevant lines from after.
1174- if call_line not in after :
1175- # the normal run hit a trap, and the tnh run hit a host
1176- # limitation that forces us to ignore this run. for example,
1177- # after running tnh we may end up doing an unbounded number of
1178- # allocations, if that is what the program normally does (and
1179- # the normal run only avoided that by trapping).
1180- assert IGNORE in after
1181- return
1182- after_index = after .index (call_line )
1183- after = after [:after_index ]
1203+ trapping_export = get_export_from_call_line (call_line )
1204+
1205+ # now that we know the trapping export, we can leave only the safe
1206+ # ones that are before it
1207+ safe_exports = []
1208+ for line in before .splitlines ():
1209+ if FUZZ_EXEC_CALL_PREFIX in line :
1210+ export = get_export_from_call_line (line )
1211+ if export == trapping_export :
1212+ break
1213+ safe_exports .append (export )
1214+
1215+ # filter out the other exports
1216+ filtered = before_wasm + '.filtered.wasm'
1217+ filter_exports (before_wasm , filtered , safe_exports )
1218+ before_wasm = filtered
1219+
1220+ # re-execute the now safe wasm
1221+ before = run_bynterp (before_wasm , ['--fuzz-exec-before' ])
1222+ assert TRAP_PREFIX not in before , 'we should have fixed this problem'
1223+
1224+ after_wasm_tnh = after_wasm + '.tnh.wasm'
1225+ run ([in_bin ('wasm-opt' ), before_wasm , '-o' , after_wasm_tnh , '-tnh' ] + opts + FEATURE_OPTS )
1226+ after = run_bynterp (after_wasm_tnh , ['--fuzz-exec-before' ])
11841227
11851228 # some results cannot be compared, so we must filter them out here.
11861229 def ignore_references (out ):
0 commit comments