pxb1988/dex2jar · error · RuntimeException

stack not balanced

Error message

stack not balanced

What it means

Thrown by J2IRConverter.merge() while abstractly interpreting JVM bytecode to build the data-flow frames used for Java-to-IR conversion. When two control-flow paths converge on a block that already has a frame, the operand stack depths must match; if the incoming stack size differs from the existing frame's stack size, the converter declares the stack unbalanced and aborts, because it cannot build a consistent single-assignment relation for the operands.

Solutions

  1. Identify the offending method/class from the surrounding stack trace and recompile it from source with a standard compiler so stack maps are balanced
  2. Remove or soften the obfuscation (e.g. disable branch/control-flow obfuscation options) and retry the conversion
  3. Ensure the input class file is not corrupted and matches a supported ASM class-file version (update dex-translator/ASM dependencies)
  4. Patch J2IRConverter.merge to pad/trim stacks heuristically if you control the build and must accept such bytecode

Example fix

// before: converting obfuscated jar
dexTranslator.convert(archiveIn, archiveOut);
// after: pre-strip control-flow obfuscation, e.g. recompile source or run a deobfuscator first
jar2dex.convert(new File("input-obfuscated.jar"), new File("out-dex.zip")); // feed deobfuscated jar instead
Defensive patterns

Strategy: try-catch

Validate before calling

// Prefer pre-verified input; check for branch obfuscation markers before conversion
boolean risky = classFileWasObfuscated || !compiledByStandardCompiler;
if (risky) { runDeobfuscatorOrRecompile(); }

Try / catch

try {
    dexTranslator.convert(inJar, outDex);
} catch (RuntimeException e) {
    if ("stack not balanced".equals(e.getMessage())) {
        log.warn("Unbalanced-stack method; recompile or deobfuscate input", e);
        // fall back to a pre-desugared/deobfuscated artifact
    } else throw e;
}

Prevention

When it happens

Trigger: Converting a Java class whose bytecode has a jump target reachable from paths with different operand-stack depths — typically hand-written, obfuscated, or bytecode-generated classes (ASM/bytecode manipulation tools) that leave values on the stack across branches; javac-compiled normal code does not trigger it.

Common situations: Translating obfuscated apps (proguard/branchy obfuscation), code produced by bytecode-generation frameworks, classes assembled with custom assemblers, or corrupted/class-file-version-mismatched bytecode fed into dex-translator's jar2dex pipeline.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pxb1988/dex2jar@b5bda4fb49 (2026-09-08). Data as JSON: /api/errors/655d4207fb7eadd7. Report an issue: GitHub.

Appendix: source

Thrown at dex-translator/src/main/java/com/googlecode/d2j/converter/J2IRConverter.java:911

        if (parentCount[dst] > 1) {
            for (int i = 0; i < src.getLocals(); i++) {
                JvmValue p = src.getLocal(i);
                JvmValue q = distFrame.getLocal(i);
                if (p != null) {
                    if (q == null) {
                        q = new JvmValue(p.getSize());
                        distFrame.setLocal(i, q);
                    }
                    relate(p, q);
                }
            }
            if (src.getStackSize() > 0) {
                if (distFrame.getStackSize() == 0) {
                    for (int i = 0; i < src.getStackSize(); i++) {
                        distFrame.push(new JvmValue(src.getStack(i).getSize()));
                    }
                } else if (distFrame.getStackSize() != src.getStackSize()) {
                    throw new RuntimeException("stack not balanced");
                }
                for (int i = 0; i < src.getStackSize(); i++) {
                    JvmValue p = src.getStack(i);
                    JvmValue q = distFrame.getStack(i);
                    relate(p, q);
                }
            }
        } else {
            distFrame.init(src);
        }
    }

    private void relate(JvmValue parent, JvmValue child) {
        if (child.parent == null) {
            child.parent = parent;
        } else if (child.parent == parent) {
            //
        } else {

View on GitHub (pinned to b5bda4fb49)