emscripten-core / emscripten-core/emscripten

Invalid index into function table when running a function from module the second time

Open
#12,293 13 comments 0 reactions 0 assignees View on GitHub
wontfix
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.