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 }