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