All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.dev.assembler.CodeAttribute Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */


package com.tangosol.dev.assembler;


import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;

import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;

import com.tangosol.util.StringTable;


/**
* Represents a Java Virtual Machine "code" attribute which contains Java
* Byte Codes.
* 

* The Code Attribute is defined by the Java Virtual Machine Specification as * follows: *

*

*   Code_attribute
*       {
*       u2 attribute_name_index;
*       u4 attribute_length;
*       u2 max_stack;
*       u2 max_locals;
*       u4 code_length;
*       u1 code[code_length];
*       u2 exception_table_length;
*           {
*           u2 start_pc;
*           u2 end_pc;
*           u2  handler_pc;
*           u2  catch_type;
*           } exception_table[exception_table_length];
*       u2 attributes_count;
*       attribute_info attributes[attributes_count];
*       }
* 
* * @version 0.50, 05/15/98, assembler/dis-assembler * @author Cameron Purdy */ public class CodeAttribute extends Attribute implements Constants { // ----- constructors --------------------------------------------------- /** * Construct a code attribute. * * @param context the JVM structure containing the attribute */ protected CodeAttribute(VMStructure context) { super(context, ATTR_CODE); } // ----- VMStructure operations ----------------------------------------- /** * The disassembly process reads the structure from the passed input * stream and uses the constant pool to dereference any constant * references. * * @param stream the stream implementing java.io.DataInput from which * to read the assembled VM structure * @param pool the constant pool for the class which contains any * constants referenced by this VM structure */ protected void disassemble(DataInput stream, ConstantPool pool) throws IOException { stream.readInt(); // read the information related to the method's frame size; // this includes the number of local variable slots and the // maximum stack size m_cwStack = stream.readUnsignedShort(); m_cVars = stream.readUnsignedShort(); // read the Java byte codes int cbCode = stream.readInt(); if (cbCode <= 0 || cbCode > 0xFFFF) { throw new IllegalStateException("Method code attribute is restricted " + "to the range 0-65535 bytes"); } m_abCode = new byte[cbCode]; stream.readFully(m_abCode); // store the constant pool for later reference (when the byte codes // are disassembled) m_pool = pool; // read the exception table; each entry in the exception table // specifies a range of byte codes which are guarded, the exception // being guarded for, and the byte code to transfer execution to if // that exception occurs m_vectCatch.clear(); int cExcept = stream.readUnsignedShort(); for (int i = 0; i < cExcept; ++i) { GuardedSection section = new GuardedSection(); section.disassemble(stream, pool); m_vectCatch.add(section); } // the Code attribute also contains attributes; there are only two // expected (aka supported by JVM) attributes: LineNumberTable and // LocalVariableTable m_tblAttribute.clear(); int cAttr = stream.readUnsignedShort(); for (int i = 0; i < cAttr; ++i) { Attribute attribute = Attribute.loadAttribute(this, stream, pool); m_tblAttribute.put(attribute.getIdentity(), attribute); } m_nState = LOADED; } /** * The pre-assembly step collects the necessary entries for the constant * pool. During this step, all constants used by this VM structure and * any sub-structures are registered with (but not yet bound by position * in) the constant pool. * * @param pool the constant pool for the class which needs to be * populated with the constants required to build this * VM structure */ protected void preassemble(ConstantPool pool) { pool.registerConstant(super.getNameConstant()); // byte code ensureOps(); preassembleOps(pool); // guarded sections for (Enumeration enmr = m_vectCatch.elements(); enmr.hasMoreElements(); ) { ((GuardedSection) enmr.nextElement()).preassemble(pool); } // sub-attributes for (Enumeration enmr = m_tblAttribute.elements(); enmr.hasMoreElements(); ) { ((Attribute) enmr.nextElement()).preassemble(pool); } } /** * The assembly process assembles and writes the structure to the passed * output stream, resolving any dependencies using the passed constant * pool. * * @param stream the stream implementing java.io.DataOutput to which to * write the assembled VM structure * @param pool the constant pool for the class which by this point * contains the entire set of constants required to build * this VM structure */ protected void assemble(DataOutput stream, ConstantPool pool) throws IOException { // assemble the ops into byte codes; this also sets up the guarded // section information, frame (stack size/variable count) // information, and the default sub-attributes assembleOps(pool); // assemble the sub-attributes byte[] abAttr = NO_BYTES; int cAttrs = m_tblAttribute.getSize(); if (cAttrs > 0) { ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(); DataOutput streamAttr = new DataOutputStream(streamRaw); for (Enumeration enmr = m_tblAttribute.elements(); enmr.hasMoreElements(); ) { ((Attribute) enmr.nextElement()).assemble(streamAttr, pool); } abAttr = streamRaw.toByteArray(); } // header (name of attribute, length of attribute) stream.writeShort(pool.findConstant(super.getNameConstant())); stream.writeInt(2 // max stack + 2 // max locals + 4 // code length + m_abCode.length // code + 2 // guarded section count + 8 * m_vectCatch.size() // guarded sections + 2 // attribute count + abAttr.length); // attributes // body stream.writeShort(m_cwStack); stream.writeShort(m_cVars); stream.writeInt(m_abCode.length); stream.write(m_abCode); stream.writeShort(m_vectCatch.size()); for (Enumeration enmr = m_vectCatch.elements(); enmr.hasMoreElements(); ) { ((GuardedSection) enmr.nextElement()).assemble(stream, pool); } stream.writeShort(cAttrs); stream.write(abAttr); } /** * Determine if the attribute has been modified. * * @return true if the attribute has been modified */ public boolean isModified() { if (m_fModified) { return true; } // attributes Enumeration enmr = m_tblAttribute.elements(); while (enmr.hasMoreElements()) { Attribute attr = (Attribute) enmr.nextElement(); if (attr.isModified()) { return true; } } return false; } /** * Reset the modified state of the VM structure. * * This method must be overridden by sub-classes which do not maintain * the attribute as binary. */ protected void resetModified() { m_fModified = false; // attributes Enumeration enmr = m_tblAttribute.elements(); while (enmr.hasMoreElements()) { ((Attribute) enmr.nextElement()).resetModified(); } } // ----- Object operations ---------------------------------------------- /** * Produce a human-readable string describing the attribute. * * @return a string describing the attribute */ public String toString() { return "Code attribute for " + getContext().toString(); } // ----- internal state management -------------------------------------- /** * Make sure that the ops are disassembled if the code is from a * disassembled class. */ protected void ensureOps() { if (m_nState == LOADED || m_nState == ASSEMBLED) { disassembleOps(); } } /** * Disassemble the byte code into JASM ops. This is done the first time * that an operation requires the byte code to be disassembled. */ protected void disassembleOps() { // assertion if (m_abCode == null || m_opFirst != null) { throw new IllegalStateException(CLASS + ".disassembleOps: No code exists or ops already exist!"); } // at this point, all the inputs exist to disassemble the code // m_abCode - the byte code // m_cVars - number of local variables (actually, the number // of words) // m_vectCatch - guarded sections, which become try/catch ops // m_tblAttribute - the attributes table, which may contain the line // number and local variable information // m_pool - the constant pool try { // get a linked list of ops Method method = (Method) getContext(); // build an array of data types for the parameters String[] asTypes = method.getTypes(); if (method.isStatic()) { // return param is in element 0; remove it int cNew = asTypes.length - 1; String[] asNew = new String[cNew]; System.arraycopy(asTypes, 1, asNew, 0, cNew); asTypes = asNew; } else { // return param is in element 0; replace it with "this" asTypes[0] = 'L' + method.getClassName() + ';'; } Op[] aopBounds = new Op[2]; Op.disassembleOps( m_abCode, m_cVars, asTypes, m_vectCatch, (LineNumberTableAttribute) m_tblAttribute.get(ATTR_LINENUMBERS), (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES ), (LocalVariableTypeTableAttribute) m_tblAttribute.get(ATTR_VARIABLETYPES), m_pool, aopBounds); m_opFirst = aopBounds[0]; m_opLast = aopBounds[1]; } catch (IOException e) { throw ensureRuntimeException(e); } m_abCode = null; m_nState = DISASSEMBLED; } /** * Make a quick pass at the JASM ops to remove dead code and register * any constants. * * @param pool the constant pool for the class */ protected void preassembleOps(ConstantPool pool) { // assert: expected state (unassembled) and something to assemble if (!((m_nState == ADDED || m_nState == DISASSEMBLED) && m_opFirst != null)) { throw new IllegalStateException(CLASS + ".preassembleOps: Illegal state (" + m_nState + ") or no ops to assemble!"); } // always start the code with a label; this makes it easier to // enforce type safety since all non-linear execution (including // the initial method invocation) will start with a LABEL op if (!(m_opFirst instanceof Label)) { Label label = new Label(); label.setNext(m_opFirst); m_opFirst = label; } // collect all code contexts starting with the main context Label labelMain = (Label) m_opFirst; HashSet setContexts = new HashSet(); setContexts.add(labelMain); // calculate the maximum stack size (also marks dead code, etc.) int cwStack = checkStack(labelMain, 0, 0, labelMain, setContexts, 0); // assign an integer index to each context int cContexts = 0; for (Iterator iter = setContexts.iterator(); iter.hasNext(); ) { ((Label) iter.next()).setContextIndex(cContexts++); } // attributes of the code attribute which support debugging LineNumberTableAttribute attrLines = new LineNumberTableAttribute(this); LocalVariableTableAttribute attrVars = null; if (m_nState == DISASSEMBLED) { // use the disassembled local variable table (it is close // to impossible to reconstruct scope etc. from assembled // code so assume the original information was correct) attrVars = (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES); } if (attrVars == null) { attrVars = new LocalVariableTableAttribute(this); } // iterate through the ops, removing dead code, registering // constants, matching up BEGINs/ENDs (scope) and associating scopes // with variable declarations, assigning register (variable) offsets, // and preparing for the definite assignment determination Begin begin = null; // current scope HashSet setVars = new HashSet(); // currently in-scope variables boolean fDebugInfo= false; // set to true if any debug info Vector vectVar = new Vector(); // all variable declaration ops int[] aiVar = new int[cContexts]; // variable pool (by code context) int cMaxSlots = 0; // number of variable slots int iPrevLine = 0; // line number of the previous op Vector vectCatch = new Vector(); // guarded session information pass: for (Op op = m_opFirst, opPrev = null; op != null; op = (opPrev = op).getNext()) { // remove unreachable ops (dead code) if (op.isDiscardable()) { do { op = op.getNext(); if (op == null) { opPrev.setNext(null); break pass; } } while (op.isDiscardable()); // fix the linked list, removing all dead code opPrev.setNext(op); } // specific handling for certain ops int iOp = op.getValue(); switch (iOp) { case BEGIN: { Begin beginNew = (Begin) op; // store the current variable pool state with the scope beginNew.setVariablePool((int[]) aiVar.clone()); // push this begin onto the scope stack beginNew.setOuterScope(begin); // use the new begin as the start of the current scope begin = beginNew; } break; case END: { End end = (End) op; // END must have a matching BEGIN if (begin == null) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "END without BEGIN!"); } begin.setEnd(end); // update currently declared variable list // (used later by definite assignment) for (Enumeration enmr = begin.getDeclarations(); enmr.hasMoreElements(); ) { OpDeclare decl = (OpDeclare) enmr.nextElement(); setVars.remove(decl); } // restore the variable pool aiVar = begin.getVariablePool(); // pop a begin off of the scope stack begin = begin.getOuterScope(); } break; case IVAR: case LVAR: case FVAR: case DVAR: case AVAR: case RVAR: { OpDeclare decl = (OpDeclare) op; // must have begin if (begin == null) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "Declaration (?VAR) without BEGIN!"); } // add the local variable to the list of all variables // which do not have an absolute register index vectVar.addElement(decl); // add the local variable to the scope begin.addDeclaration(decl); // inform the variable of its scope decl.setBegin(begin); // update currently declared variable list // (used later by definite assignment) if (decl.hasDebugInfo()) { fDebugInfo = true; setVars.add(decl); } int iVar = decl.getSlot(); if (iVar == UNKNOWN) { // assign a register offset (variable slot offset) // note: this is not the actual variable slot index // unless the context is the main context; // the index is calculated later based on the number // of variable slots already used when the variable // declaration's code context is entered int iCtx = decl.getContext().getContextIndex(); iVar = aiVar[iCtx]; decl.setSlot(iVar); aiVar[iCtx] = iVar + decl.getWidth(); } else { int cSlots = iVar + decl.getWidth(); if (cSlots > cMaxSlots) { cMaxSlots = cSlots; } } } break; case ILOAD: case LLOAD: case FLOAD: case DLOAD: case ALOAD: case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RSTORE: case IINC: case RET: { OpDeclare decl = ((OpVariable)op).getVariable(); // if begin is unknown then no var declaration // if end op is known then var is out of scope if (decl.getBegin() == null || decl.getEnd() != null) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "Variable used out of scope! " + op); } } break; case JSR: { Jsr jsr = (Jsr) op; // store the next available variable number for the // context containing the JSR op (to help determine // the variable number base for the subroutine context // which is branched to by the JSR op) int iCtx = jsr.getContext().getContextIndex(); jsr.setFirstSlot(aiVar[iCtx]); } break; case LABEL: { Label label = (Label) op; // register all currently in-scope variables with the // label; this information is used during the definite // assignment check label.setVariables((HashSet) setVars.clone()); } break; case CATCH: { // the guarded sections are built in the order that the Catch // ops are encounted; this reflects Java language compilation Catch opCatch = (Catch) op; Try opTry = opCatch.getTry(); ClassConstant clz = opCatch.getExceptionClass(); Label label = opCatch.getLabel(); GuardedSection section = new GuardedSection(opTry, opCatch, clz, label); vectCatch.addElement(section); } break; } // build line number information int iCurLine = op.getLine(); if (iCurLine != iPrevLine && iCurLine > 0 && op.hasSize()) { attrLines.add(op); iPrevLine = iCurLine; } // register any constants op.preassemble(pool); } // assert: verify that all begins were matched with ends if (begin != null) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "BEGIN without END!"); } // assign variable slots (unless they were pre-assigned, e.g. the ops // were disassembled and therefore already had variable slot numbers) if (cMaxSlots == 0) { // organize contexts by depth Label[] alabelContext = (Label[]) setContexts.toArray(LABEL_ARRAY); Arrays.sort(alabelContext); // assert: number of contexts hasn't changed if (alabelContext.length != cContexts) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "Unexpected number of contexts!"); } // assert: first context is main (depth 0) and if second context // exists, it is depth >0 if (alabelContext[0].getDepth() != 0 || cContexts > 1 && alabelContext[1].getDepth() != 1) { throw new IllegalStateException(CLASS + ".preassembleOps: " + "Unexpected depths of contexts!"); } // determine register base for each non-main context for (int i = 0; i < cContexts; ++i) { Label labelContext = alabelContext[i]; int iFirstSlot = 0; for (Enumeration enmr = labelContext.getCallers(); enmr.hasMoreElements(); ) { Jsr jsr = (Jsr) enmr.nextElement(); int iSlot = jsr.getContext().getFirstSlot() + jsr.getFirstSlot(); if (iSlot > iFirstSlot) { iFirstSlot = iSlot; } } labelContext.setFirstSlot(iFirstSlot); } // assign absolute register slots to variables // determine number of local variables ("slots") for (Enumeration enmr = vectVar.elements(); enmr.hasMoreElements(); ) { OpDeclare decl = (OpDeclare) enmr.nextElement(); int iSlotOffset = decl.getSlot(); Label labelContext = decl.getContext(); int iSlot = labelContext.getFirstSlot() + iSlotOffset; int cSlots = iSlot + decl.getWidth(); decl.setSlot(iSlot); if (cSlots > cMaxSlots) { cMaxSlots = cSlots; } } if (fDebugInfo) { // definite assignment: build the local variable table // 1) determine the set of automatically-assigned variables // (the parameters to the method) Method method = (Method) getContext(); String[] asType = method.getTypes(); int cTypes = asType.length; int cwParams = (method.isStatic() ? 0 : 1); for (int i = 0; i < cTypes; ++i) { char chType = asType[i].charAt(0); cwParams += OpDeclare.getJavaWidth(chType); } // 2) determine what variables are assigned at each label // (checkAssignment) checkAssignment(labelMain, setVars, cwParams); // 3) iterate through all ops, building a map keyed by // variable with the value being the op where the // variable became definitely assigned (either a label // or a ?STORE op) // 4) when a variable located in the map is un-assigned // either by a label which does not contain that // variable in its assigned list or by an END op which // ends the scope of the variable, add a new range // to the local variable table HashMap mapVars = new HashMap(); for (Op op = m_opFirst; op != null; op = op.getNext()) { switch (op.getValue()) { case IVAR: case LVAR: case FVAR: case DVAR: case AVAR: case RVAR: { OpDeclare decl = (OpDeclare) op; // parameters are definitely assigned if (decl.hasDebugInfo() && decl.getSlot() < cwParams && !mapVars.containsKey(decl)) { mapVars.put(decl, decl); } } break; case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RSTORE: { OpStore stor = (OpStore) op; OpDeclare decl = stor.getVariable(); // the ?STORE ops definitely assign a variable if (decl.hasDebugInfo() && !mapVars.containsKey(decl)) { mapVars.put(decl, stor); } } break; case LABEL: { Label label = (Label) op; HashSet setLabelVars = label.getVariables(); // a LABEL can unassign variables which are in // the current map but which are not in its set Iterator iterator = mapVars.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); OpDeclare decl = (OpDeclare) entry.getKey(); if (!setLabelVars.contains(decl)) { Op opInit = (Op) entry.getValue(); attrVars.add(decl, opInit, label); iterator.remove(); } } // a LABEL can assign variables which are in its // set of definitely assigned variables but which // are not in the current map if (setLabelVars.size() != mapVars.size()) { iterator = setLabelVars.iterator(); while (iterator.hasNext()) { OpDeclare decl = (OpDeclare) iterator.next(); if (!mapVars.containsKey(decl)) { mapVars.put(decl, label); } } } } break; case END: { End end = (End) op; // an END op unassigns all variables that go out // of scope Iterator iterator = mapVars.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); OpDeclare decl = (OpDeclare) entry.getKey(); if (decl.getEnd() == end) { Op opInit = (Op) entry.getValue(); attrVars.add(decl, opInit, end); iterator.remove(); } } } break; } } } } // store the frame size (register count and stack height) m_cVars = cMaxSlots; m_cwStack = cwStack; // store the guarded section information m_vectCatch = vectCatch; // store the line number table if (attrLines.isEmpty()) { m_tblAttribute.remove(attrLines.getIdentity()); } else { m_tblAttribute.put(attrLines.getIdentity(), attrLines); } // store the local variable table (but only if there is any // line number information) if (attrLines.isEmpty() || attrVars.isEmpty()) { m_tblAttribute.remove(attrVars.getIdentity()); } else { m_tblAttribute.put(attrVars.getIdentity(), attrVars); } } /** * Assemble the JASM ops into byte code. This is done when the attribute * is asked to assemble. * * @param pool the constant pool for the class which contains any * constants referenced by the byte code */ protected void assembleOps(ConstantPool pool) throws IOException { // assert: expected state (unassembled) and something to assemble if (!((m_nState == ADDED || m_nState == DISASSEMBLED) && m_opFirst != null)) { throw new IllegalStateException(CLASS + ".assembleOps: Illegal state (" + m_nState + ") or no ops to assemble!"); } // assign PC's (byte code offsets) to each op int of = 0; for (Op op = m_opFirst; op != null; op = op.getNext()) { op.setOffset(of); op.calculateSize(pool); of += op.getSize(); } // assemble the byte code ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(); DataOutput stream = new DataOutputStream(streamRaw); for (Op op = m_opFirst; op != null; op = op.getNext()) { op.assemble(stream, pool); } // store the byte code m_abCode = streamRaw.toByteArray(); m_nState = ASSEMBLED; } /** * Walk the code as the JVM would, following all possible branches, and * verify that the stack is maintained properly by the code. * * @param labelFirst the label to start checking the stack from * @param cwCurStack the size of the stack immediately before opFirst * @param cwMaxStack the maximum size of the stack encountered so far * @param labelContext the label starting the code (either main or sub) * @param setContexts the set of all reachable code contexts * @param iSubDepth the current depth in the subroutine call stack * (0==main) * * @return the maximum stack size encountered */ protected int checkStack(Label labelFirst, int cwCurStack, int cwMaxStack, Label labelContext, Set setContexts, int iSubDepth) { // check for a new max stack (e.g. from JSR pushing the return address) if (cwCurStack > cwMaxStack) { cwMaxStack = cwCurStack; } // if the label isn't a new code context, verify that the label // is not reached by a JSR if (labelFirst != labelContext && labelFirst.isSubroutine()) { throw new IllegalStateException(CLASS + ".checkStack: " + "Label reachable both by subroutine invocation and otherwise!"); } for (Op op = labelFirst; op != null; op = op.getNext()) { // check if this op has already been visited boolean fVisited = op.isReachable(); // set the expected stack height for the op op.setStackHeight(cwCurStack); // in order to compute the stack change for a JSR, trace the // branch to its conclusion (i.e. the RET) int iOp = op.getValue(); if (iOp == JSR) { Jsr jsr = (Jsr) op; Label label = jsr.getLabel(); boolean fSub = label.isSubroutine(); boolean fPrev = label.isReachable(); // each reachable JSR is within a specific code context; // the JSR must know its code context in order to help with // the calculation of subroutine depth jsr.setContext(labelContext); // each subroutine label knows all reachable JSR ops which // invoke the subroutine label.addCaller(jsr); // there are several possibilities: // 1) the label has already been reached // 1) it is not marked as a subroutine so the code is // illegal (a subroutine must only be accessed by // a JSR statement) // 2) it is marked as a subroutine but the RET height // at the label is unknown, which means that either // the call is recursive or the subroutine does not // reach a RET // 3) it is marked as a subroutine and the RET height // is already known so use it // 2) the label has not already been reached so mark the // label as a subroutine and calculate its RET height // by checking the stack from there if (!fSub) { // verify that the label has not already been reached by // some non-JSR method if (fPrev) { throw new IllegalStateException(CLASS + ".checkStack: " + "Label reachable both by subroutine invocation and otherwise!"); } // keep track of all subroutines (each is a code context) setContexts.add(label); // JSR pushes one word onto the stack before transferring // control to the label cwMaxStack = checkStack(label, cwCurStack + 1, cwMaxStack, label, setContexts, iSubDepth + 1); } // if a RET was not found for the JSR, then the JSR does // not continue if (label.getRet() == null) { return cwMaxStack; } } // update the size of the stack based on the affect of this op cwCurStack += op.getStackChange(); // check for new max stack if (cwCurStack > cwMaxStack) { cwMaxStack = cwCurStack; } // check for stack underflow else if (cwCurStack < 0) { throw new IllegalStateException(CLASS + ".checkStack: " + "Stack underflow on " + op.toString() + " (#" + op.getValue() + " @" + op.getOffset() + ")!"); } switch (iOp) { case LABEL: { Label label = (Label) op; // check if we've already been here; if so then return if (fVisited) { if (labelContext != label.getContext()) { throw new IllegalStateException(CLASS + ".checkStack: " + "Label " + label + " is reachable within two " + "different code contexts!"); } return cwMaxStack; } else { label.setContext(labelContext); } } break; case RET: // must be here from a JSR if (iSubDepth > 0) { labelContext.setRet((Ret)op); labelContext.setRetHeight(cwCurStack); return cwMaxStack; } else { throw new IllegalStateException(CLASS + ".checkStack: " + "RET without JSR!"); } case GOTO: return checkStack(((Goto)op).getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: case IFNULL: case IFNONNULL: cwMaxStack = checkStack(((OpBranch)op).getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); break; case SWITCH: case TABLESWITCH: case LOOKUPSWITCH: { OpSwitch opswitch = (OpSwitch)op; // check default branch cwMaxStack = checkStack(opswitch.getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); // check cases Case[] aopCase = opswitch.getCases(); int cCases = aopCase.length; for (int i = 0; i < cCases; ++i) { cwMaxStack = checkStack(aopCase[i].getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); } } return cwMaxStack; case TRY: { Try optry = (Try) op; Catch[] aopCatch = optry.getCatches(); int cCatches = aopCatch.length; for (int i = 0; i < cCatches; ++i) { // there is always exactly one word on the stack when // an exception handler is invoked cwMaxStack = checkStack(aopCatch[i].getLabel(), 1, cwMaxStack, labelContext, setContexts, iSubDepth); } } break; case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: case RETURN: case ATHROW: return cwMaxStack; case IVAR: case LVAR: case FVAR: case DVAR: case AVAR: case RVAR: { // associate each declaration with a code context OpDeclare decl = (OpDeclare) op; decl.setContext(labelContext); } break; } } throw new IllegalStateException(CLASS + ".checkStack: " + "Code not terminated properly! (e.g. no return)"); } /** * Walk the code as the JVM would, following all possible branches, and * determine which variables are assigned at each label. * * @param labelFirst the op to start checking from * @param setVars the current set of variables that have a value * @param cwParams the number of parameter words to the method */ protected void checkAssignment(Label labelFirst, HashSet setVars, int cwParams) { for (Op op = labelFirst; op != null; op = op.getNext()) { int iOp = op.getValue(); switch (iOp) { case IVAR: case LVAR: case FVAR: case DVAR: case AVAR: case RVAR: { OpDeclare decl = (OpDeclare) op; // parameters are definitely assigned if (decl.getSlot() < cwParams) { setVars.add(decl); } } break; case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: case RSTORE: { OpStore stor = (OpStore) op; // the ?STORE ops definitely assign a variable OpDeclare decl = stor.getVariable(); setVars.add(decl); } break; case LABEL: { Label label = (Label) op; HashSet setLabelVars = label.getVariables(); // determine the intersection of the two sets of // assigned variables; // if the set of definitely assigned variables at the // label did not change, and if we've been here before, // then return (no further changes to make on this path) if (!setLabelVars.retainAll(setVars) && label.isVisited()) { return; } // make sure the current set of definitely-assigned, // in-scope variables is up-to-date if (setVars.size() != setLabelVars.size()) { setVars = (HashSet) setLabelVars.clone(); } label.setVisited(true); } break; case JSR: { Jsr jsr = (Jsr) op; Label label = jsr.getLabel(); Ret ret = label.getRet(); // check variable assignments in the subroutine // (Note: assignments made by the subroutine are not // reflected when control returns to this point) checkAssignment(label, (HashSet) setVars.clone(), cwParams); // if the flow of control cannot return, don't continue if (ret == null) { return; } // merge the definitely assigned variables at the RET // with the current set; (union of in-scope assigned // vars, although some of the RET vars may not be in- // scope; that is automatically corrected on the next // encountered label) setVars.addAll(ret.getVariables()); } break; case RET: { Ret ret = (Ret) op; // keep an intersection of the current set of assigned // variables with the set already stored on the RET HashSet setRetVars = ret.getVariables(); if (setRetVars == null) { // none registered on the ret; store the current set ret.setVariables(setVars); } else { // compute intersection of in-scope assigned vars setRetVars.retainAll(setVars); } } return; case GOTO: // since the flow of control is transferring uncondition- // ally, pass the current set of assigned variables checkAssignment(((Goto)op).getLabel(), setVars, cwParams); return; case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: case IFNULL: case IFNONNULL: // the flow of control can branch at this point checkAssignment(((OpBranch)op).getLabel(), (HashSet) setVars.clone(), cwParams); break; case SWITCH: case TABLESWITCH: case LOOKUPSWITCH: { OpSwitch opswitch = (OpSwitch)op; // check cases Case[] aopCase = opswitch.getCases(); int cCases = aopCase.length; for (int i = 0; i < cCases; ++i) { checkAssignment(aopCase[i].getLabel(), (HashSet) setVars.clone(), cwParams); } // check default branch checkAssignment(opswitch.getLabel(), setVars, cwParams); } return; case TRY: { Try optry = (Try) op; Catch[] aopCatch = optry.getCatches(); int cCatches = aopCatch.length; for (int i = 0; i < cCatches; ++i) { checkAssignment(aopCatch[i].getLabel(), (HashSet) setVars.clone(), cwParams); } } break; case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: case RETURN: case ATHROW: return; } } throw new IllegalStateException(CLASS + ".checkAssignment: " + "Code not terminated properly! (e.g. no return)"); } // ----- assembler interface -------------------------------------------- /** * Clear any ops and associated structures. */ public void clear() { m_abCode = null; m_opFirst = null; m_opLast = null; m_iLine = 0; m_vectCatch.clear(); m_nState = CLEAR; m_fModified = true; } /** * Add an op to the code. This is the primary means of building byte * code. * * @param op an instance of Op (or an Op sub-class) * * @return the op that was added */ public Op add(Op op) { // assertions if (m_nState > ADDED) { throw new IllegalStateException(CLASS + ".add: Illegal state (" + m_nState + ")"); } if (op == null) { throw new IllegalArgumentException(CLASS + ".add: Op is required!"); } if (op instanceof Case) { // a Case op follows only a Switch or Case op if (!(m_opLast instanceof Switch || m_opLast instanceof Case)) { throw new IllegalArgumentException(CLASS + ".add: A Case Op can only follow a Switch or a Case op!"); } } // set the source code line for the op op.setLine(m_iLine); // add the op to the linked list if (m_opFirst == null) { m_opFirst = op; m_opLast = op; } else { m_opLast.setNext(op); m_opLast = op; } // update state m_nState = ADDED; m_fModified = true; return op; } /** * Get the current "source code line". * * @return the current line of code */ public int getLine() { return m_iLine; } /** * Set the current "source code line". * * @param iLine the current line of code */ public void setLine(int iLine) { m_iLine = iLine; } /** * Advance to the next "source code line". */ public void nextLine() { ++m_iLine; } /** * Return the first op-code for this CodeAttribute */ public Op getFirstOp() { ensureOps(); return m_opFirst; } /** * For debugging or listing purposes, print the code details. */ public void print() { out("Code Listing:"); for (Op op = m_opFirst; op != null; op = op.getNext()) { op.print(); } LocalVariableTableAttribute attr = (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES); if (attr != null) { out("Local variable table:"); out(attr); } } /** * For script display purposes, format the JASM code. */ public String toJasm() { StringBuffer sb = new StringBuffer(8192); switch (m_nState) { case LOADED: ensureOps(); // fall through case DISASSEMBLED: if (m_opFirst != null) { int cDigits = getMaxDecDigits(m_opLast.getOffset()); int nLine = 0; for (Op op = m_opFirst; op != null; op = op.getNext()) { int nOpLine = op.getLine(); if (nOpLine != nLine) { nLine = nOpLine; sb.append('\n') .append(toDecString(op.getOffset(), cDigits)) .append(": // line ") .append(nLine); } String s = op.toJasm(); if (s != null) { sb.append('\n') .append(toDecString(op.getOffset(), cDigits)) .append(": ") .append(s); } } break; } // fall through default: for (Op op = m_opFirst; op != null; op = op.getNext()) { sb.append("\n[") .append(op.getOffset()) .append("] (") .append(op.getLine()) .append(") ") .append(op.toString()); } break; } return sb.substring(1); } // ----- accessor: attribute ------------------------------------------- /** * Access an attribute structure. * * @param sName the attribute name * * @return the specified attribute or null if the attribute does not exist */ public Attribute getAttribute(String sName) { return (Attribute) m_tblAttribute.get(sName); } /** * Add an attribute structure. * * @param sName the attribute name * * @return the new attribute */ public Attribute addAttribute(String sName) { Attribute attribute; if (sName.equals(ATTR_LINENUMBERS)) { attribute = new LineNumberTableAttribute(this); } else if (sName.equals(ATTR_VARIABLES)) { attribute = new LocalVariableTableAttribute(this); } else if (sName.equals(ATTR_VARIABLETYPES)) { attribute = new LocalVariableTypeTableAttribute(this); } else if (sName.equals(ATTR_STACKMAPTABLE)) { attribute = new StackMapTableAttribute(this); } else if (sName.equals(ATTR_RTVISTANNOT)) { attribute = new RuntimeVisibleTypeAnnotationsAttribute(this); } else if (sName.equals(ATTR_RTINVISTANNOT)) { attribute = new RuntimeInvisibleTypeAnnotationsAttribute(this); } else { attribute = new Attribute(this, sName); } m_tblAttribute.put(attribute.getIdentity(), attribute); m_fModified = true; return attribute; } /** * Remove a attribute. * * @param sName the attribute name */ public void removeAttribute(String sName) { if (m_tblAttribute.remove(sName) != null) { m_fModified = true; } } // ----- data members --------------------------------------------------- /** * The name of this class. */ private static final String CLASS = "CodeAttribute"; /** * An empty byte array. */ private static final byte[] NO_BYTES = new byte[0]; /** * An empty label array. */ private static final Label[] LABEL_ARRAY = new Label[0]; /** * State: Initialized. */ private static final int CLEAR = 0; /** * State: The user of this class has "assembled" (added) ops. */ private static final int ADDED = 1; /** * State: This attribute has disassembled, but the binary code itself * was not disassembled; it is in m_abCode. */ private static final int LOADED = 2; /** * State: The binary code in m_abCode was disassembled into ops. */ private static final int DISASSEMBLED = 3; /** * State: The ops in the linked list were assembled into m_abCode. */ private static final int ASSEMBLED = 4; /** * Line number table sub-attribute. */ private static final UtfConstant CONST_LINENUMBERS = new UtfConstant(ATTR_LINENUMBERS); /** * Local variable table sub-attribute. */ private static final UtfConstant CONST_VARIABLES = new UtfConstant(ATTR_VARIABLES); /** * State of the code attribute. */ private int m_nState; /** * The constant pool supplied for disassembly. This is necessary for when * the byte code must be fully disassembled. */ private ConstantPool m_pool; /** * Tracks modifications to the code attribute. */ private boolean m_fModified; /** * Number of variables (as read or assembled). */ private int m_cVars; /** * Size of stack (as read or assembled). */ private int m_cwStack; /** * The byte code (as read or assembled). */ private byte[] m_abCode; /** * The first op (the head of a linked list). */ private Op m_opFirst; /** * The last op (the tail of a linked list). * n/a for disassembled ops. */ private Op m_opLast; /** * The current "source code line". * n/a for disassembled ops. */ private int m_iLine; /** * Guarded sections (each represents information about a Java "catch" or * "finally" construct) (as read or assembled). */ private Vector m_vectCatch = new Vector(); /** * Sub-attributes. */ private StringTable m_tblAttribute = new StringTable(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy