Skip to content

Commit

Permalink
Parse -s changes using the json parser where possible, allowing human…
Browse files Browse the repository at this point in the history
…-readable C++ names in -s lists, which is important for Asyncify (emscripten-core#9132)

We have nice syntax for lists, -s LIST=[foo,bar] which works
well in many cases. But sometimes the list contains items
that contain ",", for example -s LIST=["foo(x, y)", "bar"]. To handle
that properly, try to parse things that look like json using the json
parser; if it fails go back to the old code.

Together with WebAssembly/binaryen#2275 
this fixes emscripten-core#9128 where the lists there actually do have ","s in them.

Add testing for Asyncify lists with such names, and also added
extra warnings for when escaping is confusing, and a test for that
as well.
  • Loading branch information
kripken authored and belraquib committed Dec 23, 2020
1 parent 7aff17f commit 633f989
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 7 deletions.
20 changes: 19 additions & 1 deletion emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,9 +1591,22 @@ def parse_passes(string):
shared.Settings.ASYNCIFY_IMPORTS += ['__call_main']
if shared.Settings.ASYNCIFY_IMPORTS:
passes += ['--pass-arg=asyncify-imports@%s' % ','.join(['env.' + i for i in shared.Settings.ASYNCIFY_IMPORTS])]

# shell escaping can be confusing; try to emit useful warnings
def check_human_readable_list(items):
for item in items:
if item.count('(') != item.count(')'):
logger.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''')
logger.warning(''' ''' + item)
logger.warning('''This may indicate improper escaping that led to splitting inside your names.''')
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_WHITELIST=["foo(int, char)", "bar"]' ''')
break

if shared.Settings.ASYNCIFY_BLACKLIST:
check_human_readable_list(shared.Settings.ASYNCIFY_BLACKLIST)
passes += ['--pass-arg=asyncify-blacklist@%s' % ','.join(shared.Settings.ASYNCIFY_BLACKLIST)]
if shared.Settings.ASYNCIFY_WHITELIST:
check_human_readable_list(shared.Settings.ASYNCIFY_WHITELIST)
passes += ['--pass-arg=asyncify-whitelist@%s' % ','.join(shared.Settings.ASYNCIFY_WHITELIST)]
if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
passes += ['--ignore-implicit-traps']
Expand Down Expand Up @@ -3494,7 +3507,12 @@ def parse_string_list(text):
return parse_string_list_members(inner)

if text[0] == '[':
return parse_string_list(text)
# if json parsing fails, we fall back to our own parser, which can handle a few
# simpler syntaxes
try:
return json.loads(text)
except:
return parse_string_list(text)

try:
return int(text)
Expand Down
4 changes: 3 additions & 1 deletion tests/core/test_asyncify_lists.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <assert.h>
#include <stdio.h>
#include <emscripten.h>

Expand All @@ -7,8 +8,9 @@ int bar();

// Foo is a function that needs asyncify support.
__attribute__((noinline))
int foo() {
int foo(int a = 0, double b = 0) {
if (x == 1337) return bar(); // don't inline me
assert(a == b);
emscripten_sleep(1);
return 1;
}
Expand Down
10 changes: 5 additions & 5 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7555,13 +7555,13 @@ def test_coroutine_emterpretify_async(self):

@parameterized({
'normal': ([], True),
'blacklist_a': (['-s', 'ASYNCIFY_BLACKLIST=["foo()"]'], False),
'blacklist_a': (['-s', 'ASYNCIFY_BLACKLIST=["foo(int, double)"]'], False),
'blacklist_b': (['-s', 'ASYNCIFY_BLACKLIST=["bar()"]'], True),
'blacklist_c': (['-s', 'ASYNCIFY_BLACKLIST=["baz()"]'], False),
'whitelist_a': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo()","baz()","c_baz","Structy::funcy()","bar()"]'], True),
'whitelist_b': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo()","baz()","c_baz","Structy::funcy()"]'], True),
'whitelist_c': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo()","baz()","c_baz"]'], False),
'whitelist_d': (['-s', 'ASYNCIFY_WHITELIST=["foo()","baz()","c_baz","Structy::funcy()"]'], False),
'whitelist_a': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True),
'whitelist_b': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True),
'whitelist_c': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False),
'whitelist_d': (['-s', 'ASYNCIFY_WHITELIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False),
})
@no_fastcomp('new asyncify only')
def test_asyncify_lists(self, args, should_pass):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8833,6 +8833,23 @@ def test_emcc_parsing(self):
self.assertNotEqual(proc.returncode, 0)
self.assertContained(expected, proc.stderr)

@no_fastcomp('uses new ASYNCIFY')
def test_asyncify_escaping(self):
proc = run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "ASYNCIFY_WHITELIST=[DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)]"], stdout=PIPE, stderr=PIPE)
self.assertContained('emcc: ASYNCIFY list contains an item without balanced parentheses', proc.stderr)
self.assertContained(' DOS_ReadFile(unsigned short', proc.stderr)
self.assertContained('Try to quote the entire argument', proc.stderr)

@no_fastcomp('uses new ASYNCIFY')
def test_asyncify_response_file(self):
create_test_file('a.txt', r'''[
"DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)"
]
''')
proc = run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'ASYNCIFY=1', '-s', "[email protected]"], stdout=PIPE, stderr=PIPE)
# we should parse the response file properly, and then issue a proper warning for the missing function
self.assertContained('Asyncify whitelist contained a non-existing function name: DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)', proc.stderr)

# Sockets and networking

def test_inet(self):
Expand Down

0 comments on commit 633f989

Please sign in to comment.