1 /*
2  * Copyright (c) 2016, 2019, 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 
ClassfileBytecodeProvider(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection)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 
ClassfileBytecodeProvider(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, ClassLoader loader)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
getBytecode(ResolvedJavaMethod method)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
supportsInvokedynamic()91     public boolean supportsInvokedynamic() {
92         return false;
93     }
94 
95     @Override
shouldRecordMethodDependencies()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      */
getClassfile(Class<?> c)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 
resolveToClass(String descriptor)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 
FieldKey(String name, String type)161         FieldKey(String name, String type) {
162             this.name = name;
163             this.type = type;
164         }
165 
166         @Override
toString()167         public String toString() {
168             return name + ":" + type;
169         }
170 
171         @Override
equals(Object obj)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
hashCode()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 
MethodKey(String name, String descriptor)193         MethodKey(String name, String descriptor) {
194             this.name = name;
195             this.descriptor = descriptor;
196         }
197 
198         @Override
toString()199         public String toString() {
200             return name + ":" + descriptor;
201         }
202 
203         @Override
equals(Object obj)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
hashCode()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 
lookup(ResolvedJavaType type, String name, String descriptor)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 
createMethodMap(ResolvedJavaMethod[] methodArray)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 
lookup(ResolvedJavaType type, String name, String fieldType, boolean isStatic)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 
createFieldMap(ResolvedJavaField[] fieldArray)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      */
getMethods(ResolvedJavaType type)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      */
getFields(ResolvedJavaType type)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 
findField(ResolvedJavaType type, String name, String fieldType, boolean isStatic)320     ResolvedJavaField findField(ResolvedJavaType type, String name, String fieldType, boolean isStatic) {
321         return getFields(type).lookup(type, name, fieldType, isStatic);
322     }
323 
findMethod(ResolvedJavaType type, String name, String descriptor, boolean isStatic)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 }
332