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