From 1fb608e1bcbbc3fd68205ea168f10584cc5c2a62 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 27 Feb 2026 17:52:24 +0000 Subject: [PATCH] 8378792: ObjectMethods.bootstrap missing getter validation Reviewed-by: rriggs, jvernee --- .../java/lang/runtime/ObjectMethods.java | 23 +++--- .../java/lang/runtime/ObjectMethodsTest.java | 71 ++++++++++++++----- 2 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java index 18aa6f29f1f..e4b2886404f 100644 --- a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java +++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -363,9 +363,9 @@ public final class ObjectMethods { * @return the method handle */ private static MethodHandle makeToString(MethodHandles.Lookup lookup, - Class receiverClass, - MethodHandle[] getters, - List names) { + Class receiverClass, + MethodHandle[] getters, + List names) { assert getters.length == names.size(); if (getters.length == 0) { // special case @@ -516,8 +516,8 @@ public final class ObjectMethods { requireNonNull(type); requireNonNull(recordClass); requireNonNull(names); - requireNonNull(getters); - Arrays.stream(getters).forEach(Objects::requireNonNull); + List getterList = List.of(getters); // deep null check + MethodType methodType; if (type instanceof MethodType mt) methodType = mt; @@ -526,7 +526,14 @@ public final class ObjectMethods { if (!MethodHandle.class.equals(type)) throw new IllegalArgumentException(type.toString()); } - List getterList = List.of(getters); + + for (MethodHandle getter : getterList) { + var getterType = getter.type(); + if (getterType.parameterCount() != 1 || getterType.returnType() == void.class || getterType.parameterType(0) != recordClass) { + throw new IllegalArgumentException("Illegal getter type %s for recordClass %s".formatted(getterType, recordClass.getTypeName())); + } + } + MethodHandle handle = switch (methodName) { case "equals" -> { if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class))) @@ -541,7 +548,7 @@ public final class ObjectMethods { case "toString" -> { if (methodType != null && !methodType.equals(MethodType.methodType(String.class, recordClass))) throw new IllegalArgumentException("Bad method type: " + methodType); - List nameList = "".equals(names) ? List.of() : List.of(names.split(";")); + List nameList = names.isEmpty() ? List.of() : List.of(names.split(";")); if (nameList.size() != getterList.size()) throw new IllegalArgumentException("Name list and accessor list do not match"); yield makeToString(lookup, recordClass, getters, nameList); diff --git a/test/jdk/java/lang/runtime/ObjectMethodsTest.java b/test/jdk/java/lang/runtime/ObjectMethodsTest.java index 951d3b68383..d7ca5912273 100644 --- a/test/jdk/java/lang/runtime/ObjectMethodsTest.java +++ b/test/jdk/java/lang/runtime/ObjectMethodsTest.java @@ -36,11 +36,11 @@ import java.lang.invoke.MethodType; import java.lang.runtime.ObjectMethods; import static java.lang.invoke.MethodType.methodType; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; public class ObjectMethodsTest { @@ -144,8 +144,8 @@ public class ObjectMethodsTest { assertEquals("Empty[]", (String)handle.invokeExact(new Empty())); } - Class NPE = NullPointerException.class; - Class IAE = IllegalArgumentException.class; + private static final Class NPE = NullPointerException.class; + private static final Class IAE = IllegalArgumentException.class; @Test public void exceptions() { @@ -157,25 +157,60 @@ public class ObjectMethodsTest { assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "toString", C.EQUALS_DESC, C.class, "x;y", C.ACCESSORS)); assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "hashCode", C.TO_STRING_DESC, C.class, "x;y", C.ACCESSORS)); assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "equals", C.HASHCODE_DESC, C.class, "x;y", C.ACCESSORS)); + } - record NamePlusType(String mn, MethodType mt) {} - List namePlusTypeList = List.of( + record NamePlusType(String name, MethodType type) {} + + static List namePlusTypeList() { + return List.of( new NamePlusType("toString", C.TO_STRING_DESC), new NamePlusType("equals", C.EQUALS_DESC), new NamePlusType("hashCode", C.HASHCODE_DESC) ); - - for (NamePlusType npt : namePlusTypeList) { - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, npt.mn(), npt.mt(), C.class, "x;y", null)); - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, npt.mn(), npt.mt(), C.class, "x;y", new MethodHandle[]{null})); - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, npt.mn(), npt.mt(), C.class, null, C.ACCESSORS)); - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, npt.mn(), npt.mt(), null, "x;y", C.ACCESSORS)); - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, npt.mn(), null, C.class, "x;y", C.ACCESSORS)); - assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, null, npt.mt(), C.class, "x;y", C.ACCESSORS)); - assertThrows(NPE, () -> ObjectMethods.bootstrap(null, npt.mn(), npt.mt(), C.class, "x;y", C.ACCESSORS)); - } } + @MethodSource("namePlusTypeList") + @ParameterizedTest + void commonExceptions(NamePlusType npt) { + String name = npt.name(); + MethodType type = npt.type(); + + // Null checks + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, "x;y", null)); + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, "x;y", new MethodHandle[]{null})); + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, null, C.ACCESSORS)); + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, null, "x;y", C.ACCESSORS)); + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, name, null, C.class, "x;y", C.ACCESSORS)); + assertThrows(NPE, () -> ObjectMethods.bootstrap(LOOKUP, null, type, C.class, "x;y", C.ACCESSORS)); + assertThrows(NPE, () -> ObjectMethods.bootstrap(null, name, type, C.class, "x;y", C.ACCESSORS)); + + // Bad indy call receiver type - change C to this test class + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, name, type.changeParameterType(0, this.getClass()), C.class, "x;y", C.ACCESSORS)); + + // Bad getter types + var wrongReceiverGetter = assertDoesNotThrow(() -> MethodHandles.lookup().findGetter(this.getClass(), "y", int.class)); + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, "x;y", + new MethodHandle[]{ + C.ACCESSORS[0], + wrongReceiverGetter, + })); + var extraArgGetter = MethodHandles.dropArguments(C.ACCESSORS[1], 1, int.class); + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, "x;y", + new MethodHandle[]{ + C.ACCESSORS[0], + extraArgGetter, + })); + var voidReturnGetter = MethodHandles.empty(MethodType.methodType(void.class, C.class)); + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, name, type, C.class, "x;y", + new MethodHandle[]{ + C.ACCESSORS[0], + voidReturnGetter, + })); + } + + // same field name and type as C::y, for wrongReceiverGetter + private int y; + // Based on the ObjectMethods internal implementation private static int hashCombiner(int x, int y) { return x*31 + y;