skylot/jadx · error · JadxRuntimeException

Unknown global traverser state. Has a global state been dupl

Error message

Unknown global traverser state. Has a global state been duplicated?

What it means

Thrown inside an abort-on-terminus predicate created by MergePathActivePathTraverserHandler. The predicate checks whether a traverser state's global state matches the finally global state or the candidate global state. If it matches neither, the finally/candidate global state partitioning is broken — the error message asks whether a global state was inadvertently duplicated. This protects the invariant that every traverser state belongs to exactly one of two mutually exclusive global state groups during a scope-split merge.

Source

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

		}
		return blockInfo.getBlock() == terminus;
	}

	private static Function<TraverserState, Boolean> getStateAbortOnTerminusFunction(
			IdentifiedScopeWithTerminatorTraverserState finallyState,
			IdentifiedScopeWithTerminatorTraverserState candidateState) {
		BlockNode finallyTerminus = finallyState.getTerminus();
		BlockNode candidateTerminus = candidateState.getTerminus();
		GlobalTraverserSourceState finallyGlobalState = finallyState.getGlobalState();
		GlobalTraverserSourceState candidateGlobalState = candidateState.getGlobalState();

		return (final TraverserState state) -> {
			if (state.getGlobalState() == finallyGlobalState) {
				return isStateOnTerminus(state, finallyTerminus);
			} else if (state.getGlobalState() == candidateGlobalState) {
				return isStateOnTerminus(state, candidateTerminus);
			} else {
				throw new JadxRuntimeException("Unknown global traverser state. Has a global state been duplicated?");
			}
		};
	}

	private static PostMergeStatus getScopeSplitPostMergeStatus(List<TraverserActivePathState> pathsTaken) {
		// If the scope split is the same, all branches must not end in a terminator.
		PostMergeStatus status = new PostMergeStatus();
		for (TraverserActivePathState path : pathsTaken) {
			TraverserState finallyState;
			TraverserState candidateState;
			if (path.getFinallyState().isTerminal() || path.getCandidateState().isTerminal()) {
				TraverserState rawFinallyState = path.getFinallyState();
				TraverserState rawCandidateState = path.getCandidateState();
				boolean finallyIsCached = rawFinallyState instanceof RecoveredFromCacheTraverserState;
				boolean candidateIsCached = rawCandidateState instanceof RecoveredFromCacheTraverserState;
				if (!(finallyIsCached && candidateIsCached)) {
					status.perfectMatch = false;
					continue;

View on GitHub (pinned to e738a26571)

Solutions

  1. Report to jadx developers with the input file and full stack trace.
  2. Workaround: skip the finally visitor pass for this input.
  3. If developing jadx, verify that GlobalTraverserSourceState instances are not re-created during duplication — the same object references must flow from the original active path into all duplicated states.
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: During scope-split merge processing, a TraverserController is launched with an abort function. The function is called on states produced during sub-traversal. If a sub-traversal state's getGlobalState() (which delegates to getGlobalStateFor via the comparator) returns a GlobalTraverserSourceState that is reference-unequal to both the finally and candidate global states captured at predicate-creation time, this fires.

Common situations: Complex multi-branch finally blocks where the scope split introduces new global states, or where TraverserActivePathState.duplicate() fails to propagate the original global state references. Triggered by obfuscated bytecode with dense try-finally structures.

Related errors


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