-
Notifications
You must be signed in to change notification settings - Fork 268
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PHP] Log a developer-friendly error message whenever an "unreachable…
…" error is triggered
- Loading branch information
Showing
2 changed files
with
98 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
packages/php-wasm/universal/src/lib/wasm-error-reporting.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
const UNREACHABLE_ERROR = ` | ||
"unreachable" WASM instruction executed. The typical reason is not listing all | ||
the PHP functions that can yield to JavaScript event loop in the ASYNCIFY_ONLY | ||
during the PHP compilation process. How to proceed? Find the "ASYNCIFY" section in | ||
Dockerfile in the WordPress Playground repository. | ||
Below is a list of all the PHP functions found in the stack trace. | ||
If they're all listed in the Dockerfile, you'll need to trigger this error | ||
again with long stack traces enabled. In node.js, you can do it using | ||
the --stack-trace-limit=100 CLI option: \n\n`; | ||
|
||
type Runtime = { | ||
asm: Record<string, unknown>; | ||
}; | ||
|
||
/** | ||
* Wraps WASM function calls with try/catch that | ||
* provides better error reporting. | ||
* | ||
* @param runtime | ||
*/ | ||
export function improveWASMErrorReporting(runtime: Runtime) { | ||
let logged = false; | ||
for (const key in runtime.asm) { | ||
if (typeof runtime.asm[key] == 'function') { | ||
const original = runtime.asm[key] as any; | ||
runtime.asm[key] = function (...args: any[]) { | ||
try { | ||
return original(...args); | ||
} catch (e) { | ||
if (logged || !(e instanceof Object)) { | ||
throw e; | ||
} | ||
logged = true; | ||
let betterMessage = UNREACHABLE_ERROR; | ||
if ('message' in e && e.message === 'unreachable') { | ||
for (const fn of extractPHPFunctionsFromTrace(e)) { | ||
betterMessage += ` * ${fn}\n`; | ||
} | ||
} | ||
errorBox(betterMessage); | ||
throw e; | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
// ANSI escape codes for CLI colors and formats | ||
const redBg = '\x1b[41m'; | ||
const bold = '\x1b[1m'; | ||
const reset = '\x1b[0m'; | ||
const eol = '\x1B[K'; | ||
|
||
function errorBox(message: string) { | ||
console.log(`${redBg}\n${eol}\n${bold} WASM ERROR${reset}${redBg}`); | ||
for (const line of message.split('\n')) { | ||
console.log(`${eol} ${line} `); | ||
} | ||
console.log(`${reset}`); | ||
} | ||
|
||
function extractPHPFunctionsFromTrace(e: any) { | ||
if (!e || !('stack' in e)) { | ||
return []; | ||
} | ||
try { | ||
return (e.stack as string) | ||
.split('\n') | ||
.slice(1) | ||
.map((line) => { | ||
const [fn, source] = line | ||
.trim() | ||
.substring('at '.length) | ||
.split(' '); | ||
const filename = source.split(':')[0].split('/').pop() || ''; | ||
return { | ||
fn, | ||
isJs: | ||
filename.endsWith('.js') || | ||
filename.endsWith('.cjs') || | ||
filename.endsWith('.mjs'), | ||
}; | ||
}) | ||
.filter( | ||
({ fn, isJs }) => | ||
!isJs && | ||
!fn.startsWith('dynCall_') && | ||
!fn.startsWith('dynCall_ii') | ||
) | ||
.map(({ fn }) => fn); | ||
} catch (err) { | ||
return []; | ||
} | ||
} |