Skip to content

Commit 01357b9

Browse files
authored
PHP: Ensures that ExitStatus error thrown my Emscripten has a stack trace attached (#470)
## Description Overrides Emscripten's default ExitStatus object which gets thrown on failure. Unfortunately, the default object is not a subclass of Error and does not provide any stack trace. This is a deliberate behavior on Emscripten's end to prevent memory leaks after the program exits. See: emscripten-core/emscripten#9108 In case of WordPress Playground, the worker in which the PHP runs will typically exit after the PHP program finishes, so we don't have to worry about memory leaks. As for assigning to a previously undeclared ExitStatus variable here, the Emscripten module declares `ExitStatus` as `function ExitStatus` which means it gets hoisted to the top of the scope and can be reassigned here – before the actual declaration is reached. If that sounds weird, try this example: ```js ExitStatus = () => { console.log("reassigned"); } function ExitStatus() {} ExitStatus(); // logs "reassigned" ``` ## Testing instructions Confirm the CI tests passed Related: #416 cc @wojtekn
1 parent c78999b commit 01357b9

39 files changed

+912
-77
lines changed

packages/php-wasm/compile/Dockerfile

+6-5
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,8 @@ RUN ls /root/output/
973973
# fi
974974

975975
# Postprocess the build php.js module:
976-
COPY ./build-assets/append-before-return.js /root/append-before-return.js
976+
COPY ./build-assets/esm-prefix.js /root/esm-prefix.js
977+
COPY ./build-assets/esm-suffix.js /root/esm-suffix.js
977978
RUN \
978979
# Figure out the target file names and URLs
979980
# The .js and .wasm filenames should reflect the build configuration, e.g.:
@@ -1039,16 +1040,16 @@ RUN \
10391040
# Turn the php.js file into an ES module
10401041
# Manually turn the output into a esm module instead of relying on -s MODULARIZE=1.
10411042
# which pollutes the global namespace and does not play well with import() mechanics.
1042-
echo "export const dependenciesTotalSize = $FILE_SIZE; " >> /root/output/php-module.js && \
10431043
if [ "$EMSCRIPTEN_ENVIRONMENT" = "node" ]; then \
10441044
echo "const dependencyFilename = __dirname + '/$WASM_FILENAME'; " >> /root/output/php-module.js; \
10451045
else \
10461046
echo "import dependencyFilename from './$WASM_FILENAME'; " >> /root/output/php-module.js; \
10471047
fi; \
1048-
echo " export { dependencyFilename }; export function init(RuntimeName, PHPLoader) {" >> /root/output/php-module.js && \
1048+
echo " export { dependencyFilename }; " >> /root/output/php-module.js && \
1049+
echo "export const dependenciesTotalSize = $FILE_SIZE; " >> /root/output/php-module.js && \
1050+
cat /root/esm-prefix.js >> /root/output/php-module.js && \
10491051
cat /root/output/php.js >> /root/output/php-module.js && \
1050-
cat /root/append-before-return.js >> /root/output/php-module.js && \
1051-
echo " return PHPLoader; }" >> /root/output/php-module.js && \
1052+
cat /root/esm-suffix.js >> /root/output/php-module.js && \
10521053
\
10531054
# Remove the old php.js file
10541055
rm /root/output/php.js && \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export function init(RuntimeName, PHPLoader) {
2+
/**
3+
* Overrides Emscripten's default ExitStatus object which gets
4+
* thrown on failure. Unfortunately, the default object is not
5+
* a subclass of Error and does not provide any stack trace.
6+
*
7+
* This is a deliberate behavior on Emscripten's end to prevent
8+
* memory leaks after the program exits. See:
9+
*
10+
* https://github.com/emscripten-core/emscripten/pull/9108
11+
*
12+
* In case of WordPress Playground, the worker in which the PHP
13+
* runs will typically exit after the PHP program finishes, so
14+
* we don't have to worry about memory leaks.
15+
*
16+
* As for assigning to a previously undeclared ExitStatus variable here,
17+
* the Emscripten module declares `ExitStatus` as `function ExitStatus`
18+
* which means it gets hoisted to the top of the scope and can be
19+
* reassigned here – before the actual declaration is reached.
20+
*
21+
* If that sounds weird, try this example:
22+
*
23+
* ExitStatus = () => { console.log("reassigned"); }
24+
* function ExitStatus() {}
25+
* ExitStatus();
26+
* // logs "reassigned"
27+
*/
28+
ExitStatus = class PHPExitStatus extends Error {
29+
constructor(status) {
30+
super(status);
31+
this.name = "ExitStatus";
32+
this.message = "Program terminated with exit(" + status + ")";
33+
this.status = status;
34+
}
35+
}
36+
37+
// The rest of the code comes from the built php.js file and esm-suffix.js

packages/php-wasm/compile/build-assets/append-before-return.js renamed to packages/php-wasm/compile/build-assets/esm-suffix.js

+5
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ if (PHPLoader.debug && typeof Asyncify !== "undefined") {
1919
return originalHandleSleep(startAsync);
2020
}
2121
}
22+
23+
return PHPLoader;
24+
25+
// Close the opening bracket from esm-prefix.js:
26+
}

packages/php-wasm/node/public/php_5_6.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
1-
export const dependenciesTotalSize = 10176855;
21
const dependencyFilename = __dirname + '/php_5_6.wasm';
3-
export { dependencyFilename }; export function init(RuntimeName, PHPLoader) {
2+
export { dependencyFilename };
3+
export const dependenciesTotalSize = 10176855;
4+
export function init(RuntimeName, PHPLoader) {
5+
/**
6+
* Overrides Emscripten's default ExitStatus object which gets
7+
* thrown on failure. Unfortunately, the default object is not
8+
* a subclass of Error and does not provide any stack trace.
9+
*
10+
* This is a deliberate behavior on Emscripten's end to prevent
11+
* memory leaks after the program exits. See:
12+
*
13+
* https://github.com/emscripten-core/emscripten/pull/9108
14+
*
15+
* In case of WordPress Playground, the worker in which the PHP
16+
* runs will typically exit after the PHP program finishes, so
17+
* we don't have to worry about memory leaks.
18+
*
19+
* As for assigning to a previously undeclared ExitStatus variable here,
20+
* the Emscripten module declares `ExitStatus` as `function ExitStatus`
21+
* which means it gets hoisted to the top of the scope and can be
22+
* reassigned here – before the actual declaration is reached.
23+
*
24+
* If that sounds weird, try this example:
25+
*
26+
* ExitStatus = () => { console.log("reassigned"); }
27+
* function ExitStatus() {}
28+
* ExitStatus();
29+
* // logs "reassigned"
30+
*/
31+
ExitStatus = class PHPExitStatus extends Error {
32+
constructor(status) {
33+
super(status);
34+
this.name = "ExitStatus";
35+
this.message = "Program terminated with exit(" + status + ")";
36+
this.status = status;
37+
}
38+
}
39+
40+
// The rest of the code comes from the built php.js file and esm-suffix.js
441
var Module = typeof PHPLoader != "undefined" ? PHPLoader : {};
542

643
var moduleOverrides = Object.assign({}, Module);
@@ -7041,7 +7078,7 @@ DNS.address_map.addrs.localhost = '127.0.0.1';
70417078
* so that it can be inspected later.
70427079
*/
70437080
PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true;
7044-
if (PHPLoader.debug) {
7081+
if (PHPLoader.debug && typeof Asyncify !== "undefined") {
70457082
const originalHandleSleep = Asyncify.handleSleep;
70467083
Asyncify.handleSleep = function (startAsync) {
70477084
if (!ABORT) {
@@ -7050,4 +7087,8 @@ if (PHPLoader.debug) {
70507087
return originalHandleSleep(startAsync);
70517088
}
70527089
}
7053-
return PHPLoader; }
7090+
7091+
return PHPLoader;
7092+
7093+
// Close the opening bracket from esm-prefix.js:
7094+
}
0 Bytes
Binary file not shown.

packages/php-wasm/node/public/php_7_0.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
1-
export const dependenciesTotalSize = 10390258;
21
const dependencyFilename = __dirname + '/php_7_0.wasm';
3-
export { dependencyFilename }; export function init(RuntimeName, PHPLoader) {
2+
export { dependencyFilename };
3+
export const dependenciesTotalSize = 10390259;
4+
export function init(RuntimeName, PHPLoader) {
5+
/**
6+
* Overrides Emscripten's default ExitStatus object which gets
7+
* thrown on failure. Unfortunately, the default object is not
8+
* a subclass of Error and does not provide any stack trace.
9+
*
10+
* This is a deliberate behavior on Emscripten's end to prevent
11+
* memory leaks after the program exits. See:
12+
*
13+
* https://github.com/emscripten-core/emscripten/pull/9108
14+
*
15+
* In case of WordPress Playground, the worker in which the PHP
16+
* runs will typically exit after the PHP program finishes, so
17+
* we don't have to worry about memory leaks.
18+
*
19+
* As for assigning to a previously undeclared ExitStatus variable here,
20+
* the Emscripten module declares `ExitStatus` as `function ExitStatus`
21+
* which means it gets hoisted to the top of the scope and can be
22+
* reassigned here – before the actual declaration is reached.
23+
*
24+
* If that sounds weird, try this example:
25+
*
26+
* ExitStatus = () => { console.log("reassigned"); }
27+
* function ExitStatus() {}
28+
* ExitStatus();
29+
* // logs "reassigned"
30+
*/
31+
ExitStatus = class PHPExitStatus extends Error {
32+
constructor(status) {
33+
super(status);
34+
this.name = "ExitStatus";
35+
this.message = "Program terminated with exit(" + status + ")";
36+
this.status = status;
37+
}
38+
}
39+
40+
// The rest of the code comes from the built php.js file and esm-suffix.js
441
var Module = typeof PHPLoader != "undefined" ? PHPLoader : {};
542

643
var moduleOverrides = Object.assign({}, Module);
@@ -7022,7 +7059,7 @@ DNS.address_map.addrs.localhost = '127.0.0.1';
70227059
* so that it can be inspected later.
70237060
*/
70247061
PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true;
7025-
if (PHPLoader.debug) {
7062+
if (PHPLoader.debug && typeof Asyncify !== "undefined") {
70267063
const originalHandleSleep = Asyncify.handleSleep;
70277064
Asyncify.handleSleep = function (startAsync) {
70287065
if (!ABORT) {
@@ -7031,4 +7068,8 @@ if (PHPLoader.debug) {
70317068
return originalHandleSleep(startAsync);
70327069
}
70337070
}
7034-
return PHPLoader; }
7071+
7072+
return PHPLoader;
7073+
7074+
// Close the opening bracket from esm-prefix.js:
7075+
}
1 Byte
Binary file not shown.

packages/php-wasm/node/public/php_7_1.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
1-
export const dependenciesTotalSize = 10583885;
21
const dependencyFilename = __dirname + '/php_7_1.wasm';
3-
export { dependencyFilename }; export function init(RuntimeName, PHPLoader) {
2+
export { dependencyFilename };
3+
export const dependenciesTotalSize = 10583885;
4+
export function init(RuntimeName, PHPLoader) {
5+
/**
6+
* Overrides Emscripten's default ExitStatus object which gets
7+
* thrown on failure. Unfortunately, the default object is not
8+
* a subclass of Error and does not provide any stack trace.
9+
*
10+
* This is a deliberate behavior on Emscripten's end to prevent
11+
* memory leaks after the program exits. See:
12+
*
13+
* https://github.com/emscripten-core/emscripten/pull/9108
14+
*
15+
* In case of WordPress Playground, the worker in which the PHP
16+
* runs will typically exit after the PHP program finishes, so
17+
* we don't have to worry about memory leaks.
18+
*
19+
* As for assigning to a previously undeclared ExitStatus variable here,
20+
* the Emscripten module declares `ExitStatus` as `function ExitStatus`
21+
* which means it gets hoisted to the top of the scope and can be
22+
* reassigned here – before the actual declaration is reached.
23+
*
24+
* If that sounds weird, try this example:
25+
*
26+
* ExitStatus = () => { console.log("reassigned"); }
27+
* function ExitStatus() {}
28+
* ExitStatus();
29+
* // logs "reassigned"
30+
*/
31+
ExitStatus = class PHPExitStatus extends Error {
32+
constructor(status) {
33+
super(status);
34+
this.name = "ExitStatus";
35+
this.message = "Program terminated with exit(" + status + ")";
36+
this.status = status;
37+
}
38+
}
39+
40+
// The rest of the code comes from the built php.js file and esm-suffix.js
441
var Module = typeof PHPLoader != "undefined" ? PHPLoader : {};
542

643
var moduleOverrides = Object.assign({}, Module);
@@ -7007,7 +7044,7 @@ DNS.address_map.addrs.localhost = '127.0.0.1';
70077044
* so that it can be inspected later.
70087045
*/
70097046
PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true;
7010-
if (PHPLoader.debug) {
7047+
if (PHPLoader.debug && typeof Asyncify !== "undefined") {
70117048
const originalHandleSleep = Asyncify.handleSleep;
70127049
Asyncify.handleSleep = function (startAsync) {
70137050
if (!ABORT) {
@@ -7016,4 +7053,8 @@ if (PHPLoader.debug) {
70167053
return originalHandleSleep(startAsync);
70177054
}
70187055
}
7019-
return PHPLoader; }
7056+
7057+
return PHPLoader;
7058+
7059+
// Close the opening bracket from esm-prefix.js:
7060+
}
0 Bytes
Binary file not shown.

packages/php-wasm/node/public/php_7_2.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
1-
export const dependenciesTotalSize = 10993354;
21
const dependencyFilename = __dirname + '/php_7_2.wasm';
3-
export { dependencyFilename }; export function init(RuntimeName, PHPLoader) {
2+
export { dependencyFilename };
3+
export const dependenciesTotalSize = 10993354;
4+
export function init(RuntimeName, PHPLoader) {
5+
/**
6+
* Overrides Emscripten's default ExitStatus object which gets
7+
* thrown on failure. Unfortunately, the default object is not
8+
* a subclass of Error and does not provide any stack trace.
9+
*
10+
* This is a deliberate behavior on Emscripten's end to prevent
11+
* memory leaks after the program exits. See:
12+
*
13+
* https://github.com/emscripten-core/emscripten/pull/9108
14+
*
15+
* In case of WordPress Playground, the worker in which the PHP
16+
* runs will typically exit after the PHP program finishes, so
17+
* we don't have to worry about memory leaks.
18+
*
19+
* As for assigning to a previously undeclared ExitStatus variable here,
20+
* the Emscripten module declares `ExitStatus` as `function ExitStatus`
21+
* which means it gets hoisted to the top of the scope and can be
22+
* reassigned here – before the actual declaration is reached.
23+
*
24+
* If that sounds weird, try this example:
25+
*
26+
* ExitStatus = () => { console.log("reassigned"); }
27+
* function ExitStatus() {}
28+
* ExitStatus();
29+
* // logs "reassigned"
30+
*/
31+
ExitStatus = class PHPExitStatus extends Error {
32+
constructor(status) {
33+
super(status);
34+
this.name = "ExitStatus";
35+
this.message = "Program terminated with exit(" + status + ")";
36+
this.status = status;
37+
}
38+
}
39+
40+
// The rest of the code comes from the built php.js file and esm-suffix.js
441
var Module = typeof PHPLoader != "undefined" ? PHPLoader : {};
542

643
var moduleOverrides = Object.assign({}, Module);
@@ -7023,7 +7060,7 @@ DNS.address_map.addrs.localhost = '127.0.0.1';
70237060
* so that it can be inspected later.
70247061
*/
70257062
PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true;
7026-
if (PHPLoader.debug) {
7063+
if (PHPLoader.debug && typeof Asyncify !== "undefined") {
70277064
const originalHandleSleep = Asyncify.handleSleep;
70287065
Asyncify.handleSleep = function (startAsync) {
70297066
if (!ABORT) {
@@ -7032,4 +7069,8 @@ if (PHPLoader.debug) {
70327069
return originalHandleSleep(startAsync);
70337070
}
70347071
}
7035-
return PHPLoader; }
7072+
7073+
return PHPLoader;
7074+
7075+
// Close the opening bracket from esm-prefix.js:
7076+
}
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)