skylot/jadx · error · JadxRuntimeException

Expected to find fallthrough terminus for handler {}

Error message

Expected to find fallthrough terminus for handler {}

What it means

Thrown during finally block detection (MarkFinallyVisitor). For each try-catch handler edge, jadx searches the scope terminus groups to find where the try block's normal flow exits (the fallthrough terminus). If the handler edge is not found in any terminus group, the try-finally data structures are inconsistent and finally extraction cannot proceed.

Source

Thrown at jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java:396

		if (finallyScopeTerminus == null) {
			return null;
		}
		Map<InsnNode, List<InsnNode>> matchingInsns = new HashMap<>();
		for (TryEdge edge : tryInfo.handlerScopes.keySet()) {
			if (edge.isHandlerExit() && edge.getExceptionHandler() == tryInfo.finallyHandler) {
				continue;
			}
			List<BlockNode> handlerBlocks = tryInfo.handlerScopes.get(edge);
			BlockNode scopeTerminus = null;
			for (BlockNode edgeTerminusBlock : tryInfo.scopeTerminusGroups.keySet()) {
				List<TryEdge> edgesWithTerminus = tryInfo.scopeTerminusGroups.get(edgeTerminusBlock);
				if (edgesWithTerminus.contains(edge)) {
					scopeTerminus = edgeTerminusBlock;
					break;
				}
			}
			if (scopeTerminus == null) {
				throw new JadxRuntimeException("Expected to find fallthrough terminus for handler " + edge);
			}
			TraverserActivePathState comparatorState =
					new TraverserActivePathState(mth, new SameInstructionsStrategyImpl(), finallyScopeTerminus,
							scopeTerminus, allHandlerBlocks, handlerBlocks);
			TraverserController controller = new TraverserController();
			List<TraverserActivePathState> pathResults;
			try {
				pathResults = controller.process(comparatorState);
			} catch (TraverserException e) {
				LOG.error("Could not search for finally duplicate instructions in path", e);
				return null;
			}
			Set<BlockNode> completeFinally = new HashSet<>();
			Set<BlockNode> completeCandidate = new HashSet<>();
			for (TraverserActivePathState pathResult : pathResults) {
				for (Pair<InsnNode> matchingInsnPair : pathResult.getMatchedInsns()) {
					InsnNode finallyInsn = matchingInsnPair.getFirst();
					InsnNode candidateInsn = matchingInsnPair.getSecond();

View on GitHub (pinned to e738a26571)

Solutions

  1. Update jadx — finally analysis is an actively developed area with frequent edge-case fixes
  2. Report the APK and method as a jadx issue with the full stack trace
  3. Try disabling finally-related optimizations if available via command-line flags
  4. Decompile the class individually to isolate the problematic method
Defensive patterns

Strategy: try-catch

Try / catch

try {
    jadxDecompiler.load();
    jadxDecompiler.save();
} catch (JadxRuntimeException e) {
    if (e.getMessage().contains("Expected to find fallthrough terminus")) {
        LOG.warn("Finally block analysis inconsistency, skipping class");
        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: A TryEdge from tryInfo.handlerScopes has no matching entry in tryInfo.scopeTerminusGroups. Every handler edge is expected to appear in exactly one terminus group, but this edge is orphaned.

Common situations: Complex try-finally-catch combinations where the terminus grouping logic doesn't account for all handler edges; obfuscated exception handling that creates unusual try-finally nesting; methods with multiple nested finally blocks and overlapping exception ranges.

Related errors


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