1/* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23package org.graalvm.compiler.replacements.classfile; 24 25import static org.graalvm.compiler.serviceprovider.JDK9Method.getModule; 26import static org.graalvm.compiler.serviceprovider.JDK9Method.getResourceAsStream; 27 28import java.io.DataInputStream; 29import java.io.IOException; 30import java.io.InputStream; 31 32import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; 33import org.graalvm.compiler.bytecode.Bytecode; 34import org.graalvm.compiler.bytecode.BytecodeProvider; 35import org.graalvm.compiler.serviceprovider.JDK9Method; 36import org.graalvm.util.EconomicMap; 37import org.graalvm.util.Equivalence; 38 39import jdk.vm.ci.meta.JavaKind; 40import jdk.vm.ci.meta.MetaAccessProvider; 41import jdk.vm.ci.meta.ResolvedJavaField; 42import jdk.vm.ci.meta.ResolvedJavaMethod; 43import jdk.vm.ci.meta.ResolvedJavaType; 44 45/** 46 * A {@link BytecodeProvider} that provides bytecode properties of a {@link ResolvedJavaMethod} as 47 * parsed from a class file. This avoids all {@linkplain java.lang.instrument.Instrumentation 48 * instrumentation} and any bytecode rewriting performed by the VM. 49 * 50 * This mechanism retrieves class files based on the name and {@link ClassLoader} of existing 51 * {@link Class} instances. It bypasses all VM parsing and verification of the class file and 52 * assumes the class files are well formed. As such, it should only be used for classes from a 53 * trusted source such as the boot class (or module) path. 54 * 55 * A combination of {@link Class#forName(String)} and an existing {@link MetaAccessProvider} is used 56 * to resolve constant pool references. This opens up the opportunity for linkage errors if the 57 * referee is structurally changed through redefinition (e.g., a referred to method is renamed or 58 * deleted). This will result in an appropriate {@link LinkageError} being thrown. The only way to 59 * avoid this is to have a completely isolated {@code jdk.vm.ci.meta} implementation for parsing 60 * snippet/intrinsic bytecodes. 61 */ 62public final class ClassfileBytecodeProvider implements BytecodeProvider { 63 64 private final ClassLoader loader; 65 private final EconomicMap<Class<?>, Classfile> classfiles = EconomicMap.create(Equivalence.IDENTITY); 66 private final EconomicMap<String, Class<?>> classes = EconomicMap.create(); 67 private final EconomicMap<ResolvedJavaType, FieldsCache> fields = EconomicMap.create(); 68 private final EconomicMap<ResolvedJavaType, MethodsCache> methods = EconomicMap.create(); 69 final MetaAccessProvider metaAccess; 70 final SnippetReflectionProvider snippetReflection; 71 72 public ClassfileBytecodeProvider(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection) { 73 this.metaAccess = metaAccess; 74 this.snippetReflection = snippetReflection; 75 ClassLoader cl = getClass().getClassLoader(); 76 this.loader = cl == null ? ClassLoader.getSystemClassLoader() : cl; 77 } 78 79 public ClassfileBytecodeProvider(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, ClassLoader loader) { 80 this.metaAccess = metaAccess; 81 this.snippetReflection = snippetReflection; 82 this.loader = loader; 83 } 84 85 @Override 86 public Bytecode getBytecode(ResolvedJavaMethod method) { 87 Classfile classfile = getClassfile(resolveToClass(method.getDeclaringClass().getName())); 88 return classfile.getCode(method.getName(), method.getSignature().toMethodDescriptor()); 89 } 90 91 @Override 92 public boolean supportsInvokedynamic() { 93 return false; 94 } 95 96 @Override 97 public boolean shouldRecordMethodDependencies() { 98 return false; 99 } 100 101 private static InputStream getClassfileAsStream(Class<?> c) { 102 String classfilePath = c.getName().replace('.', '/') + ".class"; 103 if (JDK9Method.JAVA_SPECIFICATION_VERSION >= 9) { 104 Object module = getModule.invoke(c); 105 return getResourceAsStream.invoke(module, classfilePath); 106 } else { 107 ClassLoader cl = c.getClassLoader(); 108 if (cl == null) { 109 return ClassLoader.getSystemResourceAsStream(classfilePath); 110 } 111 return cl.getResourceAsStream(classfilePath); 112 } 113 } 114 115 /** 116 * Gets a {@link Classfile} created by parsing the class file bytes for {@code c}. 117 * 118 * @throws NoClassDefFoundError if the class file cannot be found 119 */ 120 private synchronized Classfile getClassfile(Class<?> c) { 121 assert !c.isPrimitive() && !c.isArray() : c; 122 Classfile classfile = classfiles.get(c); 123 if (classfile == null) { 124 try { 125 ResolvedJavaType type = metaAccess.lookupJavaType(c); 126 InputStream in = getClassfileAsStream(c); 127 if (in != null) { 128 DataInputStream stream = new DataInputStream(in); 129 classfile = new Classfile(type, stream, this); 130 classfiles.put(c, classfile); 131 return classfile; 132 } 133 throw new NoClassDefFoundError(c.getName()); 134 } catch (IOException e) { 135 throw (NoClassDefFoundError) new NoClassDefFoundError(c.getName()).initCause(e); 136 } 137 } 138 return classfile; 139 } 140 141 synchronized Class<?> resolveToClass(String descriptor) { 142 Class<?> c = classes.get(descriptor); 143 if (c == null) { 144 if (descriptor.length() == 1) { 145 c = JavaKind.fromPrimitiveOrVoidTypeChar(descriptor.charAt(0)).toJavaClass(); 146 } else { 147 int dimensions = 0; 148 while (descriptor.charAt(dimensions) == '[') { 149 dimensions++; 150 } 151 String name; 152 if (dimensions == 0 && descriptor.startsWith("L") && descriptor.endsWith(";")) { 153 name = descriptor.substring(1, descriptor.length() - 1).replace('/', '.'); 154 } else { 155 name = descriptor.replace('/', '.'); 156 } 157 try { 158 c = Class.forName(name, true, loader); 159 classes.put(descriptor, c); 160 } catch (ClassNotFoundException e) { 161 throw new NoClassDefFoundError(descriptor); 162 } 163 } 164 } 165 return c; 166 } 167 168 /** 169 * Name and type of a field. 170 */ 171 static final class FieldKey { 172 final String name; 173 final String type; 174 175 FieldKey(String name, String type) { 176 this.name = name; 177 this.type = type; 178 } 179 180 @Override 181 public String toString() { 182 return name + ":" + type; 183 } 184 185 @Override 186 public boolean equals(Object obj) { 187 if (obj instanceof FieldKey) { 188 FieldKey that = (FieldKey) obj; 189 return that.name.equals(this.name) && that.type.equals(this.type); 190 } 191 return false; 192 } 193 194 @Override 195 public int hashCode() { 196 return name.hashCode() ^ type.hashCode(); 197 } 198 } 199 200 /** 201 * Name and descriptor of a method. 202 */ 203 static final class MethodKey { 204 final String name; 205 final String descriptor; 206 207 MethodKey(String name, String descriptor) { 208 this.name = name; 209 this.descriptor = descriptor; 210 } 211 212 @Override 213 public String toString() { 214 return name + ":" + descriptor; 215 } 216 217 @Override 218 public boolean equals(Object obj) { 219 if (obj instanceof MethodKey) { 220 MethodKey that = (MethodKey) obj; 221 return that.name.equals(this.name) && that.descriptor.equals(this.descriptor); 222 } 223 return false; 224 } 225 226 @Override 227 public int hashCode() { 228 return name.hashCode() ^ descriptor.hashCode(); 229 } 230 } 231 232 /** 233 * Method cache for a {@link ResolvedJavaType}. 234 */ 235 static final class MethodsCache { 236 237 volatile EconomicMap<MethodKey, ResolvedJavaMethod> constructors; 238 volatile EconomicMap<MethodKey, ResolvedJavaMethod> methods; 239 240 ResolvedJavaMethod lookup(ResolvedJavaType type, String name, String descriptor) { 241 MethodKey key = new MethodKey(name, descriptor); 242 243 if (name.equals("<clinit>")) { 244 // No need to cache <clinit> as it will be looked up at most once 245 return type.getClassInitializer(); 246 } 247 if (!name.equals("<init>")) { 248 if (methods == null) { 249 // Racy initialization is safe since `methods` is volatile 250 methods = createMethodMap(type.getDeclaredMethods()); 251 } 252 253 return methods.get(key); 254 } else { 255 if (constructors == null) { 256 // Racy initialization is safe since instanceFields is volatile 257 constructors = createMethodMap(type.getDeclaredConstructors()); 258 } 259 return constructors.get(key); 260 } 261 } 262 263 private static EconomicMap<MethodKey, ResolvedJavaMethod> createMethodMap(ResolvedJavaMethod[] methodArray) { 264 EconomicMap<MethodKey, ResolvedJavaMethod> map = EconomicMap.create(); 265 for (ResolvedJavaMethod m : methodArray) { 266 map.put(new MethodKey(m.getName(), m.getSignature().toMethodDescriptor()), m); 267 } 268 return map; 269 } 270 } 271 272 /** 273 * Field cache for a {@link ResolvedJavaType}. 274 */ 275 static final class FieldsCache { 276 277 volatile EconomicMap<FieldKey, ResolvedJavaField> instanceFields; 278 volatile EconomicMap<FieldKey, ResolvedJavaField> staticFields; 279 280 ResolvedJavaField lookup(ResolvedJavaType type, String name, String fieldType, boolean isStatic) { 281 FieldKey key = new FieldKey(name, fieldType); 282 if (isStatic) { 283 if (staticFields == null) { 284 // Racy initialization is safe since staticFields is volatile 285 staticFields = createFieldMap(type.getStaticFields()); 286 } 287 return staticFields.get(key); 288 } else { 289 if (instanceFields == null) { 290 // Racy initialization is safe since instanceFields is volatile 291 instanceFields = createFieldMap(type.getInstanceFields(false)); 292 } 293 return instanceFields.get(key); 294 } 295 } 296 297 private static EconomicMap<FieldKey, ResolvedJavaField> createFieldMap(ResolvedJavaField[] fieldArray) { 298 EconomicMap<FieldKey, ResolvedJavaField> map = EconomicMap.create(); 299 for (ResolvedJavaField f : fieldArray) { 300 map.put(new FieldKey(f.getName(), f.getType().getName()), f); 301 } 302 return map; 303 } 304 } 305 306 /** 307 * Gets the methods cache for {@code type}. 308 * 309 * Synchronized since the cache is lazily created. 310 */ 311 private synchronized MethodsCache getMethods(ResolvedJavaType type) { 312 MethodsCache methodsCache = methods.get(type); 313 if (methodsCache == null) { 314 methodsCache = new MethodsCache(); 315 methods.put(type, methodsCache); 316 } 317 return methodsCache; 318 } 319 320 /** 321 * Gets the fields cache for {@code type}. 322 * 323 * Synchronized since the cache is lazily created. 324 */ 325 private synchronized FieldsCache getFields(ResolvedJavaType type) { 326 FieldsCache fieldsCache = fields.get(type); 327 if (fieldsCache == null) { 328 fieldsCache = new FieldsCache(); 329 fields.put(type, fieldsCache); 330 } 331 return fieldsCache; 332 } 333 334 ResolvedJavaField findField(ResolvedJavaType type, String name, String fieldType, boolean isStatic) { 335 return getFields(type).lookup(type, name, fieldType, isStatic); 336 } 337 338 ResolvedJavaMethod findMethod(ResolvedJavaType type, String name, String descriptor, boolean isStatic) { 339 ResolvedJavaMethod method = getMethods(type).lookup(type, name, descriptor); 340 if (method != null && method.isStatic() == isStatic) { 341 return method; 342 } 343 return null; 344 } 345} 346