/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.graalvm.compiler.replacements.classfile; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.lang.instrument.Instrumentation; import org.graalvm.compiler.bytecode.Bytecode; import org.graalvm.compiler.debug.GraalError; import org.graalvm.compiler.replacements.classfile.ClassfileConstant.Utf8; import jdk.vm.ci.meta.ConstantPool; import jdk.vm.ci.meta.DefaultProfilingInfo; import jdk.vm.ci.meta.ExceptionHandler; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.LineNumberTable; import jdk.vm.ci.meta.Local; import jdk.vm.ci.meta.LocalVariableTable; import jdk.vm.ci.meta.ProfilingInfo; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.TriState; /** * The bytecode properties of a method as parsed directly from a class file without any * {@linkplain Instrumentation instrumentation} or other rewriting performed on the bytecode. */ public class ClassfileBytecode implements Bytecode { private static final int EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES = 8; private static final int LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES = 4; private static final int LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES = 10; private final ResolvedJavaMethod method; private final ClassfileConstantPool constantPool; private byte[] code; private int maxLocals; private int maxStack; private byte[] exceptionTableBytes; private byte[] lineNumberTableBytes; private byte[] localVariableTableBytes; public ClassfileBytecode(ResolvedJavaMethod method, DataInputStream stream, ClassfileConstantPool constantPool) throws IOException { this.method = method; this.constantPool = constantPool; maxStack = stream.readUnsignedShort(); maxLocals = stream.readUnsignedShort(); int codeLength = stream.readInt(); code = new byte[codeLength]; stream.readFully(code); int exceptionTableLength = stream.readUnsignedShort(); exceptionTableBytes = new byte[exceptionTableLength * EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES]; stream.readFully(exceptionTableBytes); readCodeAttributes(stream); } private void readCodeAttributes(DataInputStream stream) throws IOException { int count = stream.readUnsignedShort(); for (int i = 0; i < count; i++) { String attributeName = constantPool.get(Utf8.class, stream.readUnsignedShort()).value; int attributeLength = stream.readInt(); switch (attributeName) { case "LocalVariableTable": { int length = stream.readUnsignedShort(); localVariableTableBytes = new byte[length * LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES]; stream.readFully(localVariableTableBytes); break; } case "LineNumberTable": { int length = stream.readUnsignedShort(); lineNumberTableBytes = new byte[length * LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES]; stream.readFully(lineNumberTableBytes); break; } default: { Classfile.skipFully(stream, attributeLength); break; } } } } @Override public byte[] getCode() { return code; } @Override public int getCodeSize() { return code.length; } @Override public int getMaxLocals() { return maxLocals; } @Override public int getMaxStackSize() { return maxStack; } @Override public ExceptionHandler[] getExceptionHandlers() { if (exceptionTableBytes == null) { return new ExceptionHandler[0]; } final int exceptionTableLength = exceptionTableBytes.length / EXCEPTION_HANDLER_TABLE_SIZE_IN_BYTES; ExceptionHandler[] handlers = new ExceptionHandler[exceptionTableLength]; DataInputStream stream = new DataInputStream(new ByteArrayInputStream(exceptionTableBytes)); for (int i = 0; i < exceptionTableLength; i++) { try { final int startPc = stream.readUnsignedShort(); final int endPc = stream.readUnsignedShort(); final int handlerPc = stream.readUnsignedShort(); int catchTypeIndex = stream.readUnsignedShort(); JavaType catchType; if (catchTypeIndex == 0) { catchType = null; } else { final int opcode = -1; // opcode is not used catchType = constantPool.lookupType(catchTypeIndex, opcode); // Check for Throwable which catches everything. if (catchType.toJavaName().equals("java.lang.Throwable")) { catchTypeIndex = 0; catchType = null; } } handlers[i] = new ExceptionHandler(startPc, endPc, handlerPc, catchTypeIndex, catchType); } catch (IOException e) { throw new GraalError(e); } } return handlers; } @Override public StackTraceElement asStackTraceElement(int bci) { int line = getLineNumberTable().getLineNumber(bci); return new StackTraceElement(method.getDeclaringClass().toJavaName(), method.getName(), method.getDeclaringClass().getSourceFileName(), line); } @Override public ConstantPool getConstantPool() { return constantPool; } @Override public LineNumberTable getLineNumberTable() { if (lineNumberTableBytes == null) { return null; } final int lineNumberTableLength = lineNumberTableBytes.length / LINE_NUMBER_TABLE_ENTRY_SIZE_IN_BYTES; DataInputStream stream = new DataInputStream(new ByteArrayInputStream(lineNumberTableBytes)); int[] bci = new int[lineNumberTableLength]; int[] line = new int[lineNumberTableLength]; for (int i = 0; i < lineNumberTableLength; i++) { try { bci[i] = stream.readUnsignedShort(); line[i] = stream.readUnsignedShort(); } catch (IOException e) { throw new GraalError(e); } } return new LineNumberTable(line, bci); } @Override public LocalVariableTable getLocalVariableTable() { if (localVariableTableBytes == null) { return null; } final int localVariableTableLength = localVariableTableBytes.length / LOCAL_VARIABLE_TABLE_SIZE_IN_BYTES; DataInputStream stream = new DataInputStream(new ByteArrayInputStream(localVariableTableBytes)); Local[] locals = new Local[localVariableTableLength]; for (int i = 0; i < localVariableTableLength; i++) { try { final int startBci = stream.readUnsignedShort(); final int endBci = startBci + stream.readUnsignedShort(); final int nameCpIndex = stream.readUnsignedShort(); final int typeCpIndex = stream.readUnsignedShort(); final int slot = stream.readUnsignedShort(); String localName = constantPool.lookupUtf8(nameCpIndex); String localType = constantPool.lookupUtf8(typeCpIndex); ClassfileBytecodeProvider context = constantPool.context; Class<?> c = context.resolveToClass(localType); locals[i] = new Local(localName, context.metaAccess.lookupJavaType(c), startBci, endBci, slot); } catch (IOException e) { throw new GraalError(e); } } return new LocalVariableTable(locals); } @Override public ResolvedJavaMethod getMethod() { return method; } @Override public ProfilingInfo getProfilingInfo() { return DefaultProfilingInfo.get(TriState.FALSE); } @Override public String toString() { return getClass().getName() + method.format("<%H.%n(%p)>"); } }