diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index 8f92825bb8..dbf6b18716 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -350,29 +350,32 @@ typedef struct clazz* JAVA_CLASS; SP[-1].data.l = SP[-1].data.l ^ (*SP).data.l; \ } -#define BC_I2L() SP[-1].data.l = SP[-1].data.i +// Conversion macros must rewrite the runtime type tag too. BC_DUP2_X1 / +// BC_DUP2_X2 / BC_DUP_X2 dispatch via IS_DOUBLE_WORD on the tag, so a stale +// tag corrupts the stack on chained assignments (issue #3108). +#define BC_I2L() do { SP[-1].data.l = SP[-1].data.i; SP[-1].type = CN1_TYPE_LONG; } while(0) -#define BC_L2I() SP[-1].data.i = (JAVA_INT)SP[-1].data.l +#define BC_L2I() do { SP[-1].data.i = (JAVA_INT)SP[-1].data.l; SP[-1].type = CN1_TYPE_INT; } while(0) -#define BC_L2F() SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.l +#define BC_L2F() do { SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.l; SP[-1].type = CN1_TYPE_FLOAT; } while(0) -#define BC_L2D() SP[-1].data.d = (JAVA_DOUBLE)SP[-1].data.l +#define BC_L2D() do { SP[-1].data.d = (JAVA_DOUBLE)SP[-1].data.l; SP[-1].type = CN1_TYPE_DOUBLE; } while(0) -#define BC_I2F() SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.i +#define BC_I2F() do { SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.i; SP[-1].type = CN1_TYPE_FLOAT; } while(0) -#define BC_F2I() SP[-1].data.i = (JAVA_INT)SP[-1].data.f +#define BC_F2I() do { SP[-1].data.i = (JAVA_INT)SP[-1].data.f; SP[-1].type = CN1_TYPE_INT; } while(0) -#define BC_F2L() SP[-1].data.l = (JAVA_LONG)SP[-1].data.f +#define BC_F2L() do { SP[-1].data.l = (JAVA_LONG)SP[-1].data.f; SP[-1].type = CN1_TYPE_LONG; } while(0) -#define BC_F2D() SP[-1].data.d = SP[-1].data.f +#define BC_F2D() do { SP[-1].data.d = SP[-1].data.f; SP[-1].type = CN1_TYPE_DOUBLE; } while(0) -#define BC_D2I() SP[-1].data.i = (JAVA_INT)SP[-1].data.d +#define BC_D2I() do { SP[-1].data.i = (JAVA_INT)SP[-1].data.d; SP[-1].type = CN1_TYPE_INT; } while(0) -#define BC_D2L() SP[-1].data.l = (JAVA_LONG)SP[-1].data.d +#define BC_D2L() do { SP[-1].data.l = (JAVA_LONG)SP[-1].data.d; SP[-1].type = CN1_TYPE_LONG; } while(0) -#define BC_I2D() SP[-1].data.d = SP[-1].data.i +#define BC_I2D() do { SP[-1].data.d = SP[-1].data.i; SP[-1].type = CN1_TYPE_DOUBLE; } while(0) -#define BC_D2F() SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.d +#define BC_D2F() do { SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.d; SP[-1].type = CN1_TYPE_FLOAT; } while(0) #ifdef CN1_INCLUDE_NPE_CHECKS #define BC_ARRAYLENGTH() { \ diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java index 06e60b08d7..fce84a34c0 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java @@ -530,52 +530,57 @@ public void appendInstruction(StringBuilder b, List instructions) { b.append(" SP--; SP[-1].data.l = SP[-1].data.l ^ (*SP).data.l; /* LXOR */\n") ; break; + // The conversion opcodes below must also rewrite SP[-1].type to + // match the new category. BC_DUP2_X1 / BC_DUP2_X2 / BC_DUP_X2 + // dispatch via the runtime tag (IS_DOUBLE_WORD), so a stale tag + // (e.g. INT left over after I2D) sends the dup macro down the + // cat-1 branch and corrupts the stack. See issue #3108. case Opcodes.I2L: - b.append(" SP[-1].data.l = SP[-1].data.i; /* I2L */\n"); + b.append(" SP[-1].data.l = SP[-1].data.i; SP[-1].type = CN1_TYPE_LONG; /* I2L */\n"); break; - + case Opcodes.I2F: - b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.i; /* I2F */\n"); + b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.i; SP[-1].type = CN1_TYPE_FLOAT; /* I2F */\n"); break; - + case Opcodes.I2D: - b.append(" SP[-1].data.d = SP[-1].data.i; /* I2D */;\n"); + b.append(" SP[-1].data.d = SP[-1].data.i; SP[-1].type = CN1_TYPE_DOUBLE; /* I2D */;\n"); break; - + case Opcodes.L2I: - b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.l; /* L2I */\n"); + b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.l; SP[-1].type = CN1_TYPE_INT; /* L2I */\n"); break; - + case Opcodes.L2F: - b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.l; /* L2F */\n"); + b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.l; SP[-1].type = CN1_TYPE_FLOAT; /* L2F */\n"); break; - + case Opcodes.L2D: - b.append(" SP[-1].data.d = (JAVA_DOUBLE)SP[-1].data.l; /* L2D */\n"); + b.append(" SP[-1].data.d = (JAVA_DOUBLE)SP[-1].data.l; SP[-1].type = CN1_TYPE_DOUBLE; /* L2D */\n"); break; - + case Opcodes.F2I: - b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.f; /* F2I */\n"); + b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.f; SP[-1].type = CN1_TYPE_INT; /* F2I */\n"); break; - + case Opcodes.F2L: - b.append(" SP[-1].data.l = (JAVA_LONG)SP[-1].data.f; /* F2L */\n"); + b.append(" SP[-1].data.l = (JAVA_LONG)SP[-1].data.f; SP[-1].type = CN1_TYPE_LONG; /* F2L */\n"); break; - + case Opcodes.F2D: - b.append(" SP[-1].data.d = SP[-1].data.f; /* F2D */\n"); + b.append(" SP[-1].data.d = SP[-1].data.f; SP[-1].type = CN1_TYPE_DOUBLE; /* F2D */\n"); break; - + case Opcodes.D2I: - b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.d; /* D2I */\n"); + b.append(" SP[-1].data.i = (JAVA_INT)SP[-1].data.d; SP[-1].type = CN1_TYPE_INT; /* D2I */\n"); break; - + case Opcodes.D2L: - b.append(" SP[-1].data.l = (JAVA_LONG)SP[-1].data.d; /* D2L */\n"); + b.append(" SP[-1].data.l = (JAVA_LONG)SP[-1].data.d; SP[-1].type = CN1_TYPE_LONG; /* D2L */\n"); break; - + case Opcodes.D2F: - b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.d; /* D2F */\n"); + b.append(" SP[-1].data.f = (JAVA_FLOAT)SP[-1].data.d; SP[-1].type = CN1_TYPE_FLOAT; /* D2F */\n"); break; case Opcodes.I2B: diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java index a5d468651a..f46318d1c0 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java @@ -1216,6 +1216,52 @@ void putfieldUnfoldedPathUsesPeekNotPop() { "Object PUTFIELD must not rely on C argument evaluation order:\n" + objC); } + /** + * Regression test for issue #3108 (second cause). + * + * The widening / narrowing conversion opcodes (I2D, I2L, F2D, F2L, L2D and + * their inverses) used to write the new value into SP[-1].data but leave + * the runtime type tag untouched. BC_DUP2_X1 / BC_DUP2_X2 / BC_DUP_X2 + * dispatch via IS_DOUBLE_WORD(...) on that tag, so e.g. PUSH_INT (tag=INT) + * followed by I2D (data updated, tag still INT) followed by DUP2_X1 sent + * the dup through the cat-1 branch and shifted SP by +2 instead of +1. + * The chained assignment "a.x = b.x = (double) someInt" then read garbage + * for the second putfield's operands and crashed with NPE on iOS. + * + * Each cat-changing conversion must rewrite SP[-1].type to the new + * CN1_TYPE_*. Pure-arithmetic conversions (I2B/I2C/I2S, I2F/F2I) are not + * involved in dup-dispatch but are checked for symmetry. + */ + @Test + void conversionOpcodesUpdateRuntimeTypeTag() { + Object[][] cases = { + {Opcodes.I2L, "I2L", "CN1_TYPE_LONG"}, + {Opcodes.I2D, "I2D", "CN1_TYPE_DOUBLE"}, + {Opcodes.I2F, "I2F", "CN1_TYPE_FLOAT"}, + {Opcodes.L2I, "L2I", "CN1_TYPE_INT"}, + {Opcodes.L2F, "L2F", "CN1_TYPE_FLOAT"}, + {Opcodes.L2D, "L2D", "CN1_TYPE_DOUBLE"}, + {Opcodes.F2I, "F2I", "CN1_TYPE_INT"}, + {Opcodes.F2L, "F2L", "CN1_TYPE_LONG"}, + {Opcodes.F2D, "F2D", "CN1_TYPE_DOUBLE"}, + {Opcodes.D2I, "D2I", "CN1_TYPE_INT"}, + {Opcodes.D2L, "D2L", "CN1_TYPE_LONG"}, + {Opcodes.D2F, "D2F", "CN1_TYPE_FLOAT"}, + }; + for (Object[] c : cases) { + int opcode = (Integer) c[0]; + String name = (String) c[1]; + String expectedTypeTag = (String) c[2]; + BasicInstruction instr = new BasicInstruction(opcode, 0); + StringBuilder out = new StringBuilder(); + instr.appendInstruction(out, new ArrayList()); + String emitted = out.toString(); + assertTrue(emitted.contains("SP[-1].type = " + expectedTypeTag), + name + " must rewrite SP[-1].type to " + expectedTypeTag + + " so BC_DUP2_X1 / BC_DUP2_X2 / BC_DUP_X2 dispatch correctly. Emitted:\n" + emitted); + } + } + @Test void testArithmeticExpressionCoverage() { // Use tryReduce to construct an ArithmeticExpression since constructor is private