|
229 | 229 | // input might reach code paths you missed during testing, so it's hard |
230 | 230 | // to know you got this right), so this is not recommended unless you |
231 | 231 | // really know what are doing, and need to optimize every bit of speed |
232 | | -// and size. |
| 232 | +// and size. '*' wildcard matching supported. |
233 | 233 | // |
234 | 234 | // As with --asyncify-imports, you can use a response file here. |
235 | 235 | // |
236 | 236 | // --pass-arg=asyncify-whitelist@name1,name2,name3 |
237 | 237 | // |
238 | 238 | // If the whitelist is provided, then only the functions in the list |
239 | 239 | // will be instrumented. Like the blacklist, getting this wrong will |
240 | | -// break your application. |
| 240 | +// break your application. '*' wildcard matching supported. |
241 | 241 | // |
242 | 242 | // As with --asyncify-imports, you can use a response file here. |
243 | 243 | // |
@@ -334,6 +334,65 @@ class GlobalHelper { |
334 | 334 | std::map<Name, Type> rev; |
335 | 335 | }; |
336 | 336 |
|
| 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 | + |
337 | 396 | // Analyze the entire module to see which calls may change the state, that |
338 | 397 | // is, start an unwind or rewind), either in itself or in something called |
339 | 398 | // by it. |
@@ -374,8 +433,8 @@ class ModuleAnalyzer { |
374 | 433 | : module(module), canIndirectChangeState(canIndirectChangeState), |
375 | 434 | globals(module), asserts(asserts) { |
376 | 435 |
|
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); |
379 | 438 |
|
380 | 439 | // Scan to see which functions can directly change the state. |
381 | 440 | // Also handle the asyncify imports, removing them (as we will implement |
@@ -456,7 +515,7 @@ class ModuleAnalyzer { |
456 | 515 | map.swap(scanner.map); |
457 | 516 |
|
458 | 517 | // Functions in the blacklist are assumed to not change the state. |
459 | | - for (auto& name : blacklist) { |
| 518 | + for (auto& name : blacklist.names) { |
460 | 519 | if (auto* func = module.getFunctionOrNull(name)) { |
461 | 520 | map[func].canChangeState = false; |
462 | 521 | } |
@@ -492,21 +551,24 @@ class ModuleAnalyzer { |
492 | 551 | auto* func = work.pop(); |
493 | 552 | for (auto* caller : map[func].calledBy) { |
494 | 553 | if (!map[caller].canChangeState && !map[caller].isBottomMostRuntime && |
495 | | - !blacklist.count(caller->name)) { |
| 554 | + !blacklist.match(caller->name)) { |
496 | 555 | map[caller].canChangeState = true; |
497 | 556 | work.push(caller); |
498 | 557 | } |
499 | 558 | } |
500 | 559 | } |
501 | 560 |
|
502 | | - if (!whitelist.empty()) { |
| 561 | + if (!whitelistInput.empty()) { |
503 | 562 | // Only the functions in the whitelist can change the state. |
504 | 563 | for (auto& func : module.functions) { |
505 | 564 | if (!func->imported()) { |
506 | | - map[func.get()].canChangeState = whitelist.count(func->name) > 0; |
| 565 | + map[func.get()].canChangeState = whitelist.match(func->name); |
507 | 566 | } |
508 | 567 | } |
509 | 568 | } |
| 569 | + |
| 570 | + blacklist.checkPatternsMatches(); |
| 571 | + whitelist.checkPatternsMatches(); |
510 | 572 | } |
511 | 573 |
|
512 | 574 | bool needsInstrumentation(Function* func) { |
@@ -566,8 +628,6 @@ class ModuleAnalyzer { |
566 | 628 | } |
567 | 629 |
|
568 | 630 | GlobalHelper globals; |
569 | | - std::set<Name> blacklist; |
570 | | - std::set<Name> whitelist; |
571 | 631 | bool asserts; |
572 | 632 | }; |
573 | 633 |
|
@@ -1138,28 +1198,6 @@ struct Asyncify : public Pass { |
1138 | 1198 | blacklist = handleBracketingOperators(blacklist); |
1139 | 1199 | whitelist = handleBracketingOperators(whitelist); |
1140 | 1200 |
|
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 | | - |
1163 | 1201 | if (!blacklist.empty() && !whitelist.empty()) { |
1164 | 1202 | Fatal() << "It makes no sense to use both a blacklist and a whitelist " |
1165 | 1203 | "with asyncify."; |
|
0 commit comments