diff options
-rw-r--r-- | src/passes/Asyncify.cpp | 102 | ||||
-rw-r--r-- | src/support/string.h | 11 | ||||
-rw-r--r-- | test/unit/test_asyncify.py | 7 |
3 files changed, 82 insertions, 38 deletions
diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index cda9f2d17..2ed34090b 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -229,7 +229,7 @@ // 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. +// and size. '*' wildcard matching supported. // // As with --asyncify-imports, you can use a response file here. // @@ -237,7 +237,7 @@ // // 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. +// break your application. '*' wildcard matching supported. // // As with --asyncify-imports, you can use a response file here. // @@ -334,6 +334,65 @@ private: std::map<Name, Type> rev; }; +class PatternMatcher { +public: + std::string designation; + std::set<Name> names; + std::set<std::string> patterns; + std::set<std::string> patternsMatched; + + PatternMatcher(std::string designation, + Module& module, + const String::Split& list) + : designation(designation) { + // The lists contain human-readable strings. Turn them into the + // internal escaped names for later comparisons + for (auto& name : list) { + auto escaped = WasmBinaryBuilder::escape(name); + if (name.find('*') != std::string::npos) { + patterns.insert(std::string(escaped.str)); + } else { + auto* func = module.getFunctionOrNull(escaped); + if (!func) { + std::cerr << "warning: Asyncify " << designation + << "list contained a non-existing function name: " << name + << " (" << escaped << ")\n"; + } else if (func->imported()) { + Fatal() << "Asyncify " << designation + << "list contained an imported function name (use the import " + "list for imports): " + << name << '\n'; + } + names.insert(escaped.str); + } + } + } + + bool match(Name funcName) { + if (names.count(funcName) > 0) { + return true; + } else { + for (auto& pattern : patterns) { + if (String::wildcardMatch(pattern, funcName.str)) { + patternsMatched.insert(pattern); + return true; + } + } + } + return false; + } + + void checkPatternsMatches() { + for (auto& pattern : patterns) { + if (patternsMatched.count(pattern) == 0) { + std::cerr << "warning: Asyncify " << designation + << "list contained a non-matching pattern: " << pattern + << "\n"; + } + } + } +}; + // Analyze the entire module to see which calls may change the state, that // is, start an unwind or rewind), either in itself or in something called // by it. @@ -374,8 +433,8 @@ public: : module(module), canIndirectChangeState(canIndirectChangeState), globals(module), asserts(asserts) { - blacklist.insert(blacklistInput.begin(), blacklistInput.end()); - whitelist.insert(whitelistInput.begin(), whitelistInput.end()); + PatternMatcher blacklist("black", module, blacklistInput); + PatternMatcher whitelist("white", module, whitelistInput); // Scan to see which functions can directly change the state. // Also handle the asyncify imports, removing them (as we will implement @@ -456,7 +515,7 @@ public: map.swap(scanner.map); // Functions in the blacklist are assumed to not change the state. - for (auto& name : blacklist) { + for (auto& name : blacklist.names) { if (auto* func = module.getFunctionOrNull(name)) { map[func].canChangeState = false; } @@ -492,21 +551,24 @@ public: auto* func = work.pop(); for (auto* caller : map[func].calledBy) { if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime && - !blacklist.count(caller->name)) { + !blacklist.match(caller->name)) { map[caller].canChangeState = true; work.push(caller); } } } - if (!whitelist.empty()) { + if (!whitelistInput.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; + map[func.get()].canChangeState = whitelist.match(func->name); } } } + + blacklist.checkPatternsMatches(); + whitelist.checkPatternsMatches(); } bool needsInstrumentation(Function* func) { @@ -566,8 +628,6 @@ public: } GlobalHelper globals; - std::set<Name> blacklist; - std::set<Name> whitelist; bool asserts; }; @@ -1138,28 +1198,6 @@ struct Asyncify : public Pass { blacklist = handleBracketingOperators(blacklist); whitelist = handleBracketingOperators(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."; diff --git a/src/support/string.h b/src/support/string.h index b3d12c6ae..b93d3b363 100644 --- a/src/support/string.h +++ b/src/support/string.h @@ -87,17 +87,18 @@ inline String::Split handleBracketingOperators(String::Split split) { return ret; } -// Does a simple wildcard match between a pattern and a value. Currently -// supports a '*' at the end of the pattern. +// Does a simple '*' wildcard match between a pattern and a value. inline bool wildcardMatch(const std::string& pattern, const std::string& value) { for (size_t i = 0; i < pattern.size(); i++) { + if (pattern[i] == '*') { + return wildcardMatch(pattern.substr(i + 1), value.substr(i)) || + (value.size() > 0 && + wildcardMatch(pattern.substr(i), value.substr(i + 1))); + } if (i >= value.size()) { return false; } - if (pattern[i] == '*') { - return true; - } if (pattern[i] != value[i]) { return false; } diff --git a/test/unit/test_asyncify.py b/test/unit/test_asyncify.py index e2fe82af9..9b7c403c0 100644 --- a/test/unit/test_asyncify.py +++ b/test/unit/test_asyncify.py @@ -36,7 +36,12 @@ class AsyncifyTest(BinaryenTestCase): ('--pass-arg=asyncify-whitelist@nonexistent', 'nonexistent'), ('--pass-arg=asyncify-blacklist@main', None), ('--pass-arg=asyncify-whitelist@main', None), - ('--pass-arg=asyncify-whitelist@main', None), + ('--pass-arg=asyncify-blacklist@m*n', None), + ('--pass-arg=asyncify-whitelist@m*n', None), + ('--pass-arg=asyncify-whitelist@main*', None), + ('--pass-arg=asyncify-whitelist@*main', None), + ('--pass-arg=asyncify-blacklist@non*existent', 'non*existent'), + ('--pass-arg=asyncify-whitelist@non*existent', 'non*existent'), ('--pass-arg=asyncify-whitelist@DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)', None), ]: print(arg, warning) |