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  */
23 
24 
25 package org.graalvm.compiler.replacements.classfile;
26 
27 import java.io.ByteArrayInputStream;
28 import java.io.DataInputStream;
29 import java.io.IOException;
30 
31 import org.graalvm.compiler.bytecode.Bytecode;
32 import org.graalvm.compiler.bytecode.BytecodeProvider;
33 import org.graalvm.compiler.debug.GraalError;
34 import org.graalvm.compiler.replacements.classfile.ClassfileConstant.Utf8;
35 
36 import jdk.vm.ci.meta.ConstantPool;
37 import jdk.vm.ci.meta.DefaultProfilingInfo;
38 import jdk.vm.ci.meta.ExceptionHandler;
39 import jdk.vm.ci.meta.JavaType;
40 import jdk.vm.ci.meta.LineNumberTable;
41 import jdk.vm.ci.meta.Local;
42 import jdk.vm.ci.meta.LocalVariableTable;
43 import jdk.vm.ci.meta.ProfilingInfo;
44 import jdk.vm.ci.meta.ResolvedJavaMethod;
45 import jdk.vm.ci.meta.TriState;
46 
47 /**
48  * The bytecode properties of a method as parsed directly from a class file without any
49  * {@linkplain java.lang.instrument.Instrumentation instrumentation} or other rewriting performed on
50  * the bytecode.
51  */
52 public class ClassfileBytecode implements Bytecode {
53 
54     private static final int EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES = 8;
55     private static final int LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES = 4;
56     private static final int LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES = 10;
57 
58     private final ResolvedJavaMethod method;
59 
60     private final ClassfileConstantPool constantPool;
61 
62     private byte[] code;
63     private int maxLocals;
64     private int maxStack;
65 
66     private byte[] exceptionTableBytes;
67     private byte[] lineNumberTableBytes;
68     private byte[] localVariableTableBytes;
69 
ClassfileBytecode(ResolvedJavaMethod method, DataInputStream stream, ClassfileConstantPool constantPool)70     public ClassfileBytecode(ResolvedJavaMethod method, DataInputStream stream, ClassfileConstantPool constantPool) throws IOException {
71         this.method = method;
72         this.constantPool = constantPool;
73         maxStack = stream.readUnsignedShort();
74         maxLocals = stream.readUnsignedShort();
75         int codeLength = stream.readInt();
76         code = new byte[codeLength];
77         stream.readFully(code);
78         int exceptionTableLength = stream.readUnsignedShort();
79         exceptionTableBytes = new byte[exceptionTableLength * EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES];
80         stream.readFully(exceptionTableBytes);
81         readCodeAttributes(stream);
82     }
83 
84     @Override
getOrigin()85     public BytecodeProvider getOrigin() {
86         return constantPool.context;
87     }
88 
readCodeAttributes(DataInputStream stream)89     private void readCodeAttributes(DataInputStream stream) throws IOException {
90         int count = stream.readUnsignedShort();
91         for (int i = 0; i < count; i++) {
92             String attributeName = constantPool.get(Utf8.class, stream.readUnsignedShort()).value;
93             int attributeLength = stream.readInt();
94             switch (attributeName) {
95                 case "LocalVariableTable": {
96                     int length = stream.readUnsignedShort();
97                     localVariableTableBytes = new byte[length * LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES];
98                     stream.readFully(localVariableTableBytes);
99                     break;
100                 }
101                 case "LineNumberTable": {
102                     int length = stream.readUnsignedShort();
103                     lineNumberTableBytes = new byte[length * LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES];
104                     stream.readFully(lineNumberTableBytes);
105                     break;
106                 }
107                 default: {
108                     Classfile.skipFully(stream, attributeLength);
109                     break;
110                 }
111             }
112         }
113     }
114 
115     @Override
getCode()116     public byte[] getCode() {
117         return code;
118     }
119 
120     @Override
getCodeSize()121     public int getCodeSize() {
122         return code.length;
123     }
124 
125     @Override
getMaxLocals()126     public int getMaxLocals() {
127         return maxLocals;
128     }
129 
130     @Override
getMaxStackSize()131     public int getMaxStackSize() {
132         return maxStack;
133     }
134 
135     @Override
getExceptionHandlers()136     public ExceptionHandler[] getExceptionHandlers() {
137         if (exceptionTableBytes == null) {
138             return new ExceptionHandler[0];
139         }
140 
141         final int exceptionTableLength = exceptionTableBytes.length / EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES;
142         ExceptionHandler[] handlers = new ExceptionHandler[exceptionTableLength];
143         DataInputStream stream = new DataInputStream(new ByteArrayInputStream(exceptionTableBytes));
144 
145         for (int i = 0; i < exceptionTableLength; i++) {
146             try {
147                 final int startPc = stream.readUnsignedShort();
148                 final int endPc = stream.readUnsignedShort();
149                 final int handlerPc = stream.readUnsignedShort();
150                 int catchTypeIndex = stream.readUnsignedShort();
151 
152                 JavaType catchType;
153                 if (catchTypeIndex == 0) {
154                     catchType = null;
155                 } else {
156                     final int opcode = -1;  // opcode is not used
157                     catchType = constantPool.lookupType(catchTypeIndex, opcode);
158 
159                     // Check for Throwable which catches everything.
160                     if (catchType.toJavaName().equals("java.lang.Throwable")) {
161                         catchTypeIndex = 0;
162                         catchType = null;
163                     }
164                 }
165                 handlers[i] = new ExceptionHandler(startPc, endPc, handlerPc, catchTypeIndex, catchType);
166             } catch (IOException e) {
167                 throw new GraalError(e);
168             }
169         }
170 
171         return handlers;
172     }
173 
174     @Override
asStackTraceElement(int bci)175     public StackTraceElement asStackTraceElement(int bci) {
176         int line = getLineNumberTable().getLineNumber(bci);
177         return new StackTraceElement(method.getDeclaringClass().toJavaName(), method.getName(), method.getDeclaringClass().getSourceFileName(), line);
178     }
179 
180     @Override
getConstantPool()181     public ConstantPool getConstantPool() {
182         return constantPool;
183     }
184 
185     @Override
getLineNumberTable()186     public LineNumberTable getLineNumberTable() {
187         if (lineNumberTableBytes == null) {
188             return null;
189         }
190 
191         final int lineNumberTableLength = lineNumberTableBytes.length / LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES;
192         DataInputStream stream = new DataInputStream(new ByteArrayInputStream(lineNumberTableBytes));
193         int[] bci = new int[lineNumberTableLength];
194         int[] line = new int[lineNumberTableLength];
195 
196         for (int i = 0; i < lineNumberTableLength; i++) {
197             try {
198                 bci[i] = stream.readUnsignedShort();
199                 line[i] = stream.readUnsignedShort();
200             } catch (IOException e) {
201                 throw new GraalError(e);
202             }
203         }
204 
205         return new LineNumberTable(line, bci);
206     }
207 
208     @Override
getLocalVariableTable()209     public LocalVariableTable getLocalVariableTable() {
210         if (localVariableTableBytes == null) {
211             return null;
212         }
213 
214         final int localVariableTableLength = localVariableTableBytes.length / LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES;
215         DataInputStream stream = new DataInputStream(new ByteArrayInputStream(localVariableTableBytes));
216         Local[] locals = new Local[localVariableTableLength];
217 
218         for (int i = 0; i < localVariableTableLength; i++) {
219             try {
220                 final int startBci = stream.readUnsignedShort();
221                 final int endBci = startBci + stream.readUnsignedShort();
222                 final int nameCpIndex = stream.readUnsignedShort();
223                 final int typeCpIndex = stream.readUnsignedShort();
224                 final int slot = stream.readUnsignedShort();
225 
226                 String localName = constantPool.lookupUtf8(nameCpIndex);
227                 String localType = constantPool.lookupUtf8(typeCpIndex);
228 
229                 ClassfileBytecodeProvider context = constantPool.context;
230                 Class<?> c = context.resolveToClass(localType);
231                 locals[i] = new Local(localName, context.metaAccess.lookupJavaType(c), startBci, endBci, slot);
232             } catch (IOException e) {
233                 throw new GraalError(e);
234             }
235         }
236 
237         return new LocalVariableTable(locals);
238     }
239 
240     @Override
getMethod()241     public ResolvedJavaMethod getMethod() {
242         return method;
243     }
244 
245     @Override
getProfilingInfo()246     public ProfilingInfo getProfilingInfo() {
247         return DefaultProfilingInfo.get(TriState.FALSE);
248     }
249 
250     @Override
toString()251     public String toString() {
252         return getClass().getName() + method.format("<%H.%n(%p)>");
253     }
254 }
255