skylot/jadx · critical · JadxRuntimeException

A traverser handler which was not expected to change path st

Error message

A traverser handler which was not expected to change path states actually did

What it means

Thrown in the TraverserController when processing a single traverser state. A handler in the NOT_READY or AWAITING_OPTIONAL_PREDECESSOR_MERGE comparison state is expected to process in-place — returning exactly one result that is the same active path state object. If the handler returns a different result count or a different state object, the in-place processing invariant is violated.

Source

Thrown at jadx-core/src/main/java/jadx/core/dex/visitors/finaly/traverser/TraverserController.java:183

		return List.of(state);
	}

	/**
	 * Advances a singular state once.
	 *
	 * @return Whether this state has been aborted by the state abort function.
	 */
	private boolean advanceSingleState(TraverserActivePathState activePathState, TraverserState singleState,
			boolean hasReadyToCompare) throws TraverserException {
		boolean stateAborted = stateAbortCondition != null && stateAbortCondition.apply(singleState);
		if (stateAbortCondition == null || !stateAborted) {
			if (singleState.getCompareState() == TraverserState.ComparisonState.NOT_READY
					|| singleState.getCompareState() == TraverserState.ComparisonState.AWAITING_OPTIONAL_PREDECESSOR_MERGE
							&& hasReadyToCompare) {
				AbstractBlockTraverserHandler handler = singleState.getNextHandler();
				List<TraverserActivePathState> results = processHandlerImplementations(activePathState, handler);
				if (results.size() != 1 || results.get(0) != activePathState) {
					throw new JadxRuntimeException("A traverser handler which was not expected to change path states actually did");
				}
			}
		}
		return stateAborted;
	}
}

View on GitHub (pinned to e738a26571)

Solutions

  1. Update jadx — traverser state machine bugs are patched promptly
  2. Report as a jadx issue with the full stack trace and APK if available
  3. This is an internal contract violation; not fixable from user input
Defensive patterns

Strategy: try-catch

Try / catch

try {
    jadxDecompiler.load();
    jadxDecompiler.save();
} catch (JadxRuntimeException e) {
    if (e.getMessage().contains("not expected to change path states")) {
        LOG.error("Internal jadx traverser state machine bug — update jadx", e);
        for (ClassNode cls : jadxDecompiler.getClasses()) {
            try {
                jadxDecompiler.decompileClass(cls);
            } catch (JadxRuntimeException ex) {
                LOG.warn("Skipped: {}", cls.getFullName());
            }
        }
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: processHandlerImplementations returns a list with size != 1 or where results.get(0) is not the same object reference as activePathState. The handler split, merged, or replaced the path state when it was contractually required to modify in-place.

Common situations: Internal inconsistency in the traverser state machine — a handler incorrectly modifies path states when it should only update internal state; typically a jadx development bug in the finally-block traversal logic, not input-driven.

Related errors


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