skylot/jadx · error · JadxRuntimeException

Orphaned traverser state

Error message

Orphaned traverser state

What it means

Thrown by BaseBlockTraverserHandler.handle() when TraverserActivePathState.getReferenceForState() returns null for the current state. An active path holds exactly two state slots (finally and candidate) via AtomicReferences; getReferenceForState performs reference-identity checks against both. Returning null means the state being handled is not the finally or candidate state currently registered in its own comparator — the state graph has become detached or corrupted.

Source

Thrown at jadx-core/src/main/java/jadx/core/dex/visitors/finaly/traverser/handlers/BaseBlockTraverserHandler.java:32

	public BaseBlockTraverserHandler(TraverserState initialState) {
		super(initialState);
	}

	public BaseBlockTraverserHandler(AtomicReference<TraverserState> initialStateRef) {
		super(initialStateRef);
	}

	@Override
	protected void handle() {
		TraverserBlockInfo blockInsnInfo = getState().getBlockInsnInfo();
		if (blockInsnInfo == null) {
			throw new JadxRuntimeException("Expected to find block info within " + getClass().getSimpleName());
		}
		TraverserActivePathState comparator = getState().getComparatorState();
		AtomicReference<TraverserState> stateRef = comparator.getReferenceForState(getState());
		if (stateRef == null) {
			throw new JadxRuntimeException("Orphaned traverser state");
		}
		BlockNode block = blockInsnInfo.getBlock();
		ImplicitInsnBlockTraverserVisitor implicitVisitor = new ImplicitInsnBlockTraverserVisitor(getState());
		TraverserState stateAfterImplicit = implicitVisitor.visit(block);
		PathEndBlockTraverserVisitor pathEndVisitor = new PathEndBlockTraverserVisitor(stateAfterImplicit);
		TraverserState nextState = pathEndVisitor.visit(block);

		stateRef.set(nextState);
	}
}

View on GitHub (pinned to e738a26571)

Solutions

  1. Report to the jadx project with the input APK/JAR and stack trace — this is an internal state-management bug.
  2. Workaround: disable the finally extraction visitor pass for the problematic input.
  3. Update to the latest jadx version; the traverser state lifecycle is under active refinement.
  4. If patching jadx, audit TraverserActivePathState.duplicate() and produceFromFactories() to ensure the new state's comparatorState back-pointer matches the active path that holds the state's AtomicReference.
Defensive patterns

Strategy: validation

Validate before calling

// Before handling, verify the state is still the live reference
AtomicReference<TraverserState> ref = comparator.getReferenceForState(state);
if (ref == null) {
    // State is stale/orphaned; do not proceed
    return;
}

Prevention

When it happens

Trigger: A TraverserState object was duplicated or replaced (via stateRef.set()) but a stale reference to the previous state was still dispatched to a handler. Alternatively, a state was moved between active path instances during merge/duplicate operations without updating its comparatorState pointer.

Common situations: Decompiling APKs with intricate try-finally or synchronized-block patterns that trigger the scope-split merge path. The error surfaces when the finally traverser's state duplication logic produces a state that references a different TraverserActivePathState than the one that holds it.

Related errors


AI-assisted analysis of skylot/jadx@e738a26571 (2026-08-14). Data as JSON: /api/errors/7ab85fcc59f1d3e6. Report an issue: GitHub.