From a886aa85be5bec335de1be828b28318a326170e9 Mon Sep 17 00:00:00 2001 From: iklam Date: Fri, 17 Oct 2025 14:42:31 -0700 Subject: [PATCH 1/3] 8368199: Add @AOTSafeClassInitializer to jdk.internal.access.SharedSecrets --- src/hotspot/share/cds/cdsHeapVerifier.cpp | 7 ++++--- .../share/classes/java/lang/invoke/MethodHandleImpl.java | 6 ------ .../share/classes/java/lang/module/ModuleDescriptor.java | 6 ------ src/java.base/share/classes/java/lang/ref/Reference.java | 6 ------ src/java.base/share/classes/java/net/URI.java | 6 ------ src/java.base/share/classes/java/net/URL.java | 6 ------ .../share/classes/jdk/internal/access/SharedSecrets.java | 4 ++++ 7 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 3f72a7c68729f..ff6c606edc1ab 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -181,9 +181,10 @@ class CDSHeapVerifier::CheckStaticFields : public FieldClosure { return; } - if (fd->signature()->equals("Ljdk/internal/access/JavaLangAccess;")) { - // A few classes have static fields that point to SharedSecrets.getJavaLangAccess(). - // This object carries no state and we can create a new one in the production run. + if (fd->signature()->starts_with("Ljdk/internal/access/") && + fd->signature()->ends_with("Access;")) { + // The jdk/internal/access/*Access classes carry no states so they can be safely + // cached. return; } oop static_obj_field = _ik->java_mirror()->obj_field(fd->offset()); diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index cb1bf8294d251..5b8a4478be579 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -33,7 +33,6 @@ import jdk.internal.foreign.abi.NativeEntryPoint; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; -import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Hidden; @@ -1536,11 +1535,6 @@ private static NamedFunction createFunction(byte func) { } static { - runtimeSetup(); - } - - @AOTRuntimeSetup - private static void runtimeSetup() { SharedSecrets.setJavaLangInvokeAccess(new JavaLangInvokeAccess() { @Override public Class getDeclaringClass(Object rmname) { diff --git a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java index d5f4470d9c9c8..4f4e35c672719 100644 --- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java +++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java @@ -54,7 +54,6 @@ import jdk.internal.module.Checks; import jdk.internal.module.ModuleInfo; -import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; @@ -2668,11 +2667,6 @@ private static > long modsValue(Set set) { } static { - runtimeSetup(); - } - - @AOTRuntimeSetup - private static void runtimeSetup() { /** * Setup the shared secret to allow code in other packages access * private package methods in java.lang.module. diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index 43d9f414b3c28..88bdb99dfd606 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -25,7 +25,6 @@ package java.lang.ref; -import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -291,11 +290,6 @@ static void startReferenceHandlerThread(ThreadGroup tg) { } static { - runtimeSetup(); - } - - @AOTRuntimeSetup - private static void runtimeSetup() { // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index d130dc3b46019..d568ab1c114b8 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -43,7 +43,6 @@ import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.Exceptions; -import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import sun.nio.cs.UTF_8; @@ -3731,11 +3730,6 @@ private int scanHexSeq(int start, int n) } static { - runtimeSetup(); - } - - @AOTRuntimeSetup - private static void runtimeSetup() { SharedSecrets.setJavaNetUriAccess( new JavaNetUriAccess() { public URI create(String scheme, String path) { diff --git a/src/java.base/share/classes/java/net/URL.java b/src/java.base/share/classes/java/net/URL.java index c82236b5b85dc..1e86f41fd3f09 100644 --- a/src/java.base/share/classes/java/net/URL.java +++ b/src/java.base/share/classes/java/net/URL.java @@ -42,7 +42,6 @@ import jdk.internal.access.JavaNetURLAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.VM; -import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import sun.net.util.IPAddressUtil; import static jdk.internal.util.Exceptions.filterNonSocketInfo; @@ -1747,11 +1746,6 @@ private void setSerializedHashCode(int hc) { } static { - runtimeSetup(); - } - - @AOTRuntimeSetup - private static void runtimeSetup() { SharedSecrets.setJavaNetURLAccess( new JavaNetURLAccess() { @Override diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index e20a1e7742385..4b17f5ae589f2 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -25,6 +25,7 @@ package jdk.internal.access; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; import javax.crypto.SealedObject; @@ -60,6 +61,9 @@ interface and provides the ability to call package-private methods * */ +// Static fields in this class are stateless, so the values initialized in the +// AOT assembly phase can be safely cached. +@AOTSafeClassInitializer public class SharedSecrets { // This field is not necessarily stable private static JavaAWTFontAccess javaAWTFontAccess; From c6ea93c319c551b5d3d8a902fed2da021937a946 Mon Sep 17 00:00:00 2001 From: iklam Date: Mon, 27 Oct 2025 20:56:30 -0700 Subject: [PATCH 2/3] Added StatelessOopsFinder --- src/hotspot/share/cds/cdsHeapVerifier.cpp | 106 +++++++++++++++++- src/hotspot/share/cds/cdsHeapVerifier.hpp | 18 +++ test/hotspot/jtreg/TEST.groups | 1 + .../appcds/aotCache/SharedSecretsTest.java | 92 +++++++++++++++ 4 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/SharedSecretsTest.java diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index ff6c606edc1ab..59b07190c093b 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -28,6 +28,8 @@ #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmSymbols.hpp" #include "logging/log.hpp" @@ -153,9 +155,103 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) # undef ADD_EXCL + if (CDSConfig::is_initing_classes_at_dump_time()) { + add_shared_secret_accessors(); + } ClassLoaderDataGraph::classes_do(this); } +// We allow only "stateless" accessors in the SharedSecrets class to be AOT-initialized, for example, +// in the following pattern: +// +// class URL { +// static { +// SharedSecrets.setJavaNetURLAccess( +// new JavaNetURLAccess() { ... }); +// } +// +// This initializes the field SharedSecrets::javaNetUriAccess, whose type (the inner case in the +// above example) has no fields (static or otherwise) and is not a hidden class, so it cannot possibly +// capture any transient state from the assembly phase that might become invalid in the production run. +// +class CDSHeapVerifier::SharedSecretsAccessorFinder : public FieldClosure { + CDSHeapVerifier* _verifier; + InstanceKlass* _ik; +public: + SharedSecretsAccessorFinder(CDSHeapVerifier* verifier, InstanceKlass* ik) + : _verifier(verifier), _ik(ik) {} + + void do_field(fieldDescriptor* fd) { + if (fd->field_type() == T_OBJECT) { + oop static_obj_field = _ik->java_mirror()->obj_field(fd->offset()); + if (static_obj_field != nullptr) { + Klass* field_type = static_obj_field->klass(); + + if (!field_type->is_instance_klass()) { + ResourceMark rm; + log_error(aot, heap)("jdk.internal.access.SharedSecrets::%s must not be an array", + fd->name()->as_C_string()); + AOTMetaspace::unrecoverable_writing_error(); + } + + InstanceKlass* field_type_ik = InstanceKlass::cast(field_type); + if (has_any_fields(field_type_ik) || field_type_ik->is_hidden()) { + // If field_type_ik is a hidden class, the accessor is probably initialized using a + // Lambda, which may contain transient states. + ResourceMark rm; + log_error(aot, heap)("jdk.internal.access.SharedSecrets::%s (%s) must be stateless", + fd->name()->as_C_string(), field_type_ik->external_name()); + AOTMetaspace::unrecoverable_writing_error(); + } + + _verifier->add_shared_secret_accessor(static_obj_field); + } + } + } + + // Does k (or any of its supertypes) have at least one (static or non-static) field? + static bool has_any_fields(InstanceKlass* k) { + if (k->static_field_size() != 0 || k->nonstatic_field_size() != 0) { + return true; + } + + if (k->super() != nullptr && has_any_fields(k->super())) { + return true; + } + + Array* interfaces = k->local_interfaces(); + int num_interfaces = interfaces->length(); + for (int index = 0; index < num_interfaces; index++) { + if (has_any_fields(interfaces->at(index))) { + return true; + } + } + + return false; + } +}; + +// This function is for allowing the following pattern in the core libraries: +// +// public class URLClassPath { +// private static final JavaNetURLAccess JNUA = SharedSecrets.getJavaNetURLAccess(); +// +// SharedSecrets::javaNetUriAccess has no states so it can be safely AOT-initialized. During +// the production run, even if URLClassPath. is re-executed, it will get back the same +// instance of javaNetUriAccess as it did during the assembly phase. +// +// Note: this will forbid complex accessors such as SharedSecrets::javaObjectInputFilterAccess +// to be initialized during the AOT assembly phase. +void CDSHeapVerifier::add_shared_secret_accessors() { + TempNewSymbol klass_name = SymbolTable::new_symbol("jdk/internal/access/SharedSecrets"); + InstanceKlass* ik = SystemDictionary::find_instance_klass(Thread::current(), klass_name, + Handle()); + assert(ik != nullptr, "must have been loaded"); + + SharedSecretsAccessorFinder finder(this, ik); + ik->do_local_static_fields(&finder); +} + CDSHeapVerifier::~CDSHeapVerifier() { if (_problems > 0) { log_error(aot, heap)("Scanned %d objects. Found %d case(s) where " @@ -181,14 +277,12 @@ class CDSHeapVerifier::CheckStaticFields : public FieldClosure { return; } - if (fd->signature()->starts_with("Ljdk/internal/access/") && - fd->signature()->ends_with("Access;")) { - // The jdk/internal/access/*Access classes carry no states so they can be safely - // cached. - return; - } oop static_obj_field = _ik->java_mirror()->obj_field(fd->offset()); if (static_obj_field != nullptr) { + if (_verifier->is_shared_secret_accessor(static_obj_field)) { + return; + } + Klass* field_type = static_obj_field->klass(); if (_exclusions != nullptr) { for (const char** p = _exclusions; *p != nullptr; p++) { diff --git a/src/hotspot/share/cds/cdsHeapVerifier.hpp b/src/hotspot/share/cds/cdsHeapVerifier.hpp index 1cc03975c5cda..88ef13c9e90c8 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.hpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.hpp @@ -38,6 +38,7 @@ class Symbol; class CDSHeapVerifier : public KlassClosure { class CheckStaticFields; + class SharedSecretsAccessorFinder; class TraceFields; int _archived_objs; @@ -55,6 +56,7 @@ class CDSHeapVerifier : public KlassClosure { HeapShared::oop_hash> _table; GrowableArray _exclusions; + GrowableArray _shared_secret_accessors; void add_exclusion(const char** excl) { _exclusions.append(excl); @@ -70,6 +72,22 @@ class CDSHeapVerifier : public KlassClosure { } return nullptr; } + + void add_shared_secret_accessors(); + + void add_shared_secret_accessor(oop obj) { + _shared_secret_accessors.append(obj); + } + + bool is_shared_secret_accessor(oop obj) { + for (int i = 0; i < _shared_secret_accessors.length(); i++) { + if (_shared_secret_accessors.at(i) == obj) { + return true; + } + } + return false; + } + static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* p); CDSHeapVerifier(); diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 18f27a9662818..2f58670b90e41 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -563,6 +563,7 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java \ -runtime/cds/appcds/resolvedConstants/ResolvedConstants.java \ -runtime/cds/appcds/RewriteBytecodesTest.java \ + -runtime/cds/appcds/SignedJar.java \ -runtime/cds/appcds/SpecifySysLoaderProp.java \ -runtime/cds/appcds/StaticArchiveWithLambda.java \ -runtime/cds/appcds/TestEpsilonGCWithCDS.java \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SharedSecretsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SharedSecretsTest.java new file mode 100644 index 0000000000000..5d6e909b75156 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/SharedSecretsTest.java @@ -0,0 +1,92 @@ +/* + * 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. + * + */ + +/* + * @test + * @summary Try to AOT-initialize SharedSecrets accessors that are not allowed. + * @bug 8368199 + * @requires vm.cds.supports.aot.class.linking + * @requires vm.debug + * @library /test/lib + * @build SharedSecretsTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar MyTestApp + * @run driver SharedSecretsTest AOT + */ + +import java.io.ObjectInputFilter; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class SharedSecretsTest { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "MyTestApp"; + + public static void main(String[] args) throws Exception { + Tester t = new Tester(mainClass); + t.setCheckExitValue(false); + t.runAOTAssemblyWorkflow(); + } + + static class Tester extends CDSAppTester { + public Tester(String name) { + super(name); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { "-XX:AOTInitTestClass=" + mainClass}; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch("jdk.internal.access.SharedSecrets::javaObjectInputFilterAccess .* must be stateless"); + out.shouldNotHaveExitValue(0); + } else { + out.shouldHaveExitValue(0); + } + } + } +} + +// We use -XX:AOTInitTestClass to force this class to be AOT-initialized in the assembly phase. It will +// cause the SharedSecrets::javaObjectInputFilterAccess to be initialized, which is not allowed. +class MyTestApp { + static Object foo = ObjectInputFilter.Config.createFilter(""); + public static void main(String args[]) { + } +} From 3121fd113d8c494a11d3cde4f5748bd1c87c632c Mon Sep 17 00:00:00 2001 From: iklam Date: Tue, 28 Oct 2025 11:46:50 -0700 Subject: [PATCH 3/3] Updated comments about @AOTSafeClassInitializer in SharedSecrets.java --- .../jdk/internal/access/SharedSecrets.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index 4b17f5ae589f2..b0a71529fa7b7 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -59,10 +59,19 @@ interface and provides the ability to call package-private methods * increased complexity and lack of sustainability. * Use this only as a last resort! * + * + *

Notes on the @AOTSafeClassInitializer annotation: + * + *

All static fields in SharedSecrets that are initialized in the AOT + * assembly phase must be stateless (as checked by the HotSpot C++ class + * CDSHeapVerifier::SharedSecretsAccessorFinder) so they can be safely + * stored in the AOT cache. + * + *

Static fields such as javaObjectInputFilterAccess point to a Lambda + * which is not stateless. The AOT assembly phase must not execute any Java + * code that would lead to the initialization of such fields, or else the AOT + * cache creation will fail. */ - -// Static fields in this class are stateless, so the values initialized in the -// AOT assembly phase can be safely cached. @AOTSafeClassInitializer public class SharedSecrets { // This field is not necessarily stable