/* * Copyright (c) 2016, 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. */ package org.graalvm.compiler.replacements.classfile; import static org.graalvm.compiler.core.common.util.ModuleAPI.getModule; import static org.graalvm.compiler.core.common.util.ModuleAPI.getResourceAsStream; import static org.graalvm.compiler.core.common.util.Util.JAVA_SPECIFICATION_VERSION; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.util.HashMap; import java.util.Map; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.bytecode.Bytecode; import org.graalvm.compiler.bytecode.BytecodeProvider; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; /** * A {@link BytecodeProvider} that provides bytecode properties of a {@link ResolvedJavaMethod} as * parsed from a class file. This avoids all {@linkplain Instrumentation instrumentation} and any * bytecode rewriting performed by the VM. * * This mechanism retrieves class files based on the name and {@link ClassLoader} of existing * {@link Class} instances. It bypasses all VM parsing and verification of the class file and * assumes the class files are well formed. As such, it should only be used for classes from a * trusted source such as the boot class (or module) path. * * A combination of {@link Class#forName(String)} and an existing {@link MetaAccessProvider} is used * to resolve constant pool references. This opens up the opportunity for linkage errors if the * referee is structurally changed through redefinition (e.g., a referred to method is renamed or * deleted). This will result in an appropriate {@link LinkageError} being thrown. The only way to * avoid this is to have a completely isolated {@code jdk.vm.ci.meta} implementation for parsing * snippet/intrinsic bytecodes. */ public final class ClassfileBytecodeProvider implements BytecodeProvider { private final ClassLoader loader; private final Map, Classfile> classfiles = new HashMap<>(); private final Map> classes = new HashMap<>(); final MetaAccessProvider metaAccess; final SnippetReflectionProvider snippetReflection; public ClassfileBytecodeProvider(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection) { this.metaAccess = metaAccess; this.snippetReflection = snippetReflection; ClassLoader cl = getClass().getClassLoader(); this.loader = cl == null ? ClassLoader.getSystemClassLoader() : cl; } @Override public Bytecode getBytecode(ResolvedJavaMethod method) { Classfile classfile = getClassfile(resolveToClass(method.getDeclaringClass().getName())); return classfile.getCode(method.getName(), method.getSignature().toMethodDescriptor()); } @Override public boolean supportsInvokedynamic() { return false; } @Override public boolean shouldRecordMethodDependencies() { return false; } private static InputStream getClassfileAsStream(Class c) { String classfilePath = c.getName().replace('.', '/') + ".class"; if (JAVA_SPECIFICATION_VERSION >= 9) { Object module = getModule.invoke(c); return getResourceAsStream.invoke(module, classfilePath); } else { ClassLoader cl = c.getClassLoader(); if (cl == null) { return ClassLoader.getSystemResourceAsStream(classfilePath); } return cl.getResourceAsStream(classfilePath); } } /** * Gets a {@link Classfile} created by parsing the class file bytes for {@code c}. * * @throws NoClassDefFoundError if the class file cannot be found */ private synchronized Classfile getClassfile(Class c) { assert !c.isPrimitive() && !c.isArray() : c; Classfile classfile = classfiles.get(c); if (classfile == null) { try { ResolvedJavaType type = metaAccess.lookupJavaType(c); InputStream in = getClassfileAsStream(c); if (in != null) { DataInputStream stream = new DataInputStream(in); classfile = new Classfile(type, stream, this); classfiles.put(c, classfile); return classfile; } throw new NoClassDefFoundError(c.getName()); } catch (IOException e) { throw (NoClassDefFoundError) new NoClassDefFoundError(c.getName()).initCause(e); } } return classfile; } synchronized Class resolveToClass(String descriptor) { Class c = classes.get(descriptor); if (c == null) { if (descriptor.length() == 1) { c = JavaKind.fromPrimitiveOrVoidTypeChar(descriptor.charAt(0)).toJavaClass(); } else { int dimensions = 0; while (descriptor.charAt(dimensions) == '[') { dimensions++; } String name; if (dimensions == 0 && descriptor.startsWith("L") && descriptor.endsWith(";")) { name = descriptor.substring(1, descriptor.length() - 1).replace('/', '.'); } else { name = descriptor.replace('/', '.'); } try { c = Class.forName(name, true, loader); classes.put(descriptor, c); } catch (ClassNotFoundException e) { throw new NoClassDefFoundError(descriptor); } } } return c; } static ResolvedJavaMethod findMethod(ResolvedJavaType type, String name, String descriptor, boolean isStatic) { if (isStatic && name.equals("")) { ResolvedJavaMethod method = type.getClassInitializer(); if (method != null) { return method; } } ResolvedJavaMethod[] methodsToSearch = name.equals("") ? type.getDeclaredConstructors() : type.getDeclaredMethods(); for (ResolvedJavaMethod method : methodsToSearch) { if (method.isStatic() == isStatic && method.getName().equals(name) && method.getSignature().toMethodDescriptor().equals(descriptor)) { return method; } } return null; } static ResolvedJavaField findField(ResolvedJavaType type, String name, String fieldType, boolean isStatic) { ResolvedJavaField[] fields = isStatic ? type.getStaticFields() : type.getInstanceFields(false); for (ResolvedJavaField field : fields) { if (field.getName().equals(name) && field.getType().getName().equals(fieldType)) { return field; } } return null; } }