emscripten-core / emscripten-core/emscripten
Invalid index into function table when running a function from module the second time
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
This crash happens when doing the same function for the second time without module termination (more context here https://github.com/emscripten-core/emscripten/issues/12219). The program is a statically linked xetex (with a lot of dependencies such as ICU, fontconfig etc)
```
Caught RuntimeError: invalid index into function table
at ucnv_close_65 (http://localhost:8080/dist/busytex.wasm:wasm-function[3557]:0x1db78f)
at XeTeXFontMgr_FC::terminate() (http://localhost:8080/dist/busytex.wasm:wasm-function[890]:0x957e0)
at XeTeXFontMgr::Terminate() (http://localhost:8080/dist/busytex.wasm:wasm-function[670]:0x8569a)
at terminatefontmanager (http://localhost:8080/dist/busytex.wasm:wasm-function[696]:0x88028)
at closefilesandterminate (http://localhost:8080/dist/busytex.wasm:wasm-function[164]:0x1e27a)
at mainbody (http://localhost:8080/dist/busytex.wasm:wasm-function[144]:0x1c4e4)
```
`ucnv_close` comes from [icu](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/ucnv_8h.html) It source is [here](https://github.com/unicode-org/icu/blob/1084c1430aad6006d269d32941689defeeee2991/icu4c/source/common/ucnv.cpp#L326). Basically `terminate` just calls `ucnv_close` a few times.
Any advice on what the cause might be? I read in issues that it may be related to fPIC, but the first run succeeds without issues, so I'm not sure if it's that.
Also, is there a way to clean up the module state before calling it for the second time, at least to clean up all dynamic allocations? For now it complains that I need to enable ALLOW_MEMORY_GROWTH
Here is wasm and cpp source for `ucnv_close` (from Chrome Dev Console). Are `call_indirect`s to blame? These calls seem just virtual function calls (`converter->fromCharErrorBehaviour` and `converter->sharedData->impl->close`)
```wasm
(func $ucnv_close_65 (;3557;) (param $var0 i32)
(local $var1 i32) (local $var2 i32) (local $var3 i32) (local $var4 i32)
global.get $global0
i32.const 32
i32.sub
local.tee $var1
local.tee $var3
global.get $global2
i32.lt_u
if
call $__handle_stack_overflow
end
local.get $var3
global.set $global0
local.get $var1
i32.const 0
i32.store offset=28
block $label0
local.get $var0
i32.eqz
br_if $label0
local.get $var0
i32.load offset=4
local.tee $var2
i32.const 843
i32.ne
if
local.get $var1
i32.const 480960
i32.load
i32.store offset=24
local.get $var1
i32.const 480952
i64.load align=4
i64.store offset=16
local.get $var1
i32.const 480944
i64.load align=4
i64.store offset=8
local.get $var1
i32.const 480936
i64.load align=4
i64.store
local.get $var1
i32.const 0
i32.store offset=28
local.get $var1
local.get $var0
i32.store offset=4
local.get $var0
i32.load offset=16
local.get $var1
i32.const 0
i32.const 0
i32.const 4
local.get $var1
i32.const 28
i32.add
local.get $var2
call_indirect (param i32 i32 i32 i32 i32 i32)
end
local.get $var0
i32.load
local.tee $var2
i32.const 844
i32.ne
if
local.get $var1
i32.const 480988
i32.load
i32.store offset=24
local.get $var1
i32.const 480980
i64.load align=4
i64.store offset=16
local.get $var1
i32.const 480972
i64.load align=4
i64.store offset=8
local.get $var1
i32.const 480964
i64.load align=4
i64.store
local.get $var1
i32.const 0
i32.store offset=28
local.get $var1
local.get $var0
i32.store offset=4
local.get $var0
i32.load offset=12
local.get $var1
i32.const 0
i32.const 0
i32.const 0
i32.const 4
local.get $var1
i32.const 28
i32.add
local.get $var2
call_indirect (param i32 i32 i32 i32 i32 i32 i32)
end
local.get $var0
i32.load offset=24
i32.load offset=20
i32.load offset=16
local.tee $var2
if
local.get $var0
local.get $var2
call_indirect (param i32)
end
local.get $var0
i32.load offset=20
local.tee $var2
local.get $var0
i32.const 108
i32.add
i32.ne
if
local.get $var2
call $uprv_free_65
end
local.get $var0
i32.load offset=24
local.tee $var2
i32.load8_u offset=17
if
local.get $var2
call $ucnv_unloadSharedDataIfReady_65
end
local.get $var0
i32.load8_u offset=33
br_if $label0
local.get $var0
call $uprv_free_65
end $label0
local.get $var1
i32.const 32
i32.add
local.tee $var4
global.get $global2
i32.lt_u
if
call $__handle_stack_overflow
end
local.get $var4
global.set $global0
)
```
```cpp
/*Decreases the reference counter in the shared immutable section of the object
*and frees the mutable part*/
U_CAPI void U_EXPORT2
ucnv_close (UConverter * converter)
{
UErrorCode errorCode = U_ZERO_ERROR;
UTRACE_ENTRY_OC(UTRACE_UCNV_CLOSE);
if (converter == NULL)
{
UTRACE_EXIT();
return;
}
UTRACE_DATA3(UTRACE_OPEN_CLOSE, "close converter %s at %p, isCopyLocal=%b",
ucnv_getName(converter, &errorCode), converter, converter->isCopyLocal);
/* In order to speed up the close, only call the callbacks when they have been changed.
This performance check will only work when the callbacks are set within a shared library
or from user code that statically links this code. */
/* first, notify the callback functions that the converter is closed */
if (converter->fromCharErrorBehaviour != UCNV_TO_U_DEFAULT_CALLBACK) {
UConverterToUnicodeArgs toUArgs = {
sizeof(UConverterToUnicodeArgs),
TRUE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
toUArgs.converter = converter;
errorCode = U_ZERO_ERROR;
converter->fromCharErrorBehaviour(converter->toUContext, &toUArgs, NULL, 0, UCNV_CLOSE, &errorCode);
}
if (converter->fromUCharErrorBehaviour != UCNV_FROM_U_DEFAULT_CALLBACK) {
UConverterFromUnicodeArgs fromUArgs = {
sizeof(UConverterFromUnicodeArgs),
TRUE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
fromUArgs.converter = converter;
errorCode = U_ZERO_ERROR;
converter->fromUCharErrorBehaviour(converter->fromUContext, &fromUArgs, NULL, 0, 0, UCNV_CLOSE, &errorCode);
}
if (converter->sharedData->impl->close != NULL) {
converter->sharedData->impl->close(converter);
}
if (converter->subChars != (uint8_t *)converter->subUChars) {
uprv_free(converter->subChars);
}
if (converter->sharedData->isReferenceCounted) {
ucnv_unloadSharedDataIfReady(converter->sharedData);
}
if(!converter->isCopyLocal){
uprv_free(converter);
}
UTRACE_EXIT();
}
```
Contributor guide
Assessment
This issue has not been assessed yet.