Skip to content

heap corruption leading to SIGABRT #303

Description

@ihar-va-MA

Heap corruption leading to SIGABRT during spAnimationState listener dispatch

Description

We maintain a fork of defold/extension-spine that adds a custom "animation instancing" optimization: multiple SpineModelComponents that play the same animation (same scene, animation id, playback and blend mode) are batched onto one shared master spSkeleton/spAnimationState, which is then evaluated once per frame and its pose copied out to every member component. This is not present in upstream extension-spine — we're filing this because the root cause is a general reentrancy hazard in how defold-spine wires Spine's AnimationStateListener to synchronous Lua callbacks, and we think it's worth your awareness even though our specific trigger (a shared master state) is custom.

We see a production SIGABRT with this signature:

abort
free                      ← heap corruption detected here
spSlot_dispose            (Slot.c:44)
spSkeleton_dispose        (Skeleton.c:155)
DestroyAnimationGroup     (comp_spine_model.cpp:297)   [our fork's grouping/instancing addition]
EraseGroupIfEmpty         (comp_spine_model.cpp:451)   [our fork's grouping/instancing addition]
dmSpine::CompSpineModelUpdate (comp_spine_model.cpp:1708)
dmGameObject::UpdateComponentFunction (gameobject.cpp:2749)
dmGameSystem::CompCollectionProxyUpdate (comp_collection_proxy.cpp:448)
dmGameObject::Update (gameobject.cpp:2875 / 2893)
  ... (repeats for nested collection proxies) ...
dmGameObject::StepFrame (gameobject.cpp:2151)
dmEngine::Step (engine.cpp:2351)
dmEngineUpdate (engine.cpp:2728)
dmEngine::RunLoop (engine_loop.cpp:86)
EngineMain (engine_main.cpp:57)
EngineMainThread (engine_main.cpp:74)
dmThread::ThreadStartProxy (thread_posix.cpp:76)

The abort is apparently a symptom, not the cause — it's heap corruption surfacing later inside an unrelated free(), not a crash at the call site itself.

Hypothesis: a missing upstream fix (#294 / PR #297) may also, or instead, explain this

While investigating, we found defold/extension-spine#294 ("Replaying the same spine animation from the animation callback leads to crashes under GC pressure") — a sibling reentrancy bug, that was fixed by PR #297 "Deferred callback destruction":

  • Merge commit bf34b6bc302b7668ac13c9723cbab32c261342c1 (tagged 4.7.4), content commit 0b6ad848041cc26f40e734621fca0ab3556778c9, merged 2026-08-08.
  • The bug, per the linked repro's main/repro.script: a spine.play_anim() callback calls spine.play_anim() again on every invocation (unconditionally, not gated on message_id), then forces collectgarbage("collect") plus allocates a few thousand vmath.vector4s. That reentrant call can free the in-flight LuaCallbackInfo while it's still executing on the call stack (RunTrackCallbackdmScript::PCall → Lua → back into spine.play_animClearCompletionCallbackdmScript::DestroyCallback). The reporter noted the crash disappears if the GC-pressure call is removed — we read that as GC activity being what turns the underlying use-after-free into a visible crash, not as a separate cause.
  • The fix adds m_CallbackInvocationDepth and m_DeferredCallbacks to SpineModelComponent, deferring callback destruction until no invocation is in flight, instead of freeing immediately.

We confirmed our fork's master does not have this fix.

We can't yet say whether this missing fix, our custom AnimationGroup sharing, or both together are responsible for the production crash — heap corruption from either mechanism would surface identically (an unrelated, later free() aborting). Noting this as an open hypothesis pending further isolation on our end; we're backporting #297 regardless, to ask for your opinion on whether our problem can be something that you've already fixed in PR #297 "Deferred callback destruction", or something you can recognize.

Expected behavior

No engine crash.

Versions

  • Defold 1.13.1 (63124d7fd7570a51224f34d3d6459a2f7d11bfe7)
  • Spine extension: tagged as 4.7.2, forked
  • Spine (Esoteric Software library) runtime: 4.2

Platforms

  • Platform: Android
  • OS: Android 12 (crash telemetry also shows occurrences on other Android versions/releases; this is a heap-logic bug, not device-specific)
  • Device: e.g. vivo V2029, arm64-v8a — low/mid-tier devices over-represented, likely just exposure/frequency rather than a device-specific cause

Logs

dmSpine--DestroyAnimationGroup (comp_spine_model.cpp-297)-redacted.json

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Size

None yet

Score

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions