Skip to content

Commit fd9d3f0

Browse files
authored
Clean up functions when disposed (#227)
* Actually clean up functions after disposed * update vscode settings * new HostRef system for handling functions & generic host references * tests * HostRef * document why returning raw JS_EXCEPTION is valid here * fix ref * bugfixes * dont leak HostRef instance * fix double-free * doc * prettier * FIX MEasdfasdf * update docs
1 parent a858ac0 commit fd9d3f0

92 files changed

Lines changed: 3879 additions & 1207 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ emsdk-cache
1212
.output
1313
packages/internal-tsconfig/*.d.*ts
1414
packages/internal-tsconfig/*.*js
15-
vendor
15+
vendor
16+
CLAUDE.md

.vscode/c_cpp_properties.json

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,53 @@
11
{
2-
"configurations": [
3-
{
4-
"name": "Mac",
5-
"includePath": [
6-
"${workspaceFolder}/**",
7-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/system/include/**",
8-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/cache/sysroot/include/**",
9-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/llvm/lib/clang/15.0.0/include/**",
10-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/cache/sysroot/include/SDL",
11-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/cache/sysroot/include/compat",
12-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/llvm/lib/clang/15.0.0/include",
13-
"/opt/homebrew/Cellar/emscripten/3.1.7/libexec/cache/sysroot/include"
14-
],
15-
"defines": ["QTS_ASYNCIFY=1", "QTS_DEBUG_MODE=1", "__EMSCRIPTEN__=1"],
16-
"macFrameworkPath": [
17-
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
18-
],
19-
"compilerPath": "/usr/bin/clang",
20-
"cStandard": "c17",
21-
"cppStandard": "c++98",
22-
"intelliSenseMode": "macos-clang-arm64",
23-
"compilerArgs": [],
24-
"mergeConfigurations": false,
25-
"browse": {
26-
"path": ["${workspaceFolder}/**"],
27-
"limitSymbolsToIncludedHeaders": true
28-
},
29-
"configurationProvider": "ms-vscode.makefile-tools"
30-
},
31-
{
32-
"name": "Emscripten",
33-
"includePath": ["${workspaceFolder}/**"],
34-
"defines": [],
35-
"macFrameworkPath": [
36-
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
37-
],
38-
"compilerPath": "/opt/homebrew/bin/emcc",
39-
"cStandard": "c17",
40-
"cppStandard": "c++98",
41-
"intelliSenseMode": "macos-clang-arm64"
42-
}
43-
],
44-
"version": 4
45-
}
2+
"configurations": [
3+
{
4+
"name": "Mac",
5+
"includePath": [
6+
"${workspaceFolder}/**",
7+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/system/include/**",
8+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/cache/sysroot/include/**",
9+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/llvm/lib/clang/23/include/**",
10+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/cache/sysroot/include/SDL",
11+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/cache/sysroot/include/compat",
12+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/llvm/lib/clang/23/include",
13+
"/opt/homebrew/Cellar/emscripten/5.0.1/libexec/cache/sysroot/include"
14+
],
15+
"defines": [
16+
"QTS_ASYNCIFY=1",
17+
"QTS_DEBUG_MODE=1",
18+
"__EMSCRIPTEN__=1"
19+
],
20+
"macFrameworkPath": [
21+
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
22+
],
23+
"compilerPath": "/usr/bin/clang",
24+
"cStandard": "c17",
25+
"cppStandard": "c++98",
26+
"intelliSenseMode": "macos-clang-arm64",
27+
"compilerArgs": [],
28+
"mergeConfigurations": false,
29+
"browse": {
30+
"path": [
31+
"${workspaceFolder}/**"
32+
],
33+
"limitSymbolsToIncludedHeaders": true
34+
},
35+
"configurationProvider": "ms-vscode.makefile-tools"
36+
},
37+
{
38+
"name": "Emscripten",
39+
"includePath": [
40+
"${workspaceFolder}/**"
41+
],
42+
"defines": [],
43+
"macFrameworkPath": [
44+
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
45+
],
46+
"compilerPath": "/opt/homebrew/bin/emcc",
47+
"cStandard": "c17",
48+
"cppStandard": "c++98",
49+
"intelliSenseMode": "macos-clang-arm64"
50+
}
51+
],
52+
"version": 4
53+
}

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## v0.32.0
4+
5+
- [#227](https://github.com/justjake/quickjs-emscripten/pull/227)
6+
7+
- Re-works function binding in `newFunction` to use a different proxying strategy based on a new abstraction, `HostRef`. HostRef allows tracking references of host values from guest handles. Once all references to the HostRef object are disposed (either in the host, or GC'd in the guest), the host value will be dereferenced and become garbage collectable.
8+
- Functions passed to `newFunction` are interned in the runtime's `HostRefMap` until dereferenced.
9+
- `HostRef` is also exposed directly for use from `QuickJSContext`, see `newHostRef`, `toHostRef`, `unwrapHostRef` in the docs.
10+
- Added `QuickJSContext.newConstructorFunction`, such functions will have their `this` set to the newly constructed object, although they may also return a new object.
11+
12+
- [#244](https://github.com/justjake/quickjs-emscripten/pull/244) Upgrade [quickjs-ng](https://github.com/quickjs-ng/quickjs) to [v0.12.1](https://github.com/quickjs-ng/quickjs/releases/tag/v0.12.1).
13+
- [#243](https://github.com/justjake/quickjs-emscripten/pull/243) (thanks to @josh-) Upgrade [bellard/quickjs](https://github.com/bellard/quickjs) to [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53). Review the [QuickJS changelog](https://github.com/bellard/quickjs/blob/f1139494d18a2053630c5ed3384a42bb70db3c53/Changelog) for details.
14+
- Some intrinsics like `BigInt` are now always enabled.
15+
- BigNum extension removed.
16+
- [#242](https://github.com/justjake/quickjs-emscripten/pull/242) Upgrade to Emscripten 5.0.1.
17+
- [#221](https://github.com/justjake/quickjs-emscripten/pull/221) (thanks to @melbourne2991) Support generator return values in QuickJSIterator.
18+
319
## v0.31.0
420

521
- [#137](https://github.com/justjake/quickjs-emscripten/pull/137)

CLAUDE.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,87 @@ Other test filters:
5757
- `scripts/generate.ts` - Generates FFI bindings and symbols
5858
- `templates/Variant.mk` - Makefile template for variants
5959
- `c/interface.c` - C interface to QuickJS exposed to JavaScript
60+
61+
## QuickJS C API Tips
62+
63+
### Value Ownership
64+
65+
QuickJS has strict ownership semantics. Functions either "consume" (take ownership of) or "borrow" values:
66+
67+
**Functions that CONSUME values (caller must NOT free afterward):**
68+
69+
- `JS_DefinePropertyValue` - consumes `val`
70+
- `JS_DefinePropertyValueStr` - consumes `val`
71+
- `JS_DefinePropertyValueUint32` - consumes `val`
72+
- `JS_SetPropertyValue` - consumes `val`
73+
- `JS_SetPropertyStr` - consumes `val`
74+
- `JS_Throw` - consumes the error value
75+
76+
**Functions that DUP values internally (caller SHOULD free afterward):**
77+
78+
- `JS_NewCFunctionData` - calls `JS_DupValue` on data values, so free your reference after
79+
- `JS_SetProperty` - dups the value
80+
81+
**Common double-free bug pattern:**
82+
83+
```c
84+
// WRONG - double free!
85+
JSValue val = JS_NewString(ctx, "hello");
86+
JS_DefinePropertyValueStr(ctx, obj, "name", val, JS_PROP_CONFIGURABLE);
87+
JS_FreeValue(ctx, val); // BUG: val was already consumed!
88+
89+
// CORRECT
90+
JSValue val = JS_NewString(ctx, "hello");
91+
JS_DefinePropertyValueStr(ctx, obj, "name", val, JS_PROP_CONFIGURABLE);
92+
// No JS_FreeValue needed - value is consumed
93+
```
94+
95+
### quickjs vs quickjs-ng Differences
96+
97+
Some functions have different signatures between bellard/quickjs and quickjs-ng:
98+
99+
```c
100+
// bellard/quickjs - class ID is global
101+
JS_NewClassID(&class_id);
102+
103+
// quickjs-ng - class ID is per-runtime
104+
JS_NewClassID(rt, &class_id);
105+
```
106+
107+
Use `#ifdef QTS_USE_QUICKJS_NG` for compatibility:
108+
109+
```c
110+
#ifdef QTS_USE_QUICKJS_NG
111+
JS_NewClassID(rt, &class_id);
112+
#else
113+
JS_NewClassID(&class_id);
114+
#endif
115+
```
116+
117+
### Class Registration
118+
119+
- `JS_NewClassID` allocates a new class ID (only call once globally or per-runtime for ng)
120+
- `JS_NewClass` registers the class definition with a runtime
121+
- `JS_IsRegisteredClass` checks if a class is already registered with a runtime
122+
- Class prototypes default to `JS_NULL` for new classes - set with `JS_SetClassProto` if needed
123+
124+
### Disposal Order
125+
126+
When disposing resources, order matters for finalizers:
127+
128+
```typescript
129+
// CORRECT: Free runtime first so GC finalizers can call back to JS
130+
const rt = new Lifetime(ffi.QTS_NewRuntime(), undefined, (rt_ptr) => {
131+
ffi.QTS_FreeRuntime(rt_ptr); // 1. Free runtime - runs GC finalizers
132+
callbacks.deleteRuntime(rt_ptr); // 2. Then delete callbacks
133+
});
134+
```
135+
136+
### GC and Prevent Corruption Assertions
137+
138+
If you see assertions like:
139+
- `Assertion failed: i != 0, at: quickjs.c, JS_FreeAtomStruct` - atom hash corruption (often double-free)
140+
- `Assertion failed: list_empty(&rt->gc_obj_list)` - objects leaked
141+
- `Assertion failed: p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT` - memory corruption
142+
143+
These usually indicate memory management bugs: double-frees, use-after-free, or missing frees.

0 commit comments

Comments
 (0)