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 d2219bb08b861..a544b3948e59b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -105,6 +105,7 @@ public static Lower instance(Context context) { private final boolean disableProtectedAccessors; // experimental private final PkgInfo pkginfoOpt; private final boolean optimizeOuterThis; + private final boolean nullCheckOuterThis; private final boolean useMatchException; private final HashMap typePairToName; private int variableIndex = 0; @@ -134,6 +135,8 @@ protected Lower(Context context) { optimizeOuterThis = target.optimizeOuterThis() || options.getBoolean("optimizeOuterThis", false); + nullCheckOuterThis = options.getBoolean("nullCheckOuterThis", + target.nullCheckOuterThisByDefault()); disableProtectedAccessors = options.isSet("disableProtectedAccessors"); Source source = Source.instance(context); Preview preview = Preview.instance(context); @@ -1794,18 +1797,27 @@ JCStatement initField(int pos, Symbol rhs, Symbol lhs) { make.Ident(rhs)).setType(lhs.erasure(types))); } - /** Return tree simulating the assignment {@code this.this$n = this$n}. + /** + * Return tree simulating null checking outer this and/or assigning. This is + * called when a null check is required (nullCheckOuterThis), or a synthetic + * field is generated (stores). */ - JCStatement initOuterThis(int pos, VarSymbol rhs) { + JCStatement initOuterThis(int pos, VarSymbol rhs, boolean stores) { Assert.check(rhs.owner.kind == MTH); - VarSymbol lhs = outerThisStack.head; - Assert.check(rhs.owner.owner == lhs.owner); + Assert.check(nullCheckOuterThis || stores); // One of the flags must be true make.at(pos); - return - make.Exec( - make.Assign( + JCExpression expression = make.Ident(rhs); + if (nullCheckOuterThis) { + expression = attr.makeNullCheck(expression); + } + if (stores) { + VarSymbol lhs = outerThisStack.head; + Assert.check(rhs.owner.owner == lhs.owner); + expression = make.Assign( make.Select(make.This(lhs.owner.erasure(types)), lhs), - make.Ident(rhs)).setType(lhs.erasure(types))); + expression).setType(lhs.erasure(types)); + } + return make.Exec(expression); } /* ************************************************************************ @@ -2210,15 +2222,22 @@ public void visitClassDef(JCClassDecl tree) { } // If this$n was accessed, add the field definition and prepend // initializer code to any super() invocation to initialize it - if (currentClass.hasOuterInstance() && shouldEmitOuterThis(currentClass)) { - tree.defs = tree.defs.prepend(otdef); - enterSynthetic(tree.pos(), otdef.sym, currentClass.members()); + // otherwise prepend enclosing instance null check code if required + emitOuter: + if (currentClass.hasOuterInstance()) { + boolean storesThis = shouldEmitOuterThis(currentClass); + if (storesThis) { + tree.defs = tree.defs.prepend(otdef); + enterSynthetic(tree.pos(), otdef.sym, currentClass.members()); + } else if (!nullCheckOuterThis) { + break emitOuter; + } for (JCTree def : tree.defs) { if (TreeInfo.isConstructor(def)) { JCMethodDecl mdef = (JCMethodDecl)def; if (TreeInfo.hasConstructorCall(mdef, names._super)) { - List initializer = List.of(initOuterThis(mdef.body.pos, mdef.params.head.sym)); + List initializer = List.of(initOuterThis(mdef.body.pos, mdef.params.head.sym, storesThis)) ; TreeInfo.mapSuperCalls(mdef.body, supercall -> make.Block(0, initializer.append(supercall))); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java index 90f235fff04bc..5b207352e6e34 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java @@ -238,4 +238,11 @@ public boolean optimizeOuterThis() { public boolean usesReferenceOnlySelectorTypes() { return compareTo(Target.JDK1_23) < 0; } + + /** + * Should we emit a null check against incoming outer this argument by default? + */ + public boolean nullCheckOuterThisByDefault() { + return compareTo(JDK1_25) >= 0; + } } diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java index 450e2b5956a54..11694085f539a 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/AnnotatedExtendsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -59,7 +59,7 @@ public static strictfp void main(String args[]) throws Exception { .classes(classPath.toString()) .run() .getOutput(Task.OutputKind.DIRECT); - if (!javapOut.contains("0: #21(): CLASS_EXTENDS, type_index=65535")) + if (!javapOut.contains("0: #27(): CLASS_EXTENDS, type_index=65535")) throw new AssertionError("Expected output missing: " + javapOut); } } diff --git a/test/langtools/tools/javac/nestmates/CheckNestmateAttrs.java b/test/langtools/tools/javac/nestmates/CheckNestmateAttrs.java index a56d294f9b406..e02e47861d705 100644 --- a/test/langtools/tools/javac/nestmates/CheckNestmateAttrs.java +++ b/test/langtools/tools/javac/nestmates/CheckNestmateAttrs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -89,9 +89,9 @@ void run() { runCheck(params, new String [] { "NestHost: class CheckNestmateAttrs", "0: aload_0", - "1: getfield #1 // Field this$1:LCheckNestmateAttrs$Inner;", - "4: getfield #13 // Field CheckNestmateAttrs$Inner.this$0:LCheckNestmateAttrs;", - "7: invokevirtual #19 // Method CheckNestmateAttrs.test:()V", + "1: getfield #7 // Field this$1:LCheckNestmateAttrs$Inner;", + "4: getfield #19 // Field CheckNestmateAttrs$Inner.this$0:LCheckNestmateAttrs;", + "7: invokevirtual #25 // Method CheckNestmateAttrs.test:()V", "10: return" }); diff --git a/test/langtools/tools/javac/outerThisNull/NoOuterThisNullChecks.java b/test/langtools/tools/javac/outerThisNull/NoOuterThisNullChecks.java new file mode 100644 index 0000000000000..1706394dc29ce --- /dev/null +++ b/test/langtools/tools/javac/outerThisNull/NoOuterThisNullChecks.java @@ -0,0 +1,73 @@ +/* + * 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.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8164714 + * @summary No null check for immediate enclosing instance for VM/reflective + * invocation of inner classes for older versions or on request + * + * @clean * + * @compile -XDnullCheckOuterThis=false NoOuterThisNullChecks.java + * @run junit NoOuterThisNullChecks + * + * @clean * + * @compile --release 17 NoOuterThisNullChecks.java + * @run junit NoOuterThisNullChecks + */ +class NoOuterThisNullChecks { + static Stream> testClasses() { + return Stream.of(NoOuterThis.class, OuterThisField.class); + } + + @MethodSource("testClasses") + @ParameterizedTest + void testNoOuter(Class clz) { + assertDoesNotThrow(() -> clz.getDeclaredConstructor(NoOuterThisNullChecks.class).newInstance((Object) null)); + + MethodHandle mh = assertDoesNotThrow(() -> MethodHandles.lookup().findConstructor(clz, MethodType.methodType(void.class, NoOuterThisNullChecks.class))) + .asType(MethodType.methodType(Object.class, Object.class)); + assertDoesNotThrow(() -> { + Object stub = mh.invokeExact((Object) null); + }); + } + + class NoOuterThis {} + class OuterThisField { + @Override + public String toString() { + return "outer this = " + NoOuterThisNullChecks.this; + } + } +} diff --git a/test/langtools/tools/javac/outerThisNull/OuterThisNullChecks.java b/test/langtools/tools/javac/outerThisNull/OuterThisNullChecks.java new file mode 100644 index 0000000000000..17ad042f78dd1 --- /dev/null +++ b/test/langtools/tools/javac/outerThisNull/OuterThisNullChecks.java @@ -0,0 +1,74 @@ +/* + * 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.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8164714 + * @summary Null check for immediate enclosing instance for VM/reflective + * invocation of inner classes + * + * @clean * + * @compile OuterThisNullChecks.java + * @run junit OuterThisNullChecks + * + * @clean * + * @compile --release 17 -XDnullCheckOuterThis=true OuterThisNullChecks.java + * @run junit OuterThisNullChecks + */ +class OuterThisNullChecks { + static Stream> testClasses() { + return Stream.of(NoOuterThis.class, OuterThisField.class); + } + + @MethodSource("testClasses") + @ParameterizedTest + void testNoOuter(Class clz) { + var ite = assertThrows(InvocationTargetException.class, () -> clz.getDeclaredConstructor(OuterThisNullChecks.class).newInstance((Object) null)); + assertInstanceOf(NullPointerException.class, ite.getCause()); + + MethodHandle mh = assertDoesNotThrow(() -> MethodHandles.lookup().findConstructor(clz, MethodType.methodType(void.class, OuterThisNullChecks.class))) + .asType(MethodType.methodType(Object.class, Object.class)); + assertThrows(NullPointerException.class, () -> { + Object stub = mh.invokeExact((Object) null); + }); + } + + class NoOuterThis {} + class OuterThisField { + @Override + public String toString() { + return "outer this = " + OuterThisNullChecks.this; + } + } +} diff --git a/test/langtools/tools/javap/AnnoTest.java b/test/langtools/tools/javap/AnnoTest.java index e7bf407491559..205a997aec6ef 100644 --- a/test/langtools/tools/javap/AnnoTest.java +++ b/test/langtools/tools/javap/AnnoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -49,50 +49,50 @@ void run() throws Exception { expect(out, "RuntimeVisibleAnnotations:\n" + - " 0: #18(#19=B#20)\n" + + " 0: #24(#25=B#26)\n" + " AnnoTest$ByteAnno(\n" + " value=(byte) 42\n" + " )\n" + - " 1: #21(#19=S#22)\n" + + " 1: #27(#25=S#28)\n" + " AnnoTest$ShortAnno(\n" + " value=(short) 3\n" + " )"); expect(out, "RuntimeInvisibleAnnotations:\n" + - " 0: #24(#19=[J#25,J#27,J#29,J#31,J#33])\n" + + " 0: #30(#25=[J#31,J#33,J#35,J#37,J#39])\n" + " AnnoTest$ArrayAnno(\n" + " value=[1l,2l,3l,4l,5l]\n" + " )\n" + - " 1: #35(#19=Z#36)\n" + + " 1: #41(#25=Z#42)\n" + " AnnoTest$BooleanAnno(\n" + " value=false\n" + " )\n" + - " 2: #37(#38=c#39)\n" + + " 2: #43(#44=c#45)\n" + " AnnoTest$ClassAnno(\n" + " type=class Ljava/lang/Object;\n" + " )\n" + - " 3: #40(#41=e#42.#43)\n" + + " 3: #46(#47=e#48.#49)\n" + " AnnoTest$EnumAnno(\n" + " kind=Ljavax/lang/model/element/ElementKind;.PACKAGE\n" + " )\n" + - " 4: #44(#19=I#45)\n" + + " 4: #50(#25=I#51)\n" + " AnnoTest$IntAnno(\n" + " value=2\n" + " )\n" + - " 5: #46()\n" + + " 5: #52()\n" + " AnnoTest$IntDefaultAnno\n" + - " 6: #47(#48=s#49)\n" + + " 6: #53(#54=s#55)\n" + " AnnoTest$NameAnno(\n" + " name=\"NAME\"\n" + " )\n" + - " 7: #50(#51=D#52,#54=F#55)\n" + + " 7: #56(#57=D#58,#60=F#61)\n" + " AnnoTest$MultiAnno(\n" + " d=3.14159d\n" + " f=2.71828f\n" + " )\n" + - " 8: #56()\n" + + " 8: #62()\n" + " AnnoTest$SimpleAnno\n" + - " 9: #57(#19=@#44(#19=I#58))\n" + + " 9: #63(#25=@#50(#25=I#64))\n" + " AnnoTest$AnnoAnno(\n" + " value=@AnnoTest$IntAnno(\n" + " value=5\n" + @@ -100,7 +100,7 @@ void run() throws Exception { " )"); expect(out, "RuntimeInvisibleTypeAnnotations:\n" + - " 0: #60(): CLASS_EXTENDS, type_index=0\n" + + " 0: #66(): CLASS_EXTENDS, type_index=0\n" + " AnnoTest$TypeAnno"); if (errors > 0) diff --git a/test/langtools/tools/javap/classfile/T6887895.java b/test/langtools/tools/javap/classfile/T6887895.java index 01d8550806406..3f9052adb1ee9 100644 --- a/test/langtools/tools/javap/classfile/T6887895.java +++ b/test/langtools/tools/javap/classfile/T6887895.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 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 @@ -57,7 +57,8 @@ void run() throws Exception { "java/lang/Object", "java/lang/String", "T6887895", - "T6887895$Test" + "T6887895$Test", + "java/util/Objects", }; Set expect = new TreeSet(Arrays.asList(expectNames));