diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index 54226155cf8..aee7f0afe39 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -2831,47 +2831,55 @@ public class Lower extends TreeTranslator { */ public void visitTypeTest(JCInstanceOf tree) { if (tree.expr.type.isPrimitive() || tree.pattern.type.isPrimitive()) { - JCExpression exactnessCheck = null; + JCStatement prefixStatement; + JCExpression exactnessCheck; JCExpression instanceOfExpr = translate(tree.expr); - // preserving the side effects of the value - VarSymbol dollar_s = new VarSymbol(FINAL | SYNTHETIC, - names.fromString("tmp" + variableIndex++ + this.target.syntheticNameChar()), - types.erasure(tree.expr.type), - currentMethodSym); - JCStatement var = make.at(tree.pos()) - .VarDef(dollar_s, instanceOfExpr); - if (types.isUnconditionallyExact(tree.expr.type, tree.pattern.type)) { + // instanceOfExpr; true + prefixStatement = make.Exec(instanceOfExpr); exactnessCheck = make.Literal(BOOLEAN, 1).setType(syms.booleanType.constType(1)); - } - else if (tree.expr.type.isReference()) { - JCExpression nullCheck = - makeBinary(NE, - make.Ident(dollar_s), - makeNull()); - + } else if (tree.expr.type.isPrimitive()) { + // ExactConversionSupport.isXxxExact(instanceOfExpr) + prefixStatement = null; + exactnessCheck = getExactnessCheck(tree, instanceOfExpr); + } else if (tree.expr.type.isReference()) { if (types.isUnconditionallyExact(types.unboxedType(tree.expr.type), tree.pattern.type)) { - exactnessCheck = nullCheck; - } else if (types.unboxedType(tree.expr.type).isPrimitive()) { - exactnessCheck = - makeBinary(AND, - nullCheck, - getExactnessCheck(tree, boxIfNeeded(make.Ident(dollar_s), types.unboxedType(tree.expr.type)))); + // instanceOfExpr != null + prefixStatement = null; + exactnessCheck = makeBinary(NE, instanceOfExpr, makeNull()); } else { - exactnessCheck = - makeBinary(AND, - nullCheck, - make.at(tree.pos()) - .TypeTest(make.Ident(dollar_s), make.Type(types.boxedClass(tree.pattern.type).type)) - .setType(syms.booleanType)); - } - } - else if (tree.expr.type.isPrimitive()) { - exactnessCheck = getExactnessCheck(tree, make.Ident(dollar_s)); - } + // We read the result of instanceOfExpr, so create variable + VarSymbol dollar_s = new VarSymbol(FINAL | SYNTHETIC, + names.fromString("tmp" + variableIndex++ + this.target.syntheticNameChar()), + types.erasure(tree.expr.type), + currentMethodSym); + prefixStatement = make.at(tree.pos()) + .VarDef(dollar_s, instanceOfExpr); - result = make.LetExpr(List.of(var), exactnessCheck) + JCExpression nullCheck = + makeBinary(NE, + make.Ident(dollar_s), + makeNull()); + + if (types.unboxedType(tree.expr.type).isPrimitive()) { + exactnessCheck = + makeBinary(AND, + nullCheck, + getExactnessCheck(tree, boxIfNeeded(make.Ident(dollar_s), types.unboxedType(tree.expr.type)))); + } else { + exactnessCheck = + makeBinary(AND, + nullCheck, + make.at(tree.pos()) + .TypeTest(make.Ident(dollar_s), make.Type(types.boxedClass(tree.pattern.type).type)) + .setType(syms.booleanType)); + } + } + } else { + throw Assert.error("Non primitive or reference type: " + tree.expr.type); + } + result = (prefixStatement == null ? exactnessCheck : make.LetExpr(List.of(prefixStatement), exactnessCheck)) .setType(syms.booleanType); } else { tree.expr = translate(tree.expr); diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfBytecodeTest.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfBytecodeTest.java new file mode 100644 index 00000000000..ba9cb1aa533 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfBytecodeTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.instruction.LoadInstruction; +import java.lang.classfile.instruction.StoreInstruction; +import java.util.BitSet; +import java.util.Map; + +import jdk.test.lib.compiler.InMemoryJavaCompiler; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8357185 + * @enablePreview + * @summary No unused local variable in unconditionally exact primitive patterns + * @library /test/lib + * @run junit PrimitiveInstanceOfBytecodeTest + */ +public class PrimitiveInstanceOfBytecodeTest { + + private static final String SOURCE = """ + public class Test { + public record A(int i) {} + public Integer get(A a) { + if (a instanceof A(int i)) { + return i; + } + return null; + } + } + """; + + @Test + public void testNoUnusedVarInRecordPattern() { + var testBytes = InMemoryJavaCompiler.compile(Map.of("Test", SOURCE)).get("Test"); + var code = ClassFile.of().parse(testBytes).methods().stream() + .filter(m -> m.methodName().equalsString("get")).findFirst() + .orElseThrow().findAttribute(Attributes.code()).orElseThrow(); + BitSet stores = new BitSet(code.maxLocals()); + BitSet loads = new BitSet(code.maxLocals()); + code.forEach(ce -> { + switch (ce) { + case StoreInstruction store -> stores.set(store.slot()); + case LoadInstruction load -> loads.set(load.slot()); + default -> {} + } + }); + // [this, a] are built-in locals that may be unused + loads.clear(0, 2); + stores.clear(0, 2); + if (!loads.equals(stores)) { + System.err.println("Loads: " + loads); + System.err.println("Stores: " + stores); + System.err.println(code.toDebugString()); + fail("Store and load mismatch, see stderr"); + } + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithTopLevelPatterns.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithTopLevelPatterns.java index 5c6d3e6361d..1b6c3af5520 100644 --- a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithTopLevelPatterns.java +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfPatternOpWithTopLevelPatterns.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8304487 8325257 8327683 8330387 + * @bug 8304487 8325257 8327683 8330387 8357185 * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) * @enablePreview * @compile PrimitiveInstanceOfPatternOpWithTopLevelPatterns.java @@ -52,6 +52,7 @@ public class PrimitiveInstanceOfPatternOpWithTopLevelPatterns { assertEquals(true, narrowingAndUnboxing()); assertEquals(true, patternExtractRecordComponent()); assertEquals(true, exprMethod()); + assertEquals(true, exprMethodSideEffect()); assertEquals(true, exprStaticallyQualified()); } @@ -173,6 +174,13 @@ public class PrimitiveInstanceOfPatternOpWithTopLevelPatterns { return meth() instanceof int ii; } + static int sideEffect; + public static Integer methSideEffect() { sideEffect++; return 42;} + public static boolean exprMethodSideEffect() { + sideEffect = 5; + return methSideEffect() instanceof int ii && sideEffect == 6; + } + public class A1 { public static int i = 42; } diff --git a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java index 47686c98a42..afd0f77e7bf 100644 --- a/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java +++ b/test/langtools/tools/javac/patterns/PrimitiveInstanceOfTypeComparisonOp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8304487 8325257 8327683 8330387 + * @bug 8304487 8325257 8327683 8330387 8357185 * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) * @enablePreview * @compile PrimitiveInstanceOfTypeComparisonOp.java @@ -52,6 +52,7 @@ public class PrimitiveInstanceOfTypeComparisonOp { assertEquals(true, narrowingAndUnboxing()); assertEquals(true, patternExtractRecordComponent()); assertEquals(true, exprMethod()); + assertEquals(true, exprMethodSideEffect()); assertEquals(true, exprStaticallyQualified()); } @@ -173,6 +174,13 @@ public class PrimitiveInstanceOfTypeComparisonOp { return meth() instanceof int; } + static int sideEffect; + public static Integer methSideEffect() { sideEffect++; return 42;} + public static boolean exprMethodSideEffect() { + sideEffect = 5; + return methSideEffect() instanceof int && sideEffect == 6; + } + public class A1 { public static int i = 42; }