From a524b63a899efd205a7f9d960e6c5200020c53e6 Mon Sep 17 00:00:00 2001 From: Srikanth Sankaran <131454720+srikanth-sankaran@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:34:52 +0530 Subject: [PATCH] [Switch Expression] Internal compiler error: java.lang.ClassCastException: class org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding cannot be cast to class org.eclipse.jdt.internal.compiler.lookup.ArrayBinding (#2344) * Fixes https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2335 --- .../compiler/ast/SwitchExpression.java | 50 ++++----- .../internal/compiler/ast/YieldStatement.java | 2 +- .../compiler/codegen/BranchLabel.java | 10 ++ .../internal/compiler/codegen/CodeStream.java | 6 +- .../SwitchExpressionsYieldTest.java | 106 ++++++++++++++++++ 5 files changed, 138 insertions(+), 36 deletions(-) diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java index e3dce374aa4..0ae5333430a 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; import java.util.stream.Collectors; import org.eclipse.jdt.core.compiler.CharOperation; @@ -263,31 +262,23 @@ private LocalVariableBinding addTypeStackVariable(CodeStream codeStream, TypeBin lvb.declaration = new LocalDeclaration(name, 0, 0); return lvb; } - private int getNextOffset(LocalVariableBinding local) { - int delta = ((TypeBinding.equalsEquals(local.type, TypeBinding.LONG)) || (TypeBinding.equalsEquals(local.type, TypeBinding.DOUBLE))) ? - 2 : 1; - return local.resolvedPosition + delta; - } - private void processTypesBindingsOnStack(CodeStream codeStream) { - int count = 0; + private void spillOperandStack(CodeStream codeStream) { int nextResolvedPosition = this.scope.offset; - if (!codeStream.switchSaveTypeBindings.empty()) { - this.typesOnStack = new ArrayList<>(); - int index = 0; - Stack typeStack = new Stack<>(); - int sz = codeStream.switchSaveTypeBindings.size(); - for (int i = codeStream.lastSwitchCumulativeSyntheticVars; i < sz; ++i) { - typeStack.add(codeStream.switchSaveTypeBindings.get(i)); - } - while (!typeStack.empty()) { - TypeBinding type = typeStack.pop(); - LocalVariableBinding lvb = addTypeStackVariable(codeStream, type, TypeIds.T_undefined, index++, nextResolvedPosition); - nextResolvedPosition = getNextOffset(lvb); - this.typesOnStack.add(lvb); - codeStream.store(lvb, false); - codeStream.addVariable(lvb); - ++count; - } + this.typesOnStack = new ArrayList<>(); + int index = 0; + while (codeStream.switchSaveTypeBindings.size() > 0) { + TypeBinding type = codeStream.switchSaveTypeBindings.peek(); + LocalVariableBinding lvb = addTypeStackVariable(codeStream, type, TypeIds.T_undefined, index++, nextResolvedPosition); + nextResolvedPosition += switch (lvb.type.id) { + case TypeIds.T_long, TypeIds.T_double -> 2; + default -> 1; + }; + this.typesOnStack.add(lvb); + codeStream.store(lvb, false); + codeStream.addVariable(lvb); + } + if (codeStream.stackDepth != 0 || codeStream.switchSaveTypeBindings.size() != 0) { + codeStream.classFile.referenceBinding.scope.problemReporter().operandStackSizeInappropriate(codeStream.classFile.referenceBinding.scope.referenceContext); } // now keep a position reserved for yield result value this.yieldResolvedPosition = nextResolvedPosition; @@ -295,14 +286,14 @@ private void processTypesBindingsOnStack(CodeStream codeStream) { (TypeBinding.equalsEquals(this.resolvedType, TypeBinding.DOUBLE))) ? 2 : 1; - codeStream.lastSwitchCumulativeSyntheticVars += count + 1; // 1 for yield var int delta = nextResolvedPosition - this.scope.offset; this.scope.adjustLocalVariablePositions(delta, false); } - public void loadStoredTypesAndKeep(CodeStream codeStream) { + public void refillOperandStack(CodeStream codeStream) { List tos = this.typesOnStack; int sz = tos != null ? tos.size() : 0; codeStream.clearTypeBindingStack(); + codeStream.stackDepth = 0; int index = sz - 1; while (index >= 0) { LocalVariableBinding lvb = tos.get(index--); @@ -322,15 +313,12 @@ private void removeStoredTypes(CodeStream codeStream) { } @Override public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { - int tmp = 0; if (this.containsTry) { - tmp = codeStream.lastSwitchCumulativeSyntheticVars; - processTypesBindingsOnStack(codeStream); + spillOperandStack(codeStream); } super.generateCode(currentScope, codeStream); if (this.containsTry) { removeStoredTypes(codeStream); - codeStream.lastSwitchCumulativeSyntheticVars = tmp; } if (!valueRequired) { // switch expression is saved to a variable that is not used. We need to pop the generated value from the stack diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java index ee6d2449770..771fa62f402 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java @@ -174,7 +174,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { } } if (generateExpressionResultCodeExpanded) { - this.switchExpression.loadStoredTypesAndKeep(codeStream); + this.switchExpression.refillOperandStack(codeStream); codeStream.load(this.secretYieldResultValue); codeStream.removeVariable(this.secretYieldResultValue); } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/BranchLabel.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/BranchLabel.java index 1b7e1b3edb3..6332ebef555 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/BranchLabel.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/BranchLabel.java @@ -14,9 +14,11 @@ package org.eclipse.jdt.internal.compiler.codegen; import java.util.Arrays; +import java.util.Stack; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; public class BranchLabel extends Label { @@ -29,6 +31,8 @@ public class BranchLabel extends Label { protected int targetStackDepth = -1; public final static int WIDE = 1; public final static int USED = 2; + private Stack switchSaveTypeBindings; + public BranchLabel() { // for creating labels ahead of code generation @@ -118,6 +122,7 @@ public void becomeDelegateFor(BranchLabel otherLabel) { this.forwardReferenceCount = indexInMerge; } +@SuppressWarnings("unchecked") protected void trackStackDepth(boolean branch) { /* Control can reach an instruction with a label in two ways: (1) via a branch using that label or (2) by falling through from the previous instruction. In both cases, we KNOW the stack depth at the instruction from which control flows to the instruction with the label. @@ -132,8 +137,11 @@ protected void trackStackDepth(boolean branch) { this.codeStream.classFile.referenceBinding.scope.problemReporter() .operandStackSizeInappropriate(this.codeStream.classFile.referenceBinding.scope.referenceContext); this.codeStream.stackDepth = 0; // FWIW + this.codeStream.switchSaveTypeBindings.clear(); } this.targetStackDepth = this.codeStream.stackDepth; + this.switchSaveTypeBindings = (Stack) this.codeStream.switchSaveTypeBindings.clone(); + // TODO: check that contents slot count matches targetStackDepth } // else: previous instruction completes abruptly via goto/return/throw: Wait for a backward branch to be emitted. } else { // Stack depth known at label having encountered a previous branch and/or having fallen through to label @@ -144,8 +152,10 @@ protected void trackStackDepth(boolean branch) { if (this.targetStackDepth < this.codeStream.stackDepth) this.targetStackDepth = this.codeStream.stackDepth; // FWIW, pick the higher water mark. } + // TODO: check that contents slot count matches targetStackDepth } this.codeStream.stackDepth = this.targetStackDepth; + this.codeStream.switchSaveTypeBindings = (Stack) this.switchSaveTypeBindings.clone(); } } /* diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java index 55d6eb694f7..9a383dc6f97 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java @@ -123,7 +123,6 @@ public class CodeStream { public boolean wideMode = false; public Stack switchSaveTypeBindings = new Stack<>(); - public int lastSwitchCumulativeSyntheticVars = 0; public Map> patternAccessorMap = new HashMap<>(); public Stack accessorExceptionTrapScopes = new Stack<>(); @@ -4581,7 +4580,6 @@ public void init(ClassFile targetClassFile) { this.position = 0; this.clearTypeBindingStack(); - this.lastSwitchCumulativeSyntheticVars = 0; this.patternAccessorMap.clear(); this.accessorExceptionTrapScopes.clear(); } @@ -6934,7 +6932,7 @@ public void pop2() { public void pushExceptionOnStack(TypeBinding binding) { this.stackDepth = 1; -// clearTypeBindingStack(); + clearTypeBindingStack(); pushTypeBinding(binding); if (this.stackDepth > this.stackMax) this.stackMax = this.stackDepth; @@ -7784,7 +7782,7 @@ private void pushTypeBindingArray() { return; popTypeBinding(); // index TypeBinding type = popTypeBinding(); // arrayref - pushTypeBinding(((ArrayBinding) type).leafComponentType); + pushTypeBinding(((ArrayBinding) type).elementsType()); } private TypeBinding getPopularBinding(char[] typeName) { Scope scope = this.classFile.referenceBinding.scope; diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java index f6304b82730..c0d8327cf0a 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java @@ -7193,4 +7193,110 @@ static void print(double d) {} }, "OK"); } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2335 + // [Switch Expression] Internal compiler error: java.lang.ClassCastException: class org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding cannot be cast to class org.eclipse.jdt.internal.compiler.lookup.ArrayBinding + public void testIssue2335() { + if (this.complianceLevel < ClassFileConstants.JDK14) + return; + this.runConformTest( + new String[] { + "X.java", + """ + public final class X { + + public void show() { + + int size1 = 1; + int size2 = 2; + int size3 = 3; + + short[][][] array = new short[size1][size2][size3]; + + for (int i = 0; i < size1; i++) { + for (int j = 0; j < size2; j++) { + boolean on = false; + for (int k = 0; k < size3; k++) { + array[i][j][k] = on ? (short) 1 : (short) 0; + } + } + } + System.out.println(switch(42) { + default -> { + try { + yield 42; + } finally { + + } + } + }); + + } + + public static void main(String[] args) { + new X().show(); + } + } + """ + }, + "42"); + } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2335 + // [Switch Expression] Internal compiler error: java.lang.ClassCastException: class org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding cannot be cast to class org.eclipse.jdt.internal.compiler.lookup.ArrayBinding + public void testIssue2335_min() { + if (this.complianceLevel < ClassFileConstants.JDK14) + return; + this.runConformTest( + new String[] { + "X.java", + """ + public final class X { + public static void main(String[] args) { + short[] array = new short[10]; + + for (int i = 0; i < 10; i++) { + boolean on = false; + array[i] = on ? (short) 1 : (short) 0; + } + System.out.println(switch(42) { + default -> { + try { + yield 42; + } finally { + + } + } + }); + } + } + """ + }, + "42"); + } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2335 + // [Switch Expression] Internal compiler error: java.lang.ClassCastException: class org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding cannot be cast to class org.eclipse.jdt.internal.compiler.lookup.ArrayBinding + public void testIssue2335_other() { + if (this.complianceLevel < ClassFileConstants.JDK14) + return; + this.runConformTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String[] args) { + System.out.println(switch (1) { + default -> { + try { + System.out.println(switch (10) { default -> { try { yield 10; } finally {} } }); + } finally {} + yield 1; + } + }); + } + X() {} + } + """ + }, + "10\n" + + "1"); + } }