Skip to content

vm fixes#416

Merged
jow- merged 10 commits into
jow-:masterfrom
nbd168:vm-fixes
Jul 8, 2026
Merged

vm fixes#416
jow- merged 10 commits into
jow-:masterfrom
nbd168:vm-fixes

Conversation

@nbd168

@nbd168 nbd168 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

A number of fixes for vm issues discovered in a LLM code review session.

nbd168 added 10 commits July 8, 2026 12:58
uc_vm_insn_store_val() and uc_vm_insn_update_val() pop an owned value and
hand it to ucv_key_set(). On success ucv_key_set() stores the value and
returns a new reference to it, which the caller pushes onto the stack; on
failure it stores nothing and returns NULL. The failure paths neither
stored nor released the value, so it leaked, for example when assigning a
property on a non-container (`let x = 1; x.y = {};`), on an immutable
target, or with an invalid array key (`let a = []; a["x"] = {};`).

These three cases originate in different places (the default switch
branch, a failing assert_mutable_value() and a NULL return from
ucv_key_set()), so a single per-branch guard cannot cover them all.
Instead release the value from one common cleanup point, and on the sole
success path clear the local so that the reference now owned by the stack
is not released a second time.

The third caller, uc_vm_insn_sobj(), passes borrowed stack values that
are freed by the subsequent stack pop and is deliberately left unchanged.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
The single-name branch of uc_vm_insn_import() tested
`from <= prog->exports.count`, allowing an index equal to the export count
to read one element past the end of exports.entries[]. With an empty export
table this dereferences entries[0] on a NULL array. The parallel wildcard
import loop already bounds the index with `<`; use `<` here as well.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
The signed integer code paths in uc_vm_value_arith() evaluated
INT64_MIN / -1 and INT64_MIN % -1 directly, which overflows and traps
with SIGFPE on common architectures, crashing the interpreter.

Return the mathematically correct unsigned result 2^63 for the division,
consistent with the existing behaviour of INT64_MIN * -1 and
0 - INT64_MIN, and 0 for the modulo.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
uc_vm_insn_store_var() and uc_vm_insn_update_var() raised the reference
error for assignments to undeclared variables in strict mode but then
proceeded to perform the assignment anyway. When the exception was
caught, the variable had come into existence in the global scope.

Abort the instruction after raising the exception.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
uc_vm_stack_push() takes ownership of the passed value.
uc_vm_resolve_upval() replaced a pushed upvalue with a new reference to
its resolved value but never released the upvalue reference itself,
leaking one reference per push. Triggered e.g. by iterating a wildcard
module namespace object with for-in, where the object iterator pushes
the contained upvalue references.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
uc_require_library() returns an owned reference to the module scope,
the module registry keeps its own reference. uc_vm_insn_dynload() never
released the returned reference, keeping the module scope alive even
after removal from the module registry.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
uc_vm_get_error_context() accessed callframes.entries[-1] when an
exception was raised while no callframe existed, e.g. when an embedder
invokes uc_vm_call() on a non-callable value outside of any script
execution, crashing with SIGSEGV.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
The signed integer code path in uc_vm_value_arith() negated the result
whenever the base was negative, regardless of the exponent, yielding
e.g. (-2) ** 2 = -4, (-2) ** 0 = -1 and (-2) ** -2 = -0.25. The double
code path already produced the mathematically correct results via
pow(), so both paths disagreed for equal values.

Only negate the result when the exponent is odd. Adjust the
exponentiation testcase which had codified the incorrect behaviour;
the equivalent double expression (-2.0) ** -2 already yielded 0.25
before this change.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
uc_vm_insn_mobj() switches on ucv_type() but matched the array case
with the json-c enumerator json_type_array, which only works because
its value happens to coincide with UC_ARRAY. Use the correct enum.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
Resource type prototypes live outside the GC value list
(uc_type_declare() uses ucv_object_new(NULL)), so they are reclaimed by
reference count alone. GC-managed values can hold a reference to them,
e.g. a prototype returned by proto(), and those references are only
dropped during ucv_freeall(). Releasing the prototypes before
ucv_freeall() could therefore leave one at a nonzero refcount, and its
final ucv_put() would then hit ucv_freeall()'s retain phase, which only
marks the object UC_NULL and defers the free to the value-list sweep.
As these prototypes are not on that list, the object leaks.

Move the release after ucv_freeall() so the last reference is dropped
outside the retain phase and the object is freed immediately.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
@jow- jow- merged commit 533f6de into jow-:master Jul 8, 2026
2 checks passed
@jow-

jow- commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Merged - thanks!

@nbd168 nbd168 deleted the vm-fixes branch July 8, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants