8164714: Constructor.newInstance creates instance of inner class with null outer class

Reviewed-by: vromero
This commit is contained in:
Chen Liang 2025-03-25 19:01:02 +00:00
parent c856b3425a
commit 60544a15d6
8 changed files with 209 additions and 35 deletions

View File

@ -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 class Lower extends TreeTranslator {
private final boolean disableProtectedAccessors; // experimental
private final PkgInfo pkginfoOpt;
private final boolean optimizeOuterThis;
private final boolean nullCheckOuterThis;
private final boolean useMatchException;
private final HashMap<TypePairs, String> typePairToName;
private int variableIndex = 0;
@ -134,6 +135,8 @@ public class Lower extends TreeTranslator {
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 @@ public class Lower extends TreeTranslator {
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 class Lower extends TreeTranslator {
}
// 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<JCStatement> initializer = List.of(initOuterThis(mdef.body.pos, mdef.params.head.sym));
List<JCStatement> initializer = List.of(initOuterThis(mdef.body.pos, mdef.params.head.sym, storesThis)) ;
TreeInfo.mapSuperCalls(mdef.body, supercall -> make.Block(0, initializer.append(supercall)));
}
}

View File

@ -238,4 +238,11 @@ public enum Target {
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;
}
}

View File

@ -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 class AnnotatedExtendsTest {
.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);
}
}

View File

@ -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 @@ public class CheckNestmateAttrs {
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"
});

View File

@ -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<Class<?>> 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;
}
}
}

View File

@ -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<Class<?>> 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;
}
}
}

View File

@ -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 @@ public class AnnoTest {
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 @@ public class AnnoTest {
" )");
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)

View File

@ -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 @@ public class T6887895 {
"java/lang/Object",
"java/lang/String",
"T6887895",
"T6887895$Test"
"T6887895$Test",
"java/util/Objects",
};
Set<String> expect = new TreeSet<String>(Arrays.asList(expectNames));