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