Skip to content

fix: don't render nil JsVar Ptr through value-receiver callbacks (#196)#208

Merged
linkdata merged 1 commit into
mainfrom
fix/jsvar-nil-ptr-render-lock-196
Jul 21, 2026
Merged

fix: don't render nil JsVar Ptr through value-receiver callbacks (#196)#208
linkdata merged 1 commit into
mainfrom
fix/jsvar-nil-ptr-render-lock-196

Conversation

@linkdata

Copy link
Copy Markdown
Owner

Summary

Fixes #196. Rendering a JsVar bound to a nil pointer could panic and then permanently retain the shared application lock.

JawsRender passed the typed-nil Ptr to Element.ApplyGetter. Because a typed-nil pointer is a non-nil interface, ApplyGetter recognized tag.TagGetter and called its value-receiver JawsGetTag through the nil pointer, causing a runtime panic. The write lock was released with a bare Unlock (not a deferred one), so the panic unwound past it and left the locker held forever — stalling every goroutine sharing that application state.

Fix

  • Extract the render-time critical section into renderSnapshot, which holds the write lock under a defer Unlock(). A panic from a supported bound-value callback or from marshaling can no longer leak the lock.
  • When Ptr is nil, skip the bound-value getter/init callbacks and omit the initial data — the behavior NewJsVar already documents ("reads return the zero value, rendering omits the initial data").

Behavior for a non-nil Ptr is unchanged (same getter value passed to ApplyGetter, same lock scope covering ApplyGetter + marshal so the serialized value stays consistent with the dirty tag).

Tests

Two regression tests, both verified to fail on the pre-fix code and pass after:

  • TestJsVar_RenderNilPointerWithTagGetterDoesNotLeakLock — the exact scenario from Rendering a nil JsVar pointer can panic and permanently retain its lock #196 (nil pointer to a type with a value-receiver JawsGetTag); asserts no panic, lock released, and no data-jawsdata.
  • TestJsVar_RenderMarshalPanicDoesNotLeakLock — a bound value whose MarshalJSON panics; asserts the panic propagates but the lock is still released.

go test -race ./... passes across the module; go vet, gofmt, and staticcheck are clean on the touched files.

Rendering a JsVar bound to a nil pointer passed the typed-nil Ptr to
Element.ApplyGetter, which saw a non-nil interface and invoked a
value-receiver tag.TagGetter (or InitHandler) through the nil pointer,
panicking. Because the write lock was released with a bare Unlock rather
than a deferred one, the panic unwound past it and left the shared locker
held forever, stalling every goroutine sharing that state.

Extract the render-time critical section into renderSnapshot, which holds
the write lock under a deferred unlock and skips the bound-value getter
and init callbacks when Ptr is nil. A nil Ptr now renders with no initial
data and no bound-value tag, as NewJsVar documents, and a panic from a
supported callback or from marshaling can no longer leak the lock.

Fixes #196
@linkdata
linkdata marked this pull request as ready for review July 21, 2026 20:26
@linkdata
linkdata merged commit 11a6eea into main Jul 21, 2026
7 checks passed
@linkdata
linkdata deleted the fix/jsvar-nil-ptr-render-lock-196 branch July 21, 2026 20:27
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.

Rendering a nil JsVar pointer can panic and permanently retain its lock

1 participant