2026-05-25 15:07:10 +02:00
|
|
|
// === Nested-Asyncify handleSleep currData save/restore (Emscripten #9153) ===
|
|
|
|
|
//
|
|
|
|
|
// Asyncify.currData is a single-slot global. When a fiber swap runs inside an
|
|
|
|
|
// EM_ASYNC_JS Promise await (e.g., wxDialog::ShowModal via startModal), the
|
|
|
|
|
// fiber swap overwrites currData with the fiber's asyncify_data, losing the
|
|
|
|
|
// sleep's own buffer. On Promise resolution, handleSleep's doRewind then uses
|
|
|
|
|
// the wrong buffer and crashes with "index out of bounds" or "unreachable".
|
|
|
|
|
//
|
|
|
|
|
// Workaround: intercept Asyncify.allocateData to record which pointer belongs to
|
|
|
|
|
// the active handleSleep; restore it to Asyncify.currData inside the wakeUp
|
|
|
|
|
// callback before handleSleep proceeds to _asyncify_start_rewind + doRewind.
|
|
|
|
|
if (typeof Asyncify !== "undefined") {
|
|
|
|
|
if (typeof Asyncify.handleSleep === "function"
|
|
|
|
|
&& typeof Asyncify.allocateData === "function"
|
|
|
|
|
&& !Asyncify.__nestedHandleSleepInstalled) {
|
|
|
|
|
// Stack of handleSleep contexts awaiting their allocateData association.
|
|
|
|
|
Asyncify.__pendingSleepContexts = [];
|
|
|
|
|
|
|
|
|
|
var __originalAllocateData = Asyncify.allocateData.bind(Asyncify);
|
|
|
|
|
Asyncify.allocateData = function() {
|
|
|
|
|
var ptr = __originalAllocateData();
|
|
|
|
|
// Associate with the innermost pending handleSleep not yet linked.
|
|
|
|
|
for (var i = Asyncify.__pendingSleepContexts.length - 1; i >= 0; --i) {
|
|
|
|
|
var ctx = Asyncify.__pendingSleepContexts[i];
|
|
|
|
|
if (!ctx.capturedData) {
|
|
|
|
|
ctx.capturedData = ptr;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ptr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var __originalHandleSleep = Asyncify.handleSleep.bind(Asyncify);
|
|
|
|
|
Asyncify.handleSleep = function(startAsync) {
|
|
|
|
|
var sleepCtx = { capturedData: null, cleanedUp: false };
|
|
|
|
|
Asyncify.__pendingSleepContexts.push(sleepCtx);
|
|
|
|
|
|
|
|
|
|
var cleanup = function() {
|
|
|
|
|
if (sleepCtx.cleanedUp) return;
|
|
|
|
|
sleepCtx.cleanedUp = true;
|
|
|
|
|
var idx = Asyncify.__pendingSleepContexts.indexOf(sleepCtx);
|
|
|
|
|
if (idx !== -1) Asyncify.__pendingSleepContexts.splice(idx, 1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return __originalHandleSleep(function(wakeUp) {
|
|
|
|
|
return startAsync(function(result) {
|
|
|
|
|
// wakeUp runs from pure JS on Promise resolution. Fiber swaps during
|
|
|
|
|
// the await may have overwritten Asyncify.currData. Restore OUR buffer
|
|
|
|
|
// so handleSleep's _asyncify_start_rewind and doRewind use it.
|
|
|
|
|
if (sleepCtx.capturedData) {
|
|
|
|
|
Asyncify.currData = sleepCtx.capturedData;
|
|
|
|
|
}
|
|
|
|
|
cleanup();
|
2026-06-12 16:59:07 +02:00
|
|
|
try {
|
|
|
|
|
return wakeUp(result);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// emscripten_set_main_loop(...,1) parks main() by throwing the
|
|
|
|
|
// "unwind" sentinel. When main's LAST pre-park suspension was a
|
|
|
|
|
// sleep, main is resumed from THIS wakeUp, so the sentinel
|
|
|
|
|
// propagates here instead of into callMain's catch — surfacing as
|
|
|
|
|
// an uncaught "unwind" promise rejection. Swallow it exactly like
|
|
|
|
|
// callMain/handleException do on the direct path.
|
|
|
|
|
if (e === "unwind") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2026-05-25 15:07:10 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
cleanup();
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Asyncify.__nestedHandleSleepInstalled = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// === End nested-Asyncify handleSleep fix ===
|