diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index b253a0abaeb3..1aa7b2f8b037 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -703,6 +703,10 @@ public void rescanRoot(Field reflectionField, ScanReason rescanReason) { }); } + public void rescanField(Object receiver, Field reflectionField, ScanReason reason) { + rescanField(receiver, metaAccess.lookupJavaField(reflectionField), reason); + } + /** * Trigger rescanning of an instance field. If the receiver value or field value were not * scanned before they will first be scanned and added to the shadow heap, then the value will @@ -711,11 +715,10 @@ public void rescanRoot(Field reflectionField, ScanReason rescanReason) { * type ({@code Object[]}, {{@link Collection}, {@link Map} or {@link EconomicMap}} then its * elements will be rescanned too. */ - public void rescanField(Object receiver, Field reflectionField, ScanReason reason) { + public void rescanField(Object receiver, AnalysisField field, ScanReason reason) { maybeRunInExecutor(unused -> { - AnalysisType type = metaAccess.lookupJavaType(reflectionField.getDeclaringClass()); + AnalysisType type = field.getType(); if (type.isReachable()) { - AnalysisField field = metaAccess.lookupJavaField(reflectionField); assert !field.isStatic() : field; if (!field.isReachable()) { return; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/macho/MachORelocationElement.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/macho/MachORelocationElement.java index 1c28b7bc9eac..b13babf22d8f 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/macho/MachORelocationElement.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/macho/MachORelocationElement.java @@ -30,8 +30,6 @@ import java.util.Set; import java.util.TreeMap; -import jdk.graal.compiler.core.common.NumUtil; - import com.oracle.objectfile.BuildDependency; import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; @@ -45,6 +43,8 @@ import com.oracle.objectfile.io.OutputAssembler; import com.oracle.objectfile.macho.MachOObjectFile.MachOSection; import com.oracle.objectfile.macho.MachOObjectFile.Segment64Command; + +import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.debug.GraalError; class MachORelocationElement extends MachOObjectFile.LinkEditElement { @@ -268,7 +268,9 @@ private MachORelocationInfo(MachORelocationElement containingElement, MachOSecti // FIXME: also allow section numbers here, for non-extern symbols // FIXME: encode R_ABS symbol number this.sym = symtab.getSymbol(symbolName); - assert this.sym != null : "could not find symbol " + symbolName; + if (this.sym == null) { + throw new IllegalArgumentException("Could not find symbol " + symbolName); + } // if the symbol is defined in the same file, i.e. locally, we have a target section assert !asLocalReloc || this.sym.isDefined(); this.targetSection = asLocalReloc ? (MachOSection) this.sym.getDefinedSection() : null; diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java index 39b9146b4192..0726a7275d8e 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AArch64InterpreterStubSection.java @@ -43,6 +43,7 @@ import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; import jdk.graal.compiler.asm.Assembler; @@ -50,7 +51,6 @@ import jdk.graal.compiler.asm.aarch64.AArch64MacroAssembler; import jdk.graal.compiler.core.common.LIRKind; import jdk.vm.ci.code.Register; -import jdk.vm.ci.meta.ResolvedJavaMethod; public class AArch64InterpreterStubSection extends InterpreterStubSection { public AArch64InterpreterStubSection() { @@ -159,7 +159,7 @@ private void recordEnterStubForPatching(Assembler.CodeAnnotation a) { @Platforms(Platform.HOSTED_ONLY.class) @Override - protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub) { + protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, HostedMethod enterStub) { pltBuffer.markRelocationSite(resolverPatchOffset, AARCH64_R_AARCH64_ADR_PREL_PG_HI21, NativeImage.localSymbolNameForMethod(enterStub), 0); pltBuffer.markRelocationSite(resolverPatchOffset + 4, AARCH64_R_AARCH64_ADD_ABS_LO12_NC, NativeImage.localSymbolNameForMethod(enterStub), 0); } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java index a4fdae83e9ec..5d4f3b3cda1e 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/AMD64InterpreterStubSection.java @@ -40,6 +40,7 @@ import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.image.NativeImage; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; import jdk.graal.compiler.asm.Assembler; @@ -48,7 +49,6 @@ import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; import jdk.graal.compiler.core.common.LIRKind; import jdk.graal.compiler.core.common.NumUtil; -import jdk.vm.ci.meta.ResolvedJavaMethod; public class AMD64InterpreterStubSection extends InterpreterStubSection { public AMD64InterpreterStubSection() { @@ -144,7 +144,7 @@ private void recordEnterStubForPatching(Assembler.CodeAnnotation a) { @Platforms(Platform.HOSTED_ONLY.class) @Override - protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub) { + protected void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, HostedMethod enterStub) { pltBuffer.markRelocationSite(resolverPatchOffset, resolverPatchRelocationKind, NativeImage.localSymbolNameForMethod(enterStub), resolverKindAddend); } } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java index c32a6cf0782a..9c8b81a1515a 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/DebuggerFeature.java @@ -36,9 +36,6 @@ import static com.oracle.svm.interpreter.metadata.InterpreterUniverseImpl.toHexString; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; @@ -67,8 +64,8 @@ import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.heap.ImageHeapConstant; -import com.oracle.svm.util.OriginalClassProvider; import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.BuildArtifacts; @@ -111,7 +108,7 @@ import com.oracle.svm.interpreter.metadata.ReferenceConstant; import com.oracle.svm.interpreter.metadata.serialization.SerializationContext; import com.oracle.svm.interpreter.metadata.serialization.Serializers; -import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.JVMCIReflectionUtil; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.core.common.SuppressFBWarnings; @@ -130,6 +127,7 @@ import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.UnresolvedJavaMethod; /** @@ -144,9 +142,9 @@ @Platforms(Platform.HOSTED_ONLY.class) @AutomaticallyRegisteredFeature public class DebuggerFeature implements InternalFeature { - private Method enterInterpreterMethod; + private AnalysisMethod enterInterpreterMethod; private InterpreterStubTable enterStubTable = null; - private final List> classesUsedByInterpreter = new ArrayList<>(); + private final List classesUsedByInterpreter = new ArrayList<>(); private Set methodsProcessedDuringAnalysis; private InvocationPlugins invocationPlugins; private static final String SYNTHETIC_ASSERTIONS_DISABLED_FIELD_NAME = "$assertionsDisabled"; @@ -165,9 +163,9 @@ public List> getRequiredFeatures() { SymbolsFeature.class); } - private static Class getArgumentClass(GraphBuilderContext b, ResolvedJavaMethod targetMethod, int parameterIndex, ValueNode arg) { + private static ResolvedJavaType getArgumentType(GraphBuilderContext b, ResolvedJavaMethod targetMethod, int parameterIndex, ValueNode arg) { SubstrateGraphBuilderPlugins.checkParameterUsage(arg.isConstant(), b, targetMethod, parameterIndex, "parameter is not a compile time constant"); - return OriginalClassProvider.getJavaClass(b.getConstantReflection().asJavaType(arg.asJavaConstant())); + return b.getConstantReflection().asJavaType(arg.asJavaConstant()); } @Override @@ -178,9 +176,9 @@ public void registerInvocationPlugins(Providers providers, GraphBuilderConfigura r.register(new InvocationPlugin.RequiredInvocationPlugin("markKlass", Class.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg1) { - Class targetKlass = getArgumentClass(b, targetMethod, 1, arg1); - InterpreterUtil.log("[invocation plugin] Adding %s", targetKlass); - classesUsedByInterpreter.add(targetKlass); + ResolvedJavaType targetType = getArgumentType(b, targetMethod, 1, arg1); + InterpreterUtil.log("[invocation plugin] Adding %s", targetType.getUnqualifiedName()); + classesUsedByInterpreter.add(targetType); /* no-op in compiled code */ return true; @@ -198,33 +196,32 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + AnalysisMetaAccess metaAccess = accessImpl.getMetaAccess(); - try { - enterInterpreterMethod = InterpreterStubSection.class.getMethod("enterMethodInterpreterStub", int.class, Pointer.class); - accessImpl.registerAsRoot(enterInterpreterMethod, true, "stub for interpreter"); + AnalysisType aInterpreterStubSection = metaAccess.lookupJavaType(InterpreterStubSection.class); + enterInterpreterMethod = (AnalysisMethod) JVMCIReflectionUtil.getDeclaredMethod(metaAccess, aInterpreterStubSection, "enterMethodInterpreterStub", int.class, Pointer.class); + accessImpl.registerAsRoot(enterInterpreterMethod, true, "stub for interpreter"); - // Holds references that must be kept alive in the image heap. - access.registerAsAccessed(DebuggerSupport.class.getDeclaredField("referencesInImage")); - access.registerAsAccessed(DebuggerSupport.class.getDeclaredField("methodPointersInImage")); + // Holds references that must be kept alive in the image heap. + AnalysisType aDebuggerSupport = metaAccess.lookupJavaType(DebuggerSupport.class); + accessImpl.registerAsAccessed((AnalysisField) JVMCIReflectionUtil.getDeclaredField(aDebuggerSupport, "referencesInImage"), + "Holds references that must be kept alive in the image heap."); + accessImpl.registerAsAccessed((AnalysisField) JVMCIReflectionUtil.getDeclaredField(aDebuggerSupport, "methodPointersInImage"), + "Holds references that must be kept alive in the image heap."); - accessImpl.registerAsRoot(System.class.getDeclaredMethod("arraycopy", Object.class, int.class, Object.class, int.class, int.class), true, - "Allow interpreting methods that call System.arraycopy"); - } catch (NoSuchMethodException | NoSuchFieldException e) { - throw VMError.shouldNotReachHere(e); - } + AnalysisType aSystem = metaAccess.lookupJavaType(System.class); + accessImpl.registerAsRoot((AnalysisMethod) JVMCIReflectionUtil.getDeclaredMethod(metaAccess, aSystem, "arraycopy", Object.class, int.class, Object.class, int.class, int.class), + true, "Allow interpreting methods that call System.arraycopy"); registerStringConcatenation(accessImpl); // GR-53734: Known issues around reachability - try { - // JDK code introduced a new optional intrinsic: - // https://github.com/openjdk/jdk22u/commit/a4e9168bab1c2872ce2dbc7971a45c259270271f - // consider DualPivotQuicksort.java:268, int.class is not needed if the sort helper - // is inlined, therefore it's not needed. Still needed for interpreter execution. - access.registerAsAccessed(Integer.class.getField("TYPE")); - } catch (NoSuchFieldException e) { - throw VMError.shouldNotReachHere(e); - } + // JDK code introduced a new optional intrinsic: + // https://github.com/openjdk/jdk22u/commit/a4e9168bab1c2872ce2dbc7971a45c259270271f + // consider DualPivotQuicksort.java:268, int.class is not needed if the sort helper + // is inlined, therefore it's not needed. Still needed for interpreter execution. + AnalysisType aInteger = metaAccess.lookupJavaType(Integer.class); + accessImpl.registerAsAccessed((AnalysisField) JVMCIReflectionUtil.getDeclaredField(aInteger, "TYPE"), "Read by the interpreter"); methodsProcessedDuringAnalysis = new HashSet<>(); @@ -247,20 +244,18 @@ private static void registerStringConcatenation(FeatureImpl.BeforeAnalysisAccess * These registrations enable the interpreter to "interpret" StringBuilder-based String * concatenation optimized away by the compiler. */ - try { - List appendMethods = Arrays.stream(StringBuilder.class.getDeclaredMethods()) - .filter(m -> "append".equals(m.getName())) - .collect(Collectors.toList()); - for (Method m : appendMethods) { - accessImpl.registerAsRoot(m, false, "string concat in interpreter"); - } - for (Constructor c : StringBuilder.class.getDeclaredConstructors()) { - accessImpl.registerAsRoot(c, true, "string concat in interpreter"); - } - accessImpl.registerAsRoot(StringBuilder.class.getConstructor(), true, "string concat in interpreter"); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + AnalysisMetaAccess metaAccess = accessImpl.getMetaAccess(); + AnalysisType aStringBuilder = metaAccess.lookupJavaType(StringBuilder.class); + List appendMethods = Arrays.stream(aStringBuilder.getDeclaredMethods(false)) + .filter(m -> "append".equals(m.getName())).toList(); + for (AnalysisMethod m : appendMethods) { + accessImpl.registerAsRoot(m, false, "string concat in interpreter"); + } + for (AnalysisMethod c : aStringBuilder.getDeclaredConstructors(false)) { + accessImpl.registerAsRoot(c, true, "string concat in interpreter"); } + AnalysisMethod aMethod = (AnalysisMethod) JVMCIReflectionUtil.getDeclaredConstructor(metaAccess, aStringBuilder); + accessImpl.registerAsRoot(aMethod, true, "string concat in interpreter"); } static boolean isReachable(AnalysisMethod m) { @@ -299,13 +294,13 @@ public void duringAnalysis(DuringAnalysisAccess access) { if (!classesUsedByInterpreter.isEmpty()) { access.requireAnalysisIteration(); - for (Class k : classesUsedByInterpreter) { - accessImpl.registerAsUsed(k); - Arrays.stream(k.getDeclaredMethods()).filter(m -> m.getName().startsWith("test")).forEach(m -> { - AnalysisMethod aMethod = accessImpl.getMetaAccess().lookupJavaMethod(m); + for (ResolvedJavaType k : classesUsedByInterpreter) { + AnalysisType aType = k instanceof AnalysisType analysisType ? analysisType : accessImpl.getUniverse().lookup(k); + accessImpl.registerAsUsed(aType, "used by interpreter"); + Arrays.stream(aType.getDeclaredMethods(false)).filter(m -> m.getName().startsWith("test")).forEach(aMethod -> { VMError.guarantee(!aMethod.isConstructor()); accessImpl.registerAsRoot(aMethod, aMethod.isConstructor(), "reached due to interpreter directive"); - InterpreterUtil.log("[during analysis] Adding method %s", m); + InterpreterUtil.log("[during analysis] Adding method %s", aMethod); }); } classesUsedByInterpreter.clear(); @@ -440,7 +435,7 @@ public void beforeCompilation(BeforeCompilationAccess access) { FeatureImpl.BeforeCompilationAccessImpl accessImpl = (FeatureImpl.BeforeCompilationAccessImpl) access; HostedUniverse hUniverse = accessImpl.getUniverse(); HostedMetaAccess hMetaAccess = accessImpl.getMetaAccess(); - MetaAccessProvider aMetaAccess = hMetaAccess.getWrapped(); + AnalysisMetaAccess aMetaAccess = (AnalysisMetaAccess) hMetaAccess.getWrapped(); BuildTimeInterpreterUniverse iUniverse = BuildTimeInterpreterUniverse.singleton(); for (HostedType hType : hUniverse.getTypes()) { @@ -501,20 +496,18 @@ public void beforeCompilation(BeforeCompilationAccess access) { iUniverse.purgeUnreachable(hMetaAccess); - Field vtableHolderField = ReflectionUtil.lookupField(InterpreterResolvedObjectType.class, "vtableHolder"); + AnalysisField vtableHolderField = (AnalysisField) JVMCIReflectionUtil.getDeclaredField(aMetaAccess.lookupJavaType(InterpreterResolvedObjectType.class), "vtableHolder"); ScanReason reason = new OtherReason("Manual rescan triggered before compilation from " + DebuggerFeature.class); for (HostedType hostedType : hUniverse.getTypes()) { iUniverse.mirrorSVMVTable(hostedType, objectType -> accessImpl.getHeapScanner().rescanField(objectType, vtableHolderField, reason)); } // Allow methods that call System.arraycopy to be interpreted. - try { - HostedMethod arraycopy = hMetaAccess.lookupJavaMethod( - System.class.getDeclaredMethod("arraycopy", Object.class, int.class, Object.class, int.class, int.class)); - SubstrateCompilationDirectives.singleton().registerForcedCompilation(arraycopy); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } + + HostedType systemClass = hMetaAccess.lookupJavaType(System.class); + AnalysisMethod arraycopy = (AnalysisMethod) JVMCIReflectionUtil.getDeclaredMethod(aMetaAccess, + systemClass.getWrapped(), "arraycopy", Object.class, int.class, Object.class, int.class, int.class); + SubstrateCompilationDirectives.singleton().registerForcedCompilation(arraycopy); } @Override @@ -768,7 +761,7 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { InterpreterStubSection stubSection = ImageSingletons.lookup(InterpreterStubSection.class); - stubSection.markEnterStubPatch(accessImpl.getHostedMetaAccess().lookupJavaMethod(enterInterpreterMethod)); + stubSection.markEnterStubPatch(accessImpl.getHostedUniverse().lookup(enterInterpreterMethod)); enterStubTable.writeMetadataHashString(hashString.getBytes(StandardCharsets.UTF_8)); } diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java index 5dda37e1931c..771af737d50b 100644 --- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java +++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/InterpreterStubSection.java @@ -64,6 +64,7 @@ import com.oracle.svm.hosted.image.AbstractImage; import com.oracle.svm.hosted.image.NativeImage; import com.oracle.svm.hosted.image.RelocatableBuffer; +import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.interpreter.metadata.InterpreterResolvedJavaMethod; import com.oracle.svm.interpreter.metadata.InterpreterResolvedObjectType; import com.oracle.svm.interpreter.metadata.InterpreterUnresolvedSignature; @@ -77,7 +78,6 @@ import jdk.vm.ci.meta.AllocatableValue; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @InternalVMMethod @@ -177,12 +177,12 @@ protected void recordEnterTrampoline(InterpreterResolvedJavaMethod m, int positi public abstract int getVTableStubSize(); @Platforms(Platform.HOSTED_ONLY.class) - public void markEnterStubPatch(ResolvedJavaMethod enterStub) { + public void markEnterStubPatch(HostedMethod enterStub) { markEnterStubPatch(stubsBufferImpl, enterStub); } @Platforms(Platform.HOSTED_ONLY.class) - protected abstract void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, ResolvedJavaMethod enterStub); + protected abstract void markEnterStubPatch(ObjectFile.ProgbitsSectionImpl pltBuffer, HostedMethod enterStub); @Deoptimizer.DeoptStub(stubType = Deoptimizer.StubType.InterpreterEnterStub) @NeverInline("needs ABI boundary") diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java new file mode 100644 index 000000000000..d8986e4703f1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2025, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import jdk.graal.compiler.debug.GraalError; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * This class contains utility methods for commonly used reflection functionality based on JVMCI + * reflection (i.e. {@code jdk.vm.ci.meta}) as opposed to core reflection (i.e. + * {@code java.lang.reflect}). + */ +public final class JVMCIReflectionUtil { + + /** + * Gets the method declared by {@code declaringClass} named {@code name}. Like + * {@link Class#getDeclaredMethod(String, Class...)}, this does not consider super classes or + * interfaces. + * + * @param optional when {@code true}, an exception will be thrown if the method does not exist + * @param declaringClass the class in which to look up the method + * @param name the name of the method to look up + * @param parameterTypes the parameter types of the method to look up + * @return the resolved Java method object or {@code null} if no such method exits and + * {@code optional} is {@code true} + * @throws GraalError if multiple methods with the same name and signature exist in the + * declaring class + * @throws NoSuchMethodError if no such method exists and {@code optional} is {@code false} + */ + public static ResolvedJavaMethod getDeclaredMethod(boolean optional, ResolvedJavaType declaringClass, String name, ResolvedJavaType... parameterTypes) { + var result = findMethod(declaringClass, declaringClass.getDeclaredMethods(false), name, parameterTypes); + if (!optional && result == null) { + throw new NoSuchMethodError("No method found for %s.%s(%s)".formatted( + declaringClass.toClassName(), + name, + Arrays.stream(parameterTypes).map(ResolvedJavaType::toClassName).collect(Collectors.joining(", ")))); + } + return result; + } + + /** + * Shortcut for + * {@link #getDeclaredMethod(boolean, ResolvedJavaType, String, ResolvedJavaType...)} that + * converts the {@link Class} parameters to {@link ResolvedJavaType} using the provided + * {@link MetaAccessProvider}. + */ + public static ResolvedJavaMethod getDeclaredMethod(boolean optional, MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, String name, Class... parameterTypes) { + var parameterJavaTypes = Arrays.stream(parameterTypes).map(metaAccess::lookupJavaType).toArray(ResolvedJavaType[]::new); + return getDeclaredMethod(optional, declaringClass, name, parameterJavaTypes); + } + + /** + * Shortcut for + * {@link #getDeclaredMethod(boolean, MetaAccessProvider, ResolvedJavaType, String, Class...)} + * with {@code optional} set to {@code false}. + */ + public static ResolvedJavaMethod getDeclaredMethod(MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, String name, Class... parameterTypes) { + return getDeclaredMethod(false, metaAccess, declaringClass, name, parameterTypes); + } + + /** + * Gets the constructor declared by {@code declaringClass}. Like + * {@link Class#getDeclaredConstructor(Class...)}, this does not consider super classes. + * + * @param optional when {@code true}, an exception will be thrown if the method does not exist + * @param declaringClass the class in which to look up the constructor + * @param parameterTypes the parameter types of the constructor to look up + * @return the {@linkplain ResolvedJavaMethod resolved Java method} object representing the + * requested constructor or {@code null} if no such constructor exits and + * {@code optional} is {@code true} + * @throws GraalError if multiple constructors with the same name and signature exist in the + * declaring class + * @throws NoSuchMethodError if no such constructor exists and {@code optional} is {@code false} + */ + public static ResolvedJavaMethod getDeclaredConstructor(boolean optional, ResolvedJavaType declaringClass, ResolvedJavaType... parameterTypes) { + String name = ""; + var result = findMethod(declaringClass, declaringClass.getDeclaredConstructors(false), name, parameterTypes); + if (!optional && result == null) { + throw new NoSuchMethodError("No constructor found for %s.%s(%s)".formatted( + declaringClass.toClassName(), + name, + Arrays.stream(parameterTypes).map(ResolvedJavaType::toClassName).collect(Collectors.joining(", ")))); + } + return result; + } + + /** + * Shortcut for {@link #getDeclaredConstructor(boolean, ResolvedJavaType, ResolvedJavaType...)} + * that converts the {@link Class} parameters to {@link ResolvedJavaType} using the provided + * {@link MetaAccessProvider}. + */ + public static ResolvedJavaMethod getDeclaredConstructor(boolean optional, MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, Class... parameterTypes) { + var parameterJavaTypes = Arrays.stream(parameterTypes).map(metaAccess::lookupJavaType).toArray(ResolvedJavaType[]::new); + return getDeclaredConstructor(optional, declaringClass, parameterJavaTypes); + } + + /** + * Shortcut for + * {@link #getDeclaredConstructor(boolean, MetaAccessProvider, ResolvedJavaType, Class...)} with + * {@code optional} set to {@code false}. + */ + public static ResolvedJavaMethod getDeclaredConstructor(MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, Class... parameterTypes) { + return getDeclaredConstructor(false, metaAccess, declaringClass, parameterTypes); + } + + private static ResolvedJavaMethod findMethod(ResolvedJavaType declaringClass, ResolvedJavaMethod[] methods, String name, ResolvedJavaType... parameterTypes) { + ResolvedJavaMethod res = null; + for (ResolvedJavaMethod m : methods) { + if (!m.getName().equals(name)) { + continue; + } + // ignore receiver type for comparison + JavaType[] parameterList = m.getSignature().toParameterTypes(null); + if (!Arrays.equals(parameterTypes, parameterList)) { + continue; + } + if (res == null) { + res = m; + } else { + throw new GraalError("More than one method with signature %s in %s", res.format("%h(%p)"), declaringClass.toClassName()); + } + } + return res; + } + + /** + * Gets the field declared by {@code declaringClass} named {@code fieldName}. Like + * {@link Class#getDeclaredField(String)}, this does not consider super classes or interfaces. + * Unlike {@link Class#getDeclaredField(String)}, it does include + * {@linkplain ResolvedJavaField#isInternal() internal} fields. + * + * @param optional when {@code true}, an exception will be thrown if the method does not exist + * @param declaringClass the class in which to look up the field + * @param fieldName the name of the field to look up + * @return the resolved Java field object or {@code null} if no such field exists and + * {@code optional} is {@code true} + * @throws NoSuchFieldError if no field with the specified name exists in the declaring class + * and {@code optional} is {@code false} + * @throws GraalError if multiple fields with the same name exist in the declaring class + */ + public static ResolvedJavaField getDeclaredField(boolean optional, ResolvedJavaType declaringClass, String fieldName) { + ResolvedJavaField[][] allFields = {declaringClass.getStaticFields(), declaringClass.getInstanceFields(false)}; + ResolvedJavaField found = null; + for (ResolvedJavaField[] fields : allFields) { + for (ResolvedJavaField field : fields) { + if (field.getName().equals(fieldName)) { + if (found != null) { + throw new GraalError("More than one field named %s in %s", fieldName, declaringClass.toClassName()); + } + found = field; + } + } + } + if (!optional && found == null) { + throw new NoSuchFieldError(declaringClass.toClassName() + "." + fieldName); + } + return found; + } + + /** + * Shortcut for {@link #getDeclaredField(boolean, ResolvedJavaType, String)} with + * {@code optional} set to {@code false}. + */ + public static ResolvedJavaField getDeclaredField(ResolvedJavaType declaringClass, String fieldName) { + return getDeclaredField(false, declaringClass, fieldName); + } + + /** + * Returns a list containing all fields present within this type, including + * {@linkplain ResolvedJavaField#isInternal() internal} fields. The returned List is + * unmodifiable; calls to any mutator method will always cause + * {@code UnsupportedOperationException} to be thrown. + */ + public static List getAllFields(ResolvedJavaType declaringClass) { + ResolvedJavaField[] staticFields = declaringClass.getStaticFields(); + ResolvedJavaField[] instanceFields = declaringClass.getInstanceFields(false); + ResolvedJavaField[] allFields = new ResolvedJavaField[staticFields.length + instanceFields.length]; + System.arraycopy(staticFields, 0, allFields, 0, staticFields.length); + System.arraycopy(instanceFields, 0, allFields, staticFields.length, instanceFields.length); + return Collections.unmodifiableList(Arrays.asList(allFields)); + } +}