summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/Asyncify.cpp113
-rw-r--r--src/wasm-binary.h1
-rw-r--r--src/wasm/wasm-binary.cpp8
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.txt285
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast20
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt285
-rw-r--r--test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast20
-rw-r--r--test/unit/test_asyncify.py21
8 files changed, 733 insertions, 20 deletions
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp
index 98b4425b1..7d6dd9f76 100644
--- a/src/passes/Asyncify.cpp
+++ b/src/passes/Asyncify.cpp
@@ -212,6 +212,24 @@
// If that is true for your codebase, then this can be extremely useful
// as otherwise it looks like any indirect call can go to a lot of places.
//
+// --pass-arg=asyncify-blacklist@name1,name2,name3
+//
+// If the blacklist is provided, then the functions in it will not be
+// instrumented even if it looks like they need to. This can be useful
+// if you know things the whole-program analysis doesn't, like if you
+// know certain indirect calls are safe and won't unwind. But if you
+// get the list wrong things will break (and in a production build user
+// input might reach code paths you missed during testing, so it's hard
+// to know you got this right), so this is not recommended unless you
+// really know what are doing, and need to optimize every bit of speed
+// and size.
+//
+// --pass-arg=asyncify-whitelist@name1,name2,name3
+//
+// If the whitelist is provided, then only the functions in the list
+// will be instrumented. Like the blacklist, getting this wrong will
+// break your application.
+//
#include "ir/effects.h"
#include "ir/literal-utils.h"
@@ -327,9 +345,15 @@ class ModuleAnalyzer {
public:
ModuleAnalyzer(Module& module,
std::function<bool(Name, Name)> canImportChangeState,
- bool canIndirectChangeState)
+ bool canIndirectChangeState,
+ const String::Split& blacklistInput,
+ const String::Split& whitelistInput)
: module(module), canIndirectChangeState(canIndirectChangeState),
globals(module) {
+
+ blacklist.insert(blacklistInput.begin(), blacklistInput.end());
+ whitelist.insert(whitelistInput.begin(), whitelistInput.end());
+
// Scan to see which functions can directly change the state.
// Also handle the asyncify imports, removing them (as we will implement
// them later), and replace calls to them with calls to the later proper
@@ -388,11 +412,13 @@ public:
}
Info* info;
Module* module;
+ ModuleAnalyzer* analyzer;
bool canIndirectChangeState;
};
Walker walker;
walker.info = &info;
walker.module = &module;
+ walker.analyzer = this;
walker.canIndirectChangeState = canIndirectChangeState;
walker.walk(func->body);
@@ -406,6 +432,13 @@ public:
map.swap(scanner.map);
+ // Functions in the blacklist are assumed to not change the state.
+ for (auto& name : blacklist) {
+ if (auto* func = module.getFunctionOrNull(name)) {
+ map[func].canChangeState = false;
+ }
+ }
+
// Remove the asyncify imports, if any.
for (auto& pair : map) {
auto* func = pair.first;
@@ -435,12 +468,22 @@ public:
while (!work.empty()) {
auto* func = work.pop();
for (auto* caller : map[func].calledBy) {
- if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime) {
+ if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime &&
+ !blacklist.count(caller->name)) {
map[caller].canChangeState = true;
work.push(caller);
}
}
}
+
+ if (!whitelist.empty()) {
+ // Only the functions in the whitelist can change the state.
+ for (auto& func : module.functions) {
+ if (!func->imported()) {
+ map[func.get()].canChangeState = whitelist.count(func->name) > 0;
+ }
+ }
+ }
}
bool needsInstrumentation(Function* func) {
@@ -481,6 +524,7 @@ public:
// TODO optimize the other case, at least by type
}
Module* module;
+ ModuleAnalyzer* analyzer;
Map* map;
bool canIndirectChangeState;
bool canChangeState = false;
@@ -488,6 +532,7 @@ public:
};
Walker walker;
walker.module = &module;
+ walker.analyzer = this;
walker.map = &map;
walker.canIndirectChangeState = canIndirectChangeState;
walker.walk(curr);
@@ -498,6 +543,8 @@ public:
}
GlobalHelper globals;
+ std::set<Name> blacklist;
+ std::set<Name> whitelist;
};
// Checks if something performs a call: either a direct or indirect call,
@@ -987,23 +1034,57 @@ struct Asyncify : public Pass {
String::Split listedImports(stateChangingImports, ",");
auto ignoreIndirect =
runner->options.getArgumentOrDefault("asyncify-ignore-indirect", "");
+ String::Split blacklist(
+ runner->options.getArgumentOrDefault("asyncify-blacklist", ""), ",");
+ String::Split whitelist(
+ runner->options.getArgumentOrDefault("asyncify-whitelist", ""), ",");
+
+ // The lists contain human-readable strings. Turn them into the internal
+ // escaped names for later comparisons
+ auto processList = [module](String::Split& list, const std::string& which) {
+ for (auto& name : list) {
+ auto escaped = WasmBinaryBuilder::escape(name);
+ auto* func = module->getFunctionOrNull(escaped);
+ if (!func) {
+ std::cerr << "warning: Asyncify " << which
+ << "list contained a non-existing function name: " << name
+ << " (" << escaped << ")\n";
+ } else if (func->imported()) {
+ Fatal() << "Asyncify " << which
+ << "list contained an imported function name (use the import "
+ "list for imports): "
+ << name << '\n';
+ }
+ name = escaped.str;
+ }
+ };
+ processList(blacklist, "black");
+ processList(whitelist, "white");
+
+ if (!blacklist.empty() && !whitelist.empty()) {
+ Fatal() << "It makes no sense to use both a blacklist and a whitelist "
+ "with asyncify.";
+ }
+
+ auto canImportChangeState = [&](Name module, Name base) {
+ if (allImportsCanChangeState) {
+ return true;
+ }
+ std::string full = std::string(module.str) + '.' + base.str;
+ for (auto& listedImport : listedImports) {
+ if (String::wildcardMatch(listedImport, full)) {
+ return true;
+ }
+ }
+ return false;
+ };
// Scan the module.
ModuleAnalyzer analyzer(*module,
- [&](Name module, Name base) {
- if (allImportsCanChangeState) {
- return true;
- }
- std::string full =
- std::string(module.str) + '.' + base.str;
- for (auto& listedImport : listedImports) {
- if (String::wildcardMatch(listedImport, full)) {
- return true;
- }
- }
- return false;
- },
- ignoreIndirect == "");
+ canImportChangeState,
+ ignoreIndirect == "",
+ blacklist,
+ whitelist);
// Add necessary globals before we emit code to use them.
addGlobals(module);
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index e1eb5733c..f58a10cf7 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1174,6 +1174,7 @@ public:
void readEvents();
+ static Name escape(Name name);
void readNames(size_t);
void readFeatures(size_t);
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 3d9a1ba9b..039cef5e5 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -2054,13 +2054,13 @@ static char formatNibble(int nibble) {
return nibble < 10 ? '0' + nibble : 'a' - 10 + nibble;
}
-static void escapeName(Name& name) {
+Name WasmBinaryBuilder::escape(Name name) {
bool allIdChars = true;
for (const char* p = name.str; allIdChars && *p; p++) {
allIdChars = isIdChar(*p);
}
if (allIdChars) {
- return;
+ return name;
}
// encode name, if at least one non-idchar (per WebAssembly spec) was found
std::string escaped;
@@ -2075,7 +2075,7 @@ static void escapeName(Name& name) {
escaped.push_back(formatNibble(ch >> 4));
escaped.push_back(formatNibble(ch & 15));
}
- name = escaped;
+ return escaped;
}
void WasmBinaryBuilder::readNames(size_t payloadLen) {
@@ -2098,7 +2098,7 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
for (size_t i = 0; i < num; i++) {
auto index = getU32LEB();
auto rawName = getInlineString();
- escapeName(rawName);
+ rawName = escape(rawName);
auto name = rawName;
// De-duplicate names by appending .1, .2, etc.
for (int i = 1; !usedNames.insert(name).second; ++i) {
diff --git a/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.txt b/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.txt
new file mode 100644
index 000000000..2c7ad5fe0
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.txt
@@ -0,0 +1,285 @@
+(module
+ (type $FUNCSIG$v (func))
+ (import "env" "import" (func $import))
+ (memory $0 1 2)
+ (global $__asyncify_state (mut i32) (i32.const 0))
+ (global $__asyncify_data (mut i32) (i32.const 0))
+ (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+ (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+ (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+ (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+ (func $foo (; 1 ;) (type $FUNCSIG$v)
+ (call $import)
+ (nop)
+ )
+ (func $bar (; 2 ;) (type $FUNCSIG$v)
+ (call $import)
+ (nop)
+ )
+ (func $baz (; 3 ;) (type $FUNCSIG$v)
+ (local $0 i32)
+ (local $1 i32)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (nop)
+ )
+ (local.set $0
+ (block $__asyncify_unwind (result i32)
+ (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (block
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const -4)
+ )
+ )
+ (local.set $1
+ (i32.load
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ )
+ )
+ )
+ )
+ (block
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $1)
+ (i32.const 0)
+ )
+ )
+ (block
+ (call $import)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 1)
+ )
+ (br $__asyncify_unwind
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (nop)
+ )
+ )
+ )
+ (return)
+ )
+ )
+ )
+ (block
+ (i32.store
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (local.get $0)
+ )
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const 4)
+ )
+ )
+ )
+ (nop)
+ )
+ (func $other1 (; 4 ;) (type $FUNCSIG$v)
+ (call $foo)
+ (nop)
+ )
+ (func $other2 (; 5 ;) (type $FUNCSIG$v)
+ (local $0 i32)
+ (local $1 i32)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (nop)
+ )
+ (local.set $0
+ (block $__asyncify_unwind (result i32)
+ (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (block
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const -4)
+ )
+ )
+ (local.set $1
+ (i32.load
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ )
+ )
+ )
+ )
+ (block
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $1)
+ (i32.const 0)
+ )
+ )
+ (block
+ (call $baz)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 1)
+ )
+ (br $__asyncify_unwind
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (nop)
+ )
+ )
+ )
+ (return)
+ )
+ )
+ )
+ (block
+ (i32.store
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (local.get $0)
+ )
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const 4)
+ )
+ )
+ )
+ (nop)
+ )
+ (func $asyncify_start_unwind (; 6 ;) (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 1)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_unwind (; 7 ;)
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_start_rewind (; 8 ;) (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 2)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_rewind (; 9 ;)
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+)
diff --git a/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast b/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast
new file mode 100644
index 000000000..8496e7ee2
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-blacklist@foo,bar.wast
@@ -0,0 +1,20 @@
+(module
+ (memory 1 2)
+ (import "env" "import" (func $import))
+ (func $foo
+ (call $import)
+ )
+ (func $bar
+ (call $import)
+ )
+ (func $baz
+ (call $import)
+ )
+ (func $other1
+ (call $foo)
+ )
+ (func $other2
+ (call $baz)
+ )
+)
+
diff --git a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt b/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt
new file mode 100644
index 000000000..e69bc19a7
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.txt
@@ -0,0 +1,285 @@
+(module
+ (type $FUNCSIG$v (func))
+ (import "env" "import" (func $import))
+ (memory $0 1 2)
+ (global $__asyncify_state (mut i32) (i32.const 0))
+ (global $__asyncify_data (mut i32) (i32.const 0))
+ (export "asyncify_start_unwind" (func $asyncify_start_unwind))
+ (export "asyncify_stop_unwind" (func $asyncify_stop_unwind))
+ (export "asyncify_start_rewind" (func $asyncify_start_rewind))
+ (export "asyncify_stop_rewind" (func $asyncify_stop_rewind))
+ (func $foo (; 1 ;) (type $FUNCSIG$v)
+ (local $0 i32)
+ (local $1 i32)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (nop)
+ )
+ (local.set $0
+ (block $__asyncify_unwind (result i32)
+ (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (block
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const -4)
+ )
+ )
+ (local.set $1
+ (i32.load
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ )
+ )
+ )
+ )
+ (block
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $1)
+ (i32.const 0)
+ )
+ )
+ (block
+ (call $import)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 1)
+ )
+ (br $__asyncify_unwind
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (nop)
+ )
+ )
+ )
+ (return)
+ )
+ )
+ )
+ (block
+ (i32.store
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (local.get $0)
+ )
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const 4)
+ )
+ )
+ )
+ (nop)
+ )
+ (func $bar (; 2 ;) (type $FUNCSIG$v)
+ (local $0 i32)
+ (local $1 i32)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (nop)
+ )
+ (local.set $0
+ (block $__asyncify_unwind (result i32)
+ (block
+ (block
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 2)
+ )
+ (block
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const -4)
+ )
+ )
+ (local.set $1
+ (i32.load
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ )
+ )
+ )
+ )
+ (block
+ (if
+ (if (result i32)
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (i32.const 1)
+ (i32.eq
+ (local.get $1)
+ (i32.const 0)
+ )
+ )
+ (block
+ (call $import)
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 1)
+ )
+ (br $__asyncify_unwind
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (if
+ (i32.eq
+ (global.get $__asyncify_state)
+ (i32.const 0)
+ )
+ (nop)
+ )
+ )
+ )
+ (return)
+ )
+ )
+ )
+ (block
+ (i32.store
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (local.get $0)
+ )
+ (i32.store
+ (global.get $__asyncify_data)
+ (i32.add
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.const 4)
+ )
+ )
+ )
+ (nop)
+ )
+ (func $baz (; 3 ;) (type $FUNCSIG$v)
+ (call $import)
+ (nop)
+ )
+ (func $other1 (; 4 ;) (type $FUNCSIG$v)
+ (call $foo)
+ (nop)
+ )
+ (func $other2 (; 5 ;) (type $FUNCSIG$v)
+ (call $baz)
+ (nop)
+ )
+ (func $asyncify_start_unwind (; 6 ;) (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 1)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_unwind (; 7 ;)
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_start_rewind (; 8 ;) (param $0 i32)
+ (global.set $__asyncify_state
+ (i32.const 2)
+ )
+ (global.set $__asyncify_data
+ (local.get $0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+ (func $asyncify_stop_rewind (; 9 ;)
+ (global.set $__asyncify_state
+ (i32.const 0)
+ )
+ (if
+ (i32.gt_u
+ (i32.load
+ (global.get $__asyncify_data)
+ )
+ (i32.load offset=4
+ (global.get $__asyncify_data)
+ )
+ )
+ (unreachable)
+ )
+ )
+)
diff --git a/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast b/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast
new file mode 100644
index 000000000..355ca1b11
--- /dev/null
+++ b/test/passes/asyncify_pass-arg=asyncify-whitelist@foo,bar.wast
@@ -0,0 +1,20 @@
+(module
+ (memory 1 2)
+ (import "env" "import" (func $import))
+ (func $foo
+ (call $import)
+ )
+ (func $bar
+ (call $import)
+ )
+ (func $baz
+ (call $import)
+ )
+ (func $other1
+ (call $foo) ;; even though we call foo, we are not in the whitelist, so do not instrument us
+ )
+ (func $other2
+ (call $baz)
+ )
+)
+
diff --git a/test/unit/test_asyncify.py b/test/unit/test_asyncify.py
index 479d24cab..40ee160d2 100644
--- a/test/unit/test_asyncify.py
+++ b/test/unit/test_asyncify.py
@@ -1,4 +1,5 @@
import os
+import subprocess
from scripts.test.shared import WASM_OPT, WASM_DIS, WASM_SHELL, NODEJS, run_process
from utils import BinaryenTestCase
@@ -27,3 +28,23 @@ class AsyncifyTest(BinaryenTestCase):
output = run_process(WASM_SHELL + ['a.wast'], capture_output=True).stdout
with open(self.input_path('asyncify-pure.txt')) as f:
self.assertEqual(f.read(), output)
+
+ def test_asyncify_list_bad(self):
+ for arg, warning in [
+ ('--pass-arg=asyncify-blacklist@nonexistent', 'nonexistent'),
+ ('--pass-arg=asyncify-whitelist@nonexistent', 'nonexistent'),
+ ('--pass-arg=asyncify-blacklist@main', None),
+ ('--pass-arg=asyncify-whitelist@main', None),
+ ]:
+ print(arg, warning)
+ err = run_process(WASM_OPT + [self.input_path('asyncify-pure.wast'), '--asyncify', arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE).stderr.strip()
+ if warning:
+ self.assertIn('warning', err)
+ self.assertIn(warning, err)
+ else:
+ self.assertNotIn('warning', err)
+
+ def test_asyncify_blacklist_and_whitelist(self):
+ proc = run_process(WASM_OPT + [self.input_path('asyncify-pure.wast'), '--asyncify', '--pass-arg=asyncify-whitelist@main', '--pass-arg=asyncify-blacklist@main'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
+ self.assertNotEqual(proc.returncode, 0, 'must error on using both lists at once')
+ self.assertIn('It makes no sense to use both a blacklist and a whitelist with asyncify', proc.stdout)