| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.bcel.verifier.statics; |
| |
| |
| import org.apache.bcel.Const; |
| import org.apache.bcel.Repository; |
| import org.apache.bcel.classfile.Attribute; |
| import org.apache.bcel.classfile.Code; |
| import org.apache.bcel.classfile.CodeException; |
| import org.apache.bcel.classfile.Constant; |
| import org.apache.bcel.classfile.ConstantClass; |
| import org.apache.bcel.classfile.ConstantDouble; |
| import org.apache.bcel.classfile.ConstantFieldref; |
| import org.apache.bcel.classfile.ConstantFloat; |
| import org.apache.bcel.classfile.ConstantInteger; |
| import org.apache.bcel.classfile.ConstantInterfaceMethodref; |
| import org.apache.bcel.classfile.ConstantLong; |
| import org.apache.bcel.classfile.ConstantMethodref; |
| import org.apache.bcel.classfile.ConstantNameAndType; |
| import org.apache.bcel.classfile.ConstantString; |
| import org.apache.bcel.classfile.ConstantUtf8; |
| import org.apache.bcel.classfile.Field; |
| import org.apache.bcel.classfile.JavaClass; |
| import org.apache.bcel.classfile.LineNumber; |
| import org.apache.bcel.classfile.LineNumberTable; |
| import org.apache.bcel.classfile.LocalVariable; |
| import org.apache.bcel.classfile.LocalVariableTable; |
| import org.apache.bcel.classfile.Method; |
| import org.apache.bcel.generic.ALOAD; |
| import org.apache.bcel.generic.ANEWARRAY; |
| import org.apache.bcel.generic.ASTORE; |
| import org.apache.bcel.generic.ATHROW; |
| import org.apache.bcel.generic.ArrayType; |
| import org.apache.bcel.generic.BREAKPOINT; |
| import org.apache.bcel.generic.CHECKCAST; |
| import org.apache.bcel.generic.ConstantPoolGen; |
| import org.apache.bcel.generic.DLOAD; |
| import org.apache.bcel.generic.DSTORE; |
| import org.apache.bcel.generic.FLOAD; |
| import org.apache.bcel.generic.FSTORE; |
| import org.apache.bcel.generic.FieldInstruction; |
| import org.apache.bcel.generic.GETSTATIC; |
| import org.apache.bcel.generic.GotoInstruction; |
| import org.apache.bcel.generic.IINC; |
| import org.apache.bcel.generic.ILOAD; |
| import org.apache.bcel.generic.IMPDEP1; |
| import org.apache.bcel.generic.IMPDEP2; |
| import org.apache.bcel.generic.INSTANCEOF; |
| import org.apache.bcel.generic.INVOKEDYNAMIC; |
| import org.apache.bcel.generic.INVOKEINTERFACE; |
| import org.apache.bcel.generic.INVOKESPECIAL; |
| import org.apache.bcel.generic.INVOKESTATIC; |
| import org.apache.bcel.generic.INVOKEVIRTUAL; |
| import org.apache.bcel.generic.ISTORE; |
| import org.apache.bcel.generic.Instruction; |
| import org.apache.bcel.generic.InstructionHandle; |
| import org.apache.bcel.generic.InstructionList; |
| import org.apache.bcel.generic.InvokeInstruction; |
| import org.apache.bcel.generic.JsrInstruction; |
| import org.apache.bcel.generic.LDC; |
| import org.apache.bcel.generic.LDC2_W; |
| import org.apache.bcel.generic.LLOAD; |
| import org.apache.bcel.generic.LOOKUPSWITCH; |
| import org.apache.bcel.generic.LSTORE; |
| import org.apache.bcel.generic.LoadClass; |
| import org.apache.bcel.generic.MULTIANEWARRAY; |
| import org.apache.bcel.generic.NEW; |
| import org.apache.bcel.generic.NEWARRAY; |
| import org.apache.bcel.generic.ObjectType; |
| import org.apache.bcel.generic.PUTSTATIC; |
| import org.apache.bcel.generic.RET; |
| import org.apache.bcel.generic.ReferenceType; |
| import org.apache.bcel.generic.ReturnInstruction; |
| import org.apache.bcel.generic.TABLESWITCH; |
| import org.apache.bcel.generic.Type; |
| import org.apache.bcel.verifier.PassVerifier; |
| import org.apache.bcel.verifier.VerificationResult; |
| import org.apache.bcel.verifier.Verifier; |
| import org.apache.bcel.verifier.VerifierFactory; |
| import org.apache.bcel.verifier.exc.AssertionViolatedException; |
| import org.apache.bcel.verifier.exc.ClassConstraintException; |
| import org.apache.bcel.verifier.exc.InvalidMethodException; |
| import org.apache.bcel.verifier.exc.StaticCodeConstraintException; |
| import org.apache.bcel.verifier.exc.StaticCodeInstructionConstraintException; |
| import org.apache.bcel.verifier.exc.StaticCodeInstructionOperandConstraintException; |
| |
| /** |
| * This PassVerifier verifies a class file according to |
| * pass 3, static part as described in The Java Virtual |
| * Machine Specification, 2nd edition. |
| * More detailed information is to be found at the do_verify() |
| * method's documentation. |
| * |
| * @version $Id$ |
| * @see #do_verify() |
| */ |
| public final class Pass3aVerifier extends PassVerifier{ |
| |
| /** The Verifier that created this. */ |
| private final Verifier myOwner; |
| |
| /** |
| * The method number to verify. |
| * This is the index in the array returned |
| * by JavaClass.getMethods(). |
| */ |
| private final int method_no; |
| |
| /** |
| * The one and only InstructionList object used by an instance of this class. |
| * It's here for performance reasons by do_verify() and its callees. |
| */ |
| private InstructionList instructionList; |
| /** |
| * The one and only Code object used by an instance of this class. |
| * It's here for performance reasons by do_verify() and its callees. |
| */ |
| private Code code; |
| |
| /** Should only be instantiated by a Verifier. */ |
| public Pass3aVerifier(final Verifier owner, final int method_no) { |
| myOwner = owner; |
| this.method_no = method_no; |
| } |
| |
| /** |
| * Pass 3a is the verification of static constraints of |
| * JVM code (such as legal targets of branch instructions). |
| * This is the part of pass 3 where you do not need data |
| * flow analysis. |
| * JustIce also delays the checks for a correct exception |
| * table of a Code attribute and correct line number entries |
| * in a LineNumberTable attribute of a Code attribute (which |
| * conceptually belong to pass 2) to this pass. Also, most |
| * of the check for valid local variable entries in a |
| * LocalVariableTable attribute of a Code attribute is |
| * delayed until this pass. |
| * All these checks need access to the code array of the |
| * Code attribute. |
| * |
| * @throws InvalidMethodException if the method to verify does not exist. |
| */ |
| @Override |
| public VerificationResult do_verify() { |
| try { |
| if (myOwner.doPass2().equals(VerificationResult.VR_OK)) { |
| // Okay, class file was loaded correctly by Pass 1 |
| // and satisfies static constraints of Pass 2. |
| final JavaClass jc = Repository.lookupClass(myOwner.getClassName()); |
| final Method[] methods = jc.getMethods(); |
| if (method_no >= methods.length) { |
| throw new InvalidMethodException("METHOD DOES NOT EXIST!"); |
| } |
| final Method method = methods[method_no]; |
| code = method.getCode(); |
| |
| // No Code? Nothing to verify! |
| if ( method.isAbstract() || method.isNative() ) { // IF mg HAS NO CODE (static constraint of Pass 2) |
| return VerificationResult.VR_OK; |
| } |
| |
| // TODO: |
| // We want a very sophisticated code examination here with good explanations |
| // on where to look for an illegal instruction or such. |
| // Only after that we should try to build an InstructionList and throw an |
| // AssertionViolatedException if after our examination InstructionList building |
| // still fails. |
| // That examination should be implemented in a byte-oriented way, i.e. look for |
| // an instruction, make sure its validity, count its length, find the next |
| // instruction and so on. |
| try{ |
| instructionList = new InstructionList(method.getCode().getCode()); |
| } |
| catch(final RuntimeException re) { |
| return new VerificationResult(VerificationResult.VERIFIED_REJECTED, |
| "Bad bytecode in the code array of the Code attribute of method '"+method+"'."); |
| } |
| |
| instructionList.setPositions(true); |
| |
| // Start verification. |
| VerificationResult vr = VerificationResult.VR_OK; //default |
| try{ |
| delayedPass2Checks(); |
| } |
| catch(final ClassConstraintException cce) { |
| vr = new VerificationResult(VerificationResult.VERIFIED_REJECTED, cce.getMessage()); |
| return vr; |
| } |
| try{ |
| pass3StaticInstructionChecks(); |
| pass3StaticInstructionOperandsChecks(); |
| } |
| catch(final StaticCodeConstraintException scce) { |
| vr = new VerificationResult(VerificationResult.VERIFIED_REJECTED, scce.getMessage()); |
| } |
| catch(final ClassCastException cce) { |
| vr = new VerificationResult(VerificationResult.VERIFIED_REJECTED, "Class Cast Exception: " + cce.getMessage()); |
| } |
| return vr; |
| } |
| //did not pass Pass 2. |
| return VerificationResult.VR_NOTYET; |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** |
| * These are the checks that could be done in pass 2 but are delayed to pass 3 |
| * for performance reasons. Also, these checks need access to the code array |
| * of the Code attribute of a Method so it's okay to perform them here. |
| * Also see the description of the do_verify() method. |
| * |
| * @throws ClassConstraintException if the verification fails. |
| * @see #do_verify() |
| */ |
| private void delayedPass2Checks() { |
| |
| final int[] instructionPositions = instructionList.getInstructionPositions(); |
| final int codeLength = code.getCode().length; |
| |
| ///////////////////// |
| // LineNumberTable // |
| ///////////////////// |
| final LineNumberTable lnt = code.getLineNumberTable(); |
| if (lnt != null) { |
| final LineNumber[] lineNumbers = lnt.getLineNumberTable(); |
| final IntList offsets = new IntList(); |
| lineNumber_loop: |
| for (final LineNumber lineNumber : lineNumbers) { // may appear in any order. |
| for (final int instructionPosition : instructionPositions) { |
| // TODO: Make this a binary search! The instructionPositions array is naturally ordered! |
| final int offset = lineNumber.getStartPC(); |
| if (instructionPosition == offset) { |
| if (offsets.contains(offset)) { |
| addMessage("LineNumberTable attribute '" + code.getLineNumberTable() + |
| "' refers to the same code offset ('" + offset + "') more than once" + |
| " which is violating the semantics [but is sometimes produced by IBM's 'jikes' compiler]."); |
| } else { |
| offsets.add(offset); |
| } |
| continue lineNumber_loop; |
| } |
| } |
| throw new ClassConstraintException("Code attribute '" + code + "' has a LineNumberTable attribute '" + |
| code.getLineNumberTable() + |
| "' referring to a code offset ('" + lineNumber.getStartPC() + "') that does not exist."); |
| } |
| } |
| |
| /////////////////////////// |
| // LocalVariableTable(s) // |
| /////////////////////////// |
| /* We cannot use code.getLocalVariableTable() because there could be more |
| than only one. This is a bug in BCEL. */ |
| final Attribute[] atts = code.getAttributes(); |
| for (final Attribute att : atts) { |
| if (att instanceof LocalVariableTable) { |
| final LocalVariableTable lvt = (LocalVariableTable) att; |
| final LocalVariable[] localVariables = lvt.getLocalVariableTable(); |
| for (final LocalVariable localVariable : localVariables) { |
| final int startpc = localVariable.getStartPC(); |
| final int length = localVariable.getLength(); |
| |
| if (!contains(instructionPositions, startpc)) { |
| throw new ClassConstraintException("Code attribute '" + code |
| + "' has a LocalVariableTable attribute '" + code.getLocalVariableTable() |
| + "' referring to a code offset ('" + startpc + "') that does not exist."); |
| } |
| if ((!contains(instructionPositions, startpc + length)) && (startpc + length != codeLength)) { |
| throw new ClassConstraintException("Code attribute '" + code |
| + "' has a LocalVariableTable attribute '" + code.getLocalVariableTable() |
| + "' referring to a code offset start_pc+length ('" + (startpc + length) |
| + "') that does not exist."); |
| } |
| } |
| } |
| } |
| |
| //////////////////// |
| // ExceptionTable // |
| //////////////////// |
| // In BCEL's "classfile" API, the startPC/endPC-notation is |
| // inclusive/exclusive as in the Java Virtual Machine Specification. |
| // WARNING: This is not true for BCEL's "generic" API. |
| final CodeException[] exceptionTable = code.getExceptionTable(); |
| for (final CodeException element : exceptionTable) { |
| final int startpc = element.getStartPC(); |
| final int endpc = element.getEndPC(); |
| final int handlerpc = element.getHandlerPC(); |
| if (startpc >= endpc) { |
| throw new ClassConstraintException("Code attribute '"+code+"' has an exception_table entry '"+element+ |
| "' that has its start_pc ('"+startpc+"') not smaller than its end_pc ('"+endpc+"')."); |
| } |
| if (!contains(instructionPositions, startpc)) { |
| throw new ClassConstraintException("Code attribute '"+code+"' has an exception_table entry '"+element+ |
| "' that has a non-existant bytecode offset as its start_pc ('"+startpc+"')."); |
| } |
| if ( (!contains(instructionPositions, endpc)) && (endpc != codeLength)) { |
| throw new ClassConstraintException("Code attribute '"+code+"' has an exception_table entry '"+element+ |
| "' that has a non-existant bytecode offset as its end_pc ('"+startpc+ |
| "') [that is also not equal to code_length ('"+codeLength+"')]."); |
| } |
| if (!contains(instructionPositions, handlerpc)) { |
| throw new ClassConstraintException("Code attribute '"+code+"' has an exception_table entry '"+element+ |
| "' that has a non-existant bytecode offset as its handler_pc ('"+handlerpc+"')."); |
| } |
| } |
| } |
| |
| /** |
| * These are the checks if constraints are satisfied which are described in the |
| * Java Virtual Machine Specification, Second Edition as Static Constraints on |
| * the instructions of Java Virtual Machine Code (chapter 4.8.1). |
| * |
| * @throws StaticCodeConstraintException if the verification fails. |
| */ |
| private void pass3StaticInstructionChecks() { |
| |
| // Code array must not be empty: |
| // Enforced in pass 2 (also stated in the static constraints of the Code |
| // array in vmspec2), together with pass 1 (reading code_length bytes and |
| // interpreting them as code[]). So this must not be checked again here. |
| |
| if (code.getCode().length >= Const.MAX_CODE_SIZE) {// length must be LESS than the max |
| throw new StaticCodeInstructionConstraintException( |
| "Code array in code attribute '"+code+"' too big: must be smaller than "+Const.MAX_CODE_SIZE+"65536 bytes."); |
| } |
| |
| // First opcode at offset 0: okay, that's clear. Nothing to do. |
| |
| // Only instances of the instructions documented in Section 6.4 may appear in |
| // the code array. |
| |
| // For BCEL's sake, we cannot handle WIDE stuff, but hopefully BCEL does its job right :) |
| |
| // The last byte of the last instruction in the code array must be the byte at index |
| // code_length-1 : See the do_verify() comments. We actually don't iterate through the |
| // byte array, but use an InstructionList so we cannot check for this. But BCEL does |
| // things right, so it's implicitly okay. |
| |
| // TODO: Check how BCEL handles (and will handle) instructions like IMPDEP1, IMPDEP2, |
| // BREAKPOINT... that BCEL knows about but which are illegal anyway. |
| // We currently go the safe way here. |
| InstructionHandle ih = instructionList.getStart(); |
| while (ih != null) { |
| final Instruction i = ih.getInstruction(); |
| if (i instanceof IMPDEP1) { |
| throw new StaticCodeInstructionConstraintException( |
| "IMPDEP1 must not be in the code, it is an illegal instruction for _internal_ JVM use!"); |
| } |
| if (i instanceof IMPDEP2) { |
| throw new StaticCodeInstructionConstraintException( |
| "IMPDEP2 must not be in the code, it is an illegal instruction for _internal_ JVM use!"); |
| } |
| if (i instanceof BREAKPOINT) { |
| throw new StaticCodeInstructionConstraintException( |
| "BREAKPOINT must not be in the code, it is an illegal instruction for _internal_ JVM use!"); |
| } |
| ih = ih.getNext(); |
| } |
| |
| // The original verifier seems to do this check here, too. |
| // An unreachable last instruction may also not fall through the |
| // end of the code, which is stupid -- but with the original |
| // verifier's subroutine semantics one cannot predict reachability. |
| final Instruction last = instructionList.getEnd().getInstruction(); |
| if (! ((last instanceof ReturnInstruction) || |
| (last instanceof RET) || |
| (last instanceof GotoInstruction) || |
| (last instanceof ATHROW) )) { |
| throw new StaticCodeInstructionConstraintException( |
| "Execution must not fall off the bottom of the code array."+ |
| " This constraint is enforced statically as some existing verifiers do"+ |
| " - so it may be a false alarm if the last instruction is not reachable."); |
| } |
| } |
| |
| /** |
| * These are the checks for the satisfaction of constraints which are described in the |
| * Java Virtual Machine Specification, Second Edition as Static Constraints on |
| * the operands of instructions of Java Virtual Machine Code (chapter 4.8.1). |
| * BCEL parses the code array to create an InstructionList and therefore has to check |
| * some of these constraints. Additional checks are also implemented here. |
| * |
| * @throws StaticCodeConstraintException if the verification fails. |
| */ |
| private void pass3StaticInstructionOperandsChecks() { |
| try { |
| // When building up the InstructionList, BCEL has already done all those checks |
| // mentioned in The Java Virtual Machine Specification, Second Edition, as |
| // "static constraints on the operands of instructions in the code array". |
| // TODO: see the do_verify() comments. Maybe we should really work on the |
| // byte array first to give more comprehensive messages. |
| // TODO: Review Exception API, possibly build in some "offending instruction" thing |
| // when we're ready to insulate the offending instruction by doing the |
| // above thing. |
| |
| // TODO: Implement as much as possible here. BCEL does _not_ check everything. |
| |
| final ConstantPoolGen cpg = new ConstantPoolGen(Repository.lookupClass(myOwner.getClassName()).getConstantPool()); |
| final InstOperandConstraintVisitor v = new InstOperandConstraintVisitor(cpg); |
| |
| // Checks for the things BCEL does _not_ handle itself. |
| InstructionHandle ih = instructionList.getStart(); |
| while (ih != null) { |
| final Instruction i = ih.getInstruction(); |
| |
| // An "own" constraint, due to JustIce's new definition of what "subroutine" means. |
| if (i instanceof JsrInstruction) { |
| final InstructionHandle target = ((JsrInstruction) i).getTarget(); |
| if (target == instructionList.getStart()) { |
| throw new StaticCodeInstructionOperandConstraintException( |
| "Due to JustIce's clear definition of subroutines, no JSR or JSR_W may have a top-level instruction"+ |
| " (such as the very first instruction, which is targeted by instruction '"+ih+"' as its target."); |
| } |
| if (!(target.getInstruction() instanceof ASTORE)) { |
| throw new StaticCodeInstructionOperandConstraintException( |
| "Due to JustIce's clear definition of subroutines, no JSR or JSR_W may target anything else"+ |
| " than an ASTORE instruction. Instruction '"+ih+"' targets '"+target+"'."); |
| } |
| } |
| |
| // vmspec2, page 134-137 |
| ih.accept(v); |
| |
| ih = ih.getNext(); |
| } |
| |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** A small utility method returning if a given int i is in the given int[] ints. */ |
| private static boolean contains(final int[] ints, final int i) { |
| for (final int k : ints) { |
| if (k==i) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Returns the method number as supplied when instantiating. */ |
| public int getMethodNo() { |
| return method_no; |
| } |
| |
| /** |
| * This visitor class does the actual checking for the instruction |
| * operand's constraints. |
| */ |
| private class InstOperandConstraintVisitor extends org.apache.bcel.generic.EmptyVisitor{ |
| /** The ConstantPoolGen instance this Visitor operates on. */ |
| private final ConstantPoolGen cpg; |
| |
| /** The only Constructor. */ |
| InstOperandConstraintVisitor(final ConstantPoolGen cpg) { |
| this.cpg = cpg; |
| } |
| |
| /** |
| * Utility method to return the max_locals value of the method verified |
| * by the surrounding Pass3aVerifier instance. |
| */ |
| private int max_locals() { |
| try { |
| return Repository.lookupClass(myOwner.getClassName()).getMethods()[method_no].getCode().getMaxLocals(); |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** |
| * A utility method to always raise an exeption. |
| */ |
| private void constraintViolated(final Instruction i, final String message) { |
| throw new StaticCodeInstructionOperandConstraintException("Instruction "+i+" constraint violated: "+message); |
| } |
| |
| /** |
| * A utility method to raise an exception if the index is not |
| * a valid constant pool index. |
| */ |
| private void indexValid(final Instruction i, final int idx) { |
| if (idx < 0 || idx >= cpg.getSize()) { |
| constraintViolated(i, "Illegal constant pool index '"+idx+"'."); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////// |
| // The Java Virtual Machine Specification, pages 134-137 // |
| /////////////////////////////////////////////////////////// |
| /** |
| * Assures the generic preconditions of a LoadClass instance. |
| * The referenced class is loaded and pass2-verified. |
| */ |
| @Override |
| public void visitLoadClass(final LoadClass o) { |
| final ObjectType t = o.getLoadClassType(cpg); |
| if (t != null) {// null means "no class is loaded" |
| final Verifier v = VerifierFactory.getVerifier(t.getClassName()); |
| final VerificationResult vr = v.doPass1(); |
| if (vr.getStatus() != VerificationResult.VERIFIED_OK) { |
| constraintViolated((Instruction) o, |
| "Class '"+o.getLoadClassType(cpg).getClassName()+"' is referenced, but cannot be loaded: '"+vr+"'."); |
| } |
| } |
| } |
| |
| // The target of each jump and branch instruction [...] must be the opcode [...] |
| // BCEL _DOES_ handle this. |
| |
| // tableswitch: BCEL will do it, supposedly. |
| |
| // lookupswitch: BCEL will do it, supposedly. |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| // LDC and LDC_W (LDC_W is a subclass of LDC in BCEL's model) |
| @Override |
| public void visitLDC(final LDC o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (c instanceof ConstantClass) { |
| addMessage("Operand of LDC or LDC_W is CONSTANT_Class '"+c+"' - this is only supported in JDK 1.5 and higher."); |
| } |
| else{ |
| if (! ( (c instanceof ConstantInteger) || |
| (c instanceof ConstantFloat) || |
| (c instanceof ConstantString) ) ) { |
| constraintViolated(o, |
| "Operand of LDC or LDC_W must be one of CONSTANT_Integer, CONSTANT_Float or CONSTANT_String, but is '"+c+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| // LDC2_W |
| @Override |
| public void visitLDC2_W(final LDC2_W o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! ( (c instanceof ConstantLong) || |
| (c instanceof ConstantDouble) ) ) { |
| constraintViolated(o, "Operand of LDC2_W must be CONSTANT_Long or CONSTANT_Double, but is '"+c+"'."); |
| } |
| try{ |
| indexValid(o, o.getIndex()+1); |
| } |
| catch(final StaticCodeInstructionOperandConstraintException e) { |
| throw new AssertionViolatedException("OOPS: Does not BCEL handle that? LDC2_W operand has a problem.", e); |
| } |
| } |
| |
| private ObjectType getObjectType(final FieldInstruction o) { |
| final ReferenceType rt = o.getReferenceType(cpg); |
| if(rt instanceof ObjectType) { |
| return (ObjectType)rt; |
| } |
| constraintViolated(o, "expecting ObjectType but got "+rt); |
| return null; |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| //getfield, putfield, getstatic, putstatic |
| @Override |
| public void visitFieldInstruction(final FieldInstruction o) { |
| try { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantFieldref)) { |
| constraintViolated(o, "Indexing a constant that's not a CONSTANT_Fieldref but a '"+c+"'."); |
| } |
| |
| final String field_name = o.getFieldName(cpg); |
| |
| final JavaClass jc = Repository.lookupClass(getObjectType(o).getClassName()); |
| Field[] fields = jc.getFields(); |
| Field f = null; |
| for (final Field field : fields) { |
| if (field.getName().equals(field_name)) { |
| final Type f_type = Type.getType(field.getSignature()); |
| final Type o_type = o.getType(cpg); |
| /* TODO: Check if assignment compatibility is sufficient. |
| * What does Sun do? |
| */ |
| if (f_type.equals(o_type)) { |
| f = field; |
| break; |
| } |
| } |
| } |
| if (f == null) { |
| final JavaClass[] superclasses = jc.getSuperClasses(); |
| outer: |
| for (final JavaClass superclass : superclasses) { |
| fields = superclass.getFields(); |
| for (final Field field : fields) { |
| if (field.getName().equals(field_name)) { |
| final Type f_type = Type.getType(field.getSignature()); |
| final Type o_type = o.getType(cpg); |
| if (f_type.equals(o_type)) { |
| f = field; |
| if ((f.getAccessFlags() & (Const.ACC_PUBLIC | Const.ACC_PROTECTED)) == 0) { |
| f = null; |
| } |
| break outer; |
| } |
| } |
| } |
| } |
| if (f == null) { |
| constraintViolated(o, "Referenced field '"+field_name+"' does not exist in class '"+jc.getClassName()+"'."); |
| } |
| } |
| else{ |
| /* TODO: Check if assignment compatibility is sufficient. |
| What does Sun do? */ |
| Type.getType(f.getSignature()); |
| o.getType(cpg); |
| // Type f_type = Type.getType(f.getSignature()); |
| // Type o_type = o.getType(cpg); |
| |
| // Argh. Sun's implementation allows us to have multiple fields of |
| // the same name but with a different signature. |
| //if (! f_type.equals(o_type)) { |
| // constraintViolated(o, |
| // "Referenced field '"+field_name+"' has type '"+f_type+"' instead of '"+o_type+"' as expected."); |
| //} |
| |
| /* TODO: Check for access modifiers here. */ |
| } |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitInvokeInstruction(final InvokeInstruction o) { |
| indexValid(o, o.getIndex()); |
| if ( (o instanceof INVOKEVIRTUAL) || |
| (o instanceof INVOKESPECIAL) || |
| (o instanceof INVOKESTATIC) ) { |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantMethodref)) { |
| constraintViolated(o, "Indexing a constant that's not a CONSTANT_Methodref but a '"+c+"'."); |
| } |
| else{ |
| // Constants are okay due to pass2. |
| final ConstantNameAndType cnat = (ConstantNameAndType) (cpg.getConstant(((ConstantMethodref) c).getNameAndTypeIndex())); |
| final ConstantUtf8 cutf8 = (ConstantUtf8) (cpg.getConstant(cnat.getNameIndex())); |
| if (cutf8.getBytes().equals(Const.CONSTRUCTOR_NAME) && (!(o instanceof INVOKESPECIAL)) ) { |
| constraintViolated(o, "Only INVOKESPECIAL is allowed to invoke instance initialization methods."); |
| } |
| if ( (! (cutf8.getBytes().equals(Const.CONSTRUCTOR_NAME)) ) && (cutf8.getBytes().startsWith("<")) ) { |
| constraintViolated(o, |
| "No method with a name beginning with '<' other than the instance initialization methods"+ |
| " may be called by the method invocation instructions."); |
| } |
| } |
| } |
| else{ //if (o instanceof INVOKEINTERFACE) { |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantInterfaceMethodref)) { |
| constraintViolated(o, "Indexing a constant that's not a CONSTANT_InterfaceMethodref but a '"+c+"'."); |
| } |
| // TODO: From time to time check if BCEL allows to detect if the |
| // 'count' operand is consistent with the information in the |
| // CONSTANT_InterfaceMethodref and if the last operand is zero. |
| // By now, BCEL hides those two operands because they're superfluous. |
| |
| // Invoked method must not be <init> or <clinit> |
| final ConstantNameAndType cnat = |
| (ConstantNameAndType) (cpg.getConstant(((ConstantInterfaceMethodref)c).getNameAndTypeIndex())); |
| final String name = ((ConstantUtf8) (cpg.getConstant(cnat.getNameIndex()))).getBytes(); |
| if (name.equals(Const.CONSTRUCTOR_NAME)) { |
| constraintViolated(o, "Method to invoke must not be '"+Const.CONSTRUCTOR_NAME+"'."); |
| } |
| if (name.equals(Const.STATIC_INITIALIZER_NAME)) { |
| constraintViolated(o, "Method to invoke must not be '"+Const.STATIC_INITIALIZER_NAME+"'."); |
| } |
| } |
| |
| // The LoadClassType is the method-declaring class, so we have to check the other types. |
| |
| Type t = o.getReturnType(cpg); |
| if (t instanceof ArrayType) { |
| t = ((ArrayType) t).getBasicType(); |
| } |
| if (t instanceof ObjectType) { |
| final Verifier v = VerifierFactory.getVerifier(((ObjectType) t).getClassName()); |
| final VerificationResult vr = v.doPass2(); |
| if (vr.getStatus() != VerificationResult.VERIFIED_OK) { |
| constraintViolated(o, "Return type class/interface could not be verified successfully: '"+vr.getMessage()+"'."); |
| } |
| } |
| |
| final Type[] ts = o.getArgumentTypes(cpg); |
| for (final Type element : ts) { |
| t = element; |
| if (t instanceof ArrayType) { |
| t = ((ArrayType) t).getBasicType(); |
| } |
| if (t instanceof ObjectType) { |
| final Verifier v = VerifierFactory.getVerifier(((ObjectType) t).getClassName()); |
| final VerificationResult vr = v.doPass2(); |
| if (vr.getStatus() != VerificationResult.VERIFIED_OK) { |
| constraintViolated(o, |
| "Argument type class/interface could not be verified successfully: '"+vr.getMessage()+"'."); |
| } |
| } |
| } |
| |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINSTANCEOF(final INSTANCEOF o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantClass)) { |
| constraintViolated(o, "Expecting a CONSTANT_Class operand, but found a '"+c+"'."); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitCHECKCAST(final CHECKCAST o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantClass)) { |
| constraintViolated(o, "Expecting a CONSTANT_Class operand, but found a '"+c+"'."); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitNEW(final NEW o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantClass)) { |
| constraintViolated(o, "Expecting a CONSTANT_Class operand, but found a '"+c+"'."); |
| } |
| else{ |
| final ConstantUtf8 cutf8 = (ConstantUtf8) (cpg.getConstant( ((ConstantClass) c).getNameIndex() )); |
| final Type t = Type.getType("L"+cutf8.getBytes()+";"); |
| if (t instanceof ArrayType) { |
| constraintViolated(o, "NEW must not be used to create an array."); |
| } |
| } |
| |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitMULTIANEWARRAY(final MULTIANEWARRAY o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantClass)) { |
| constraintViolated(o, "Expecting a CONSTANT_Class operand, but found a '"+c+"'."); |
| } |
| final int dimensions2create = o.getDimensions(); |
| if (dimensions2create < 1) { |
| constraintViolated(o, "Number of dimensions to create must be greater than zero."); |
| } |
| final Type t = o.getType(cpg); |
| if (t instanceof ArrayType) { |
| final int dimensions = ((ArrayType) t).getDimensions(); |
| if (dimensions < dimensions2create) { |
| constraintViolated(o, |
| "Not allowed to create array with more dimensions ('"+dimensions2create+ |
| "') than the one referenced by the CONSTANT_Class '"+t+"'."); |
| } |
| } |
| else{ |
| constraintViolated(o, "Expecting a CONSTANT_Class referencing an array type."+ |
| " [Constraint not found in The Java Virtual Machine Specification, Second Edition, 4.8.1]"); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitANEWARRAY(final ANEWARRAY o) { |
| indexValid(o, o.getIndex()); |
| final Constant c = cpg.getConstant(o.getIndex()); |
| if (! (c instanceof ConstantClass)) { |
| constraintViolated(o, "Expecting a CONSTANT_Class operand, but found a '"+c+"'."); |
| } |
| final Type t = o.getType(cpg); |
| if (t instanceof ArrayType) { |
| final int dimensions = ((ArrayType) t).getDimensions(); |
| if (dimensions > Const.MAX_ARRAY_DIMENSIONS) { |
| constraintViolated(o, |
| "Not allowed to create an array with more than "+ Const.MAX_ARRAY_DIMENSIONS + " dimensions;"+ |
| " actual: " + dimensions); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitNEWARRAY(final NEWARRAY o) { |
| final byte t = o.getTypecode(); |
| if (! ( (t == Const.T_BOOLEAN) || |
| (t == Const.T_CHAR) || |
| (t == Const.T_FLOAT) || |
| (t == Const.T_DOUBLE) || |
| (t == Const.T_BYTE) || |
| (t == Const.T_SHORT) || |
| (t == Const.T_INT) || |
| (t == Const.T_LONG) ) ) { |
| constraintViolated(o, "Illegal type code '+t+' for 'atype' operand."); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitILOAD(final ILOAD o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitFLOAD(final FLOAD o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitALOAD(final ALOAD o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitISTORE(final ISTORE o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitFSTORE(final FSTORE o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitASTORE(final ASTORE o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitIINC(final IINC o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitRET(final RET o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."); |
| } |
| else{ |
| final int maxminus1 = max_locals()-1; |
| if (idx > maxminus1) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-1 '"+maxminus1+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitLLOAD(final LLOAD o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."+ |
| " [Constraint by JustIce as an analogon to the single-slot xLOAD/xSTORE instructions; may not happen anyway.]"); |
| } |
| else{ |
| final int maxminus2 = max_locals()-2; |
| if (idx > maxminus2) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-2 '"+maxminus2+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitDLOAD(final DLOAD o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."+ |
| " [Constraint by JustIce as an analogon to the single-slot xLOAD/xSTORE instructions; may not happen anyway.]"); |
| } |
| else{ |
| final int maxminus2 = max_locals()-2; |
| if (idx > maxminus2) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-2 '"+maxminus2+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitLSTORE(final LSTORE o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."+ |
| " [Constraint by JustIce as an analogon to the single-slot xLOAD/xSTORE instructions; may not happen anyway.]"); |
| } |
| else{ |
| final int maxminus2 = max_locals()-2; |
| if (idx > maxminus2) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-2 '"+maxminus2+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitDSTORE(final DSTORE o) { |
| final int idx = o.getIndex(); |
| if (idx < 0) { |
| constraintViolated(o, "Index '"+idx+"' must be non-negative."+ |
| " [Constraint by JustIce as an analogon to the single-slot xLOAD/xSTORE instructions; may not happen anyway.]"); |
| } |
| else{ |
| final int maxminus2 = max_locals()-2; |
| if (idx > maxminus2) { |
| constraintViolated(o, "Index '"+idx+"' must not be greater than max_locals-2 '"+maxminus2+"'."); |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitLOOKUPSWITCH(final LOOKUPSWITCH o) { |
| final int[] matchs = o.getMatchs(); |
| int max = Integer.MIN_VALUE; |
| for (int i=0; i<matchs.length; i++) { |
| if (matchs[i] == max && i != 0) { |
| constraintViolated(o, "Match '"+matchs[i]+"' occurs more than once."); |
| } |
| if (matchs[i] < max) { |
| constraintViolated(o, "Lookup table must be sorted but isn't."); |
| } |
| else{ |
| max = matchs[i]; |
| } |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitTABLESWITCH(final TABLESWITCH o) { |
| // "high" must be >= "low". We cannot check this, as BCEL hides |
| // it from us. |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitPUTSTATIC(final PUTSTATIC o) { |
| try { |
| final String field_name = o.getFieldName(cpg); |
| final JavaClass jc = Repository.lookupClass(getObjectType(o).getClassName()); |
| final Field[] fields = jc.getFields(); |
| Field f = null; |
| for (final Field field : fields) { |
| if (field.getName().equals(field_name)) { |
| f = field; |
| break; |
| } |
| } |
| if (f == null) { |
| throw new AssertionViolatedException("Field '" + field_name + "' not found in " + jc.getClassName()); |
| } |
| |
| if (f.isFinal()) { |
| if (!(myOwner.getClassName().equals(getObjectType(o).getClassName()))) { |
| constraintViolated(o, |
| "Referenced field '"+f+"' is final and must therefore be declared in the current class '"+ |
| myOwner.getClassName()+"' which is not the case: it is declared in '"+o.getReferenceType(cpg)+"'."); |
| } |
| } |
| |
| if (! (f.isStatic())) { |
| constraintViolated(o, "Referenced field '"+f+"' is not static which it should be."); |
| } |
| |
| final String meth_name = Repository.lookupClass(myOwner.getClassName()).getMethods()[method_no].getName(); |
| |
| // If it's an interface, it can be set only in <clinit>. |
| if ((!(jc.isClass())) && (!(meth_name.equals(Const.STATIC_INITIALIZER_NAME)))) { |
| constraintViolated(o, "Interface field '"+f+"' must be set in a '"+Const.STATIC_INITIALIZER_NAME+"' method."); |
| } |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitGETSTATIC(final GETSTATIC o) { |
| try { |
| final String field_name = o.getFieldName(cpg); |
| final JavaClass jc = Repository.lookupClass(getObjectType(o).getClassName()); |
| final Field[] fields = jc.getFields(); |
| Field f = null; |
| for (final Field field : fields) { |
| if (field.getName().equals(field_name)) { |
| f = field; |
| break; |
| } |
| } |
| if (f == null) { |
| throw new AssertionViolatedException("Field '" + field_name + "' not found in " + jc.getClassName()); |
| } |
| |
| if (! (f.isStatic())) { |
| constraintViolated(o, "Referenced field '"+f+"' is not static which it should be."); |
| } |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /* Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| //public void visitPUTFIELD(PUTFIELD o) { |
| // for performance reasons done in Pass 3b |
| //} |
| |
| /* Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| //public void visitGETFIELD(GETFIELD o) { |
| // for performance reasons done in Pass 3b |
| //} |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINVOKEDYNAMIC(final INVOKEDYNAMIC o) { |
| throw new RuntimeException("INVOKEDYNAMIC instruction is not supported at this time"); |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINVOKEINTERFACE(final INVOKEINTERFACE o) { |
| try { |
| // INVOKEINTERFACE is a LoadClass; the Class where the referenced method is declared in, |
| // is therefore resolved/verified. |
| // INVOKEINTERFACE is an InvokeInstruction, the argument and return types are resolved/verified, |
| // too. So are the allowed method names. |
| final String classname = o.getClassName(cpg); |
| final JavaClass jc = Repository.lookupClass(classname); |
| final Method m = getMethodRecursive(jc, o); |
| if (m == null) { |
| constraintViolated(o, "Referenced method '"+o.getMethodName(cpg)+"' with expected signature '"+o.getSignature(cpg)+ |
| "' not found in class '"+jc.getClassName()+"'."); |
| } |
| if (jc.isClass()) { |
| constraintViolated(o, "Referenced class '"+jc.getClassName()+"' is a class, but not an interface as expected."); |
| } |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| /** |
| * Looks for the method referenced by the given invoke instruction in the given class |
| * or its super classes and super interfaces. |
| * @param jc the class that defines the referenced method |
| * @param invoke the instruction that references the method |
| * @return the referenced method or null if not found. |
| */ |
| private Method getMethodRecursive(final JavaClass jc, final InvokeInstruction invoke) throws ClassNotFoundException{ |
| Method m; |
| //look in the given class |
| m = getMethod(jc, invoke); |
| if(m != null) { |
| //method found in given class |
| return m; |
| } |
| //method not found, look in super classes |
| for (final JavaClass superclass : jc.getSuperClasses()) { |
| m = getMethod(superclass, invoke); |
| if(m != null) { |
| //method found in super class |
| return m; |
| } |
| } |
| //method not found, look in super interfaces |
| for (final JavaClass superclass : jc.getInterfaces()) { |
| m = getMethod(superclass, invoke); |
| if(m != null) { |
| //method found in super interface |
| return m; |
| } |
| } |
| //method not found in the hierarchy |
| return null; |
| } |
| /** |
| * Looks for the method referenced by the given invoke instruction in the given class. |
| * @param jc the class that defines the referenced method |
| * @param invoke the instruction that references the method |
| * @return the referenced method or null if not found. |
| */ |
| private Method getMethod(final JavaClass jc, final InvokeInstruction invoke) { |
| final Method[] ms = jc.getMethods(); |
| for (final Method element : ms) { |
| if ( (element.getName().equals(invoke.getMethodName(cpg))) && |
| (Type.getReturnType(element.getSignature()).equals(invoke.getReturnType(cpg))) && |
| (objarrayequals(Type.getArgumentTypes(element.getSignature()), invoke.getArgumentTypes(cpg))) ) { |
| return element; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINVOKESPECIAL(final INVOKESPECIAL o) { |
| try { |
| // INVOKESPECIAL is a LoadClass; the Class where the referenced method is declared in, |
| // is therefore resolved/verified. |
| // INVOKESPECIAL is an InvokeInstruction, the argument and return types are resolved/verified, |
| // too. So are the allowed method names. |
| final String classname = o.getClassName(cpg); |
| final JavaClass jc = Repository.lookupClass(classname); |
| final Method m = getMethodRecursive(jc, o); |
| if (m == null) { |
| constraintViolated(o, "Referenced method '"+o.getMethodName(cpg)+"' with expected signature '"+o.getSignature(cpg) |
| +"' not found in class '"+jc.getClassName()+"'."); |
| } |
| |
| JavaClass current = Repository.lookupClass(myOwner.getClassName()); |
| if (current.isSuper()) { |
| |
| if ((Repository.instanceOf( current, jc )) && (!current.equals(jc))) { |
| |
| if (! (o.getMethodName(cpg).equals(Const.CONSTRUCTOR_NAME) )) { |
| // Special lookup procedure for ACC_SUPER classes. |
| |
| int supidx = -1; |
| |
| Method meth = null; |
| while (supidx != 0) { |
| supidx = current.getSuperclassNameIndex(); |
| current = Repository.lookupClass(current.getSuperclassName()); |
| |
| final Method[] meths = current.getMethods(); |
| for (final Method meth2 : meths) { |
| if ( (meth2.getName().equals(o.getMethodName(cpg))) && |
| (Type.getReturnType(meth2.getSignature()).equals(o.getReturnType(cpg))) && |
| (objarrayequals(Type.getArgumentTypes(meth2.getSignature()), o.getArgumentTypes(cpg))) ) { |
| meth = meth2; |
| break; |
| } |
| } |
| if (meth != null) { |
| break; |
| } |
| } |
| if (meth == null) { |
| constraintViolated(o, "ACC_SUPER special lookup procedure not successful: method '"+ |
| o.getMethodName(cpg)+"' with proper signature not declared in superclass hierarchy."); |
| } |
| } |
| } |
| } |
| |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| |
| } |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINVOKESTATIC(final INVOKESTATIC o) { |
| try { |
| // INVOKESTATIC is a LoadClass; the Class where the referenced method is declared in, |
| // is therefore resolved/verified. |
| // INVOKESTATIC is an InvokeInstruction, the argument and return types are resolved/verified, |
| // too. So are the allowed method names. |
| final String classname = o.getClassName(cpg); |
| final JavaClass jc = Repository.lookupClass(classname); |
| final Method m = getMethodRecursive(jc, o); |
| if (m == null) { |
| constraintViolated(o, "Referenced method '"+o.getMethodName(cpg)+"' with expected signature '"+ |
| o.getSignature(cpg) +"' not found in class '"+jc.getClassName()+"'."); |
| } else if (! (m.isStatic())) { // implies it's not abstract, verified in pass 2. |
| constraintViolated(o, "Referenced method '"+o.getMethodName(cpg)+"' has ACC_STATIC unset."); |
| } |
| |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| |
| /** Checks if the constraints of operands of the said instruction(s) are satisfied. */ |
| @Override |
| public void visitINVOKEVIRTUAL(final INVOKEVIRTUAL o) { |
| try { |
| // INVOKEVIRTUAL is a LoadClass; the Class where the referenced method is declared in, |
| // is therefore resolved/verified. |
| // INVOKEVIRTUAL is an InvokeInstruction, the argument and return types are resolved/verified, |
| // too. So are the allowed method names. |
| final String classname = o.getClassName(cpg); |
| final JavaClass jc = Repository.lookupClass(classname); |
| final Method m = getMethodRecursive(jc, o); |
| if (m == null) { |
| constraintViolated(o, "Referenced method '"+o.getMethodName(cpg)+"' with expected signature '"+ |
| o.getSignature(cpg)+"' not found in class '"+jc.getClassName()+"'."); |
| } |
| if (! (jc.isClass())) { |
| constraintViolated(o, "Referenced class '"+jc.getClassName()+"' is an interface, but not a class as expected."); |
| } |
| |
| } catch (final ClassNotFoundException e) { |
| // FIXME: maybe not the best way to handle this |
| throw new AssertionViolatedException("Missing class: " + e, e); |
| } |
| } |
| |
| |
| // WIDE stuff is BCEL-internal and cannot be checked here. |
| |
| /** |
| * A utility method like equals(Object) for arrays. |
| * The equality of the elements is based on their equals(Object) |
| * method instead of their object identity. |
| */ |
| private boolean objarrayequals(final Object[] o, final Object[] p) { |
| if (o.length != p.length) { |
| return false; |
| } |
| |
| for (int i=0; i<o.length; i++) { |
| if (! (o[i].equals(p[i])) ) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } |
| } |