Skip to content

Commit f5f53bb

Browse files
Beuckripken
authored andcommitted
asyncify: support *-matching in whitelist and blacklist (#2344)
See emscripten-core/emscripten#9381 for rationale.
1 parent e0bfb1c commit f5f53bb

3 files changed

Lines changed: 82 additions & 38 deletions

File tree

src/passes/Asyncify.cpp

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,15 @@
229229
// input might reach code paths you missed during testing, so it's hard
230230
// to know you got this right), so this is not recommended unless you
231231
// really know what are doing, and need to optimize every bit of speed
232-
// and size.
232+
// and size. '*' wildcard matching supported.
233233
//
234234
// As with --asyncify-imports, you can use a response file here.
235235
//
236236
// --pass-arg=asyncify-whitelist@name1,name2,name3
237237
//
238238
// If the whitelist is provided, then only the functions in the list
239239
// will be instrumented. Like the blacklist, getting this wrong will
240-
// break your application.
240+
// break your application. '*' wildcard matching supported.
241241
//
242242
// As with --asyncify-imports, you can use a response file here.
243243
//
@@ -334,6 +334,65 @@ class GlobalHelper {
334334
std::map<Name, Type> rev;
335335
};
336336

337+
class PatternMatcher {
338+
public:
339+
std::string designation;
340+
std::set<Name> names;
341+
std::set<std::string> patterns;
342+
std::set<std::string> patternsMatched;
343+
344+
PatternMatcher(std::string designation,
345+
Module& module,
346+
const String::Split& list)
347+
: designation(designation) {
348+
// The lists contain human-readable strings. Turn them into the
349+
// internal escaped names for later comparisons
350+
for (auto& name : list) {
351+
auto escaped = WasmBinaryBuilder::escape(name);
352+
if (name.find('*') != std::string::npos) {
353+
patterns.insert(std::string(escaped.str));
354+
} else {
355+
auto* func = module.getFunctionOrNull(escaped);
356+
if (!func) {
357+
std::cerr << "warning: Asyncify " << designation
358+
<< "list contained a non-existing function name: " << name
359+
<< " (" << escaped << ")\n";
360+
} else if (func->imported()) {
361+
Fatal() << "Asyncify " << designation
362+
<< "list contained an imported function name (use the import "
363+
"list for imports): "
364+
<< name << '\n';
365+
}
366+
names.insert(escaped.str);
367+
}
368+
}
369+
}
370+
371+
bool match(Name funcName) {
372+
if (names.count(funcName) > 0) {
373+
return true;
374+
} else {
375+
for (auto& pattern : patterns) {
376+
if (String::wildcardMatch(pattern, funcName.str)) {
377+
patternsMatched.insert(pattern);
378+
return true;
379+
}
380+
}
381+
}
382+
return false;
383+
}
384+
385+
void checkPatternsMatches() {
386+
for (auto& pattern : patterns) {
387+
if (patternsMatched.count(pattern) == 0) {
388+
std::cerr << "warning: Asyncify " << designation
389+
<< "list contained a non-matching pattern: " << pattern
390+
<< "\n";
391+
}
392+
}
393+
}
394+
};
395+
337396
// Analyze the entire module to see which calls may change the state, that
338397
// is, start an unwind or rewind), either in itself or in something called
339398
// by it.
@@ -374,8 +433,8 @@ class ModuleAnalyzer {
374433
: module(module), canIndirectChangeState(canIndirectChangeState),
375434
globals(module), asserts(asserts) {
376435

377-
blacklist.insert(blacklistInput.begin(), blacklistInput.end());
378-
whitelist.insert(whitelistInput.begin(), whitelistInput.end());
436+
PatternMatcher blacklist("black", module, blacklistInput);
437+
PatternMatcher whitelist("white", module, whitelistInput);
379438

380439
// Scan to see which functions can directly change the state.
381440
// Also handle the asyncify imports, removing them (as we will implement
@@ -456,7 +515,7 @@ class ModuleAnalyzer {
456515
map.swap(scanner.map);
457516

458517
// Functions in the blacklist are assumed to not change the state.
459-
for (auto& name : blacklist) {
518+
for (auto& name : blacklist.names) {
460519
if (auto* func = module.getFunctionOrNull(name)) {
461520
map[func].canChangeState = false;
462521
}
@@ -492,21 +551,24 @@ class ModuleAnalyzer {
492551
auto* func = work.pop();
493552
for (auto* caller : map[func].calledBy) {
494553
if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime &&
495-
!blacklist.count(caller->name)) {
554+
!blacklist.match(caller->name)) {
496555
map[caller].canChangeState = true;
497556
work.push(caller);
498557
}
499558
}
500559
}
501560

502-
if (!whitelist.empty()) {
561+
if (!whitelistInput.empty()) {
503562
// Only the functions in the whitelist can change the state.
504563
for (auto& func : module.functions) {
505564
if (!func->imported()) {
506-
map[func.get()].canChangeState = whitelist.count(func->name) > 0;
565+
map[func.get()].canChangeState = whitelist.match(func->name);
507566
}
508567
}
509568
}
569+
570+
blacklist.checkPatternsMatches();
571+
whitelist.checkPatternsMatches();
510572
}
511573

512574
bool needsInstrumentation(Function* func) {
@@ -566,8 +628,6 @@ class ModuleAnalyzer {
566628
}
567629

568630
GlobalHelper globals;
569-
std::set<Name> blacklist;
570-
std::set<Name> whitelist;
571631
bool asserts;
572632
};
573633

@@ -1138,28 +1198,6 @@ struct Asyncify : public Pass {
11381198
blacklist = handleBracketingOperators(blacklist);
11391199
whitelist = handleBracketingOperators(whitelist);
11401200

1141-
// The lists contain human-readable strings. Turn them into the internal
1142-
// escaped names for later comparisons
1143-
auto processList = [module](String::Split& list, const std::string& which) {
1144-
for (auto& name : list) {
1145-
auto escaped = WasmBinaryBuilder::escape(name);
1146-
auto* func = module->getFunctionOrNull(escaped);
1147-
if (!func) {
1148-
std::cerr << "warning: Asyncify " << which
1149-
<< "list contained a non-existing function name: " << name
1150-
<< " (" << escaped << ")\n";
1151-
} else if (func->imported()) {
1152-
Fatal() << "Asyncify " << which
1153-
<< "list contained an imported function name (use the import "
1154-
"list for imports): "
1155-
<< name << '\n';
1156-
}
1157-
name = escaped.str;
1158-
}
1159-
};
1160-
processList(blacklist, "black");
1161-
processList(whitelist, "white");
1162-
11631201
if (!blacklist.empty() && !whitelist.empty()) {
11641202
Fatal() << "It makes no sense to use both a blacklist and a whitelist "
11651203
"with asyncify.";

src/support/string.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,18 @@ inline String::Split handleBracketingOperators(String::Split split) {
8787
return ret;
8888
}
8989

90-
// Does a simple wildcard match between a pattern and a value. Currently
91-
// supports a '*' at the end of the pattern.
90+
// Does a simple '*' wildcard match between a pattern and a value.
9291
inline bool wildcardMatch(const std::string& pattern,
9392
const std::string& value) {
9493
for (size_t i = 0; i < pattern.size(); i++) {
94+
if (pattern[i] == '*') {
95+
return wildcardMatch(pattern.substr(i + 1), value.substr(i)) ||
96+
(value.size() > 0 &&
97+
wildcardMatch(pattern.substr(i), value.substr(i + 1)));
98+
}
9599
if (i >= value.size()) {
96100
return false;
97101
}
98-
if (pattern[i] == '*') {
99-
return true;
100-
}
101102
if (pattern[i] != value[i]) {
102103
return false;
103104
}

test/unit/test_asyncify.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ def test_asyncify_list_bad(self):
3636
('--pass-arg=asyncify-whitelist@nonexistent', 'nonexistent'),
3737
('--pass-arg=asyncify-blacklist@main', None),
3838
('--pass-arg=asyncify-whitelist@main', None),
39-
('--pass-arg=asyncify-whitelist@main', None),
39+
('--pass-arg=asyncify-blacklist@m*n', None),
40+
('--pass-arg=asyncify-whitelist@m*n', None),
41+
('--pass-arg=asyncify-whitelist@main*', None),
42+
('--pass-arg=asyncify-whitelist@*main', None),
43+
('--pass-arg=asyncify-blacklist@non*existent', 'non*existent'),
44+
('--pass-arg=asyncify-whitelist@non*existent', 'non*existent'),
4045
('--pass-arg=asyncify-whitelist@DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)', None),
4146
]:
4247
print(arg, warning)

0 commit comments

Comments
 (0)