private void checkArrayArgs(MethodNode methodNode, Frame<BasicValue> logMessageFrame, Frame<BasicValue> arraySizeFrame, int lineNumber, MethodInsnNode methodInsn, int messageIndex, int arrayIndex) { BasicValue arraySizeObject = getStackValue(arraySizeFrame, methodInsn, arrayIndex); if (arraySizeObject instanceof ArraySizeBasicValue == false) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "Could not determine size of array")); return; } ArraySizeBasicValue arraySize = (ArraySizeBasicValue) arraySizeObject; PlaceHolderStringBasicValue logMessageLength = checkLogMessageConsistency(methodNode, logMessageFrame, lineNumber, methodInsn, messageIndex, arraySize.minValue); if (logMessageLength == null) { return; } if (arraySize.minValue != arraySize.maxValue) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "Multiple parameter arrays with conflicting sizes")); return; } assert logMessageLength.minValue == logMessageLength.maxValue && arraySize.minValue == arraySize.maxValue; if (logMessageLength.minValue != arraySize.minValue) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "Expected " + logMessageLength.minValue + " arguments but got " + arraySize.minValue)); return; } }
private PlaceHolderStringBasicValue checkLogMessageConsistency(MethodNode methodNode, Frame<BasicValue> logMessageFrame, int lineNumber, MethodInsnNode methodInsn, int messageIndex, int argsSize) { BasicValue logMessageLengthObject = getStackValue(logMessageFrame, methodInsn, messageIndex); if (logMessageLengthObject instanceof PlaceHolderStringBasicValue == false) { if (argsSize > 0) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "First argument must be a string constant so that we can statically ensure proper place holder usage")); } else { // don't check logger usage for logger.warn(someObject) } return null; } PlaceHolderStringBasicValue logMessageLength = (PlaceHolderStringBasicValue) logMessageLengthObject; if (logMessageLength.minValue != logMessageLength.maxValue) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "Multiple log messages with conflicting number of place holders")); return null; } return logMessageLength; }
@Override public BasicValue newOperation(AbstractInsnNode insnNode) throws AnalyzerException { switch (insnNode.getOpcode()) { case ICONST_0: return new IntegerConstantBasicValue(Type.INT_TYPE, 0); case ICONST_1: return new IntegerConstantBasicValue(Type.INT_TYPE, 1); case ICONST_2: return new IntegerConstantBasicValue(Type.INT_TYPE, 2); case ICONST_3: return new IntegerConstantBasicValue(Type.INT_TYPE, 3); case ICONST_4: return new IntegerConstantBasicValue(Type.INT_TYPE, 4); case ICONST_5: return new IntegerConstantBasicValue(Type.INT_TYPE, 5); case BIPUSH: case SIPUSH: return new IntegerConstantBasicValue(Type.INT_TYPE, ((IntInsnNode)insnNode).operand); case Opcodes.LDC: { Object constant = ((LdcInsnNode)insnNode).cst; if (constant instanceof Integer) { return new IntegerConstantBasicValue(Type.INT_TYPE, (Integer)constant); } else { return super.newOperation(insnNode); } } default: return super.newOperation(insnNode); } }
@Override public BasicValue newValue(final Type t) { if (t != null) { final ValueHolderIden iden = HOLDERS.get(t.getDescriptor()); if (iden != null) { final ReplacingBasicValue v = ReplacingBasicValue.create(t, iden, index++, valueList); v.markFunctionReturn(); return v; } // We need to track use of the "this" objectref if ((t.getSort() == Type.OBJECT) && className.equals(t.getInternalName())) { final ReplacingBasicValue rbValue = ReplacingBasicValue.create(t, null, 0, valueList); rbValue.setThis(); return rbValue; } } return super.newValue(t); }
@Override public BasicValue binaryOperation(final AbstractInsnNode insn, final BasicValue value1, final BasicValue value2) throws AnalyzerException { /* * We're looking for the assignment of a local holder objectref to a member variable. * If we spot that, then the local holder can't be replaced, since we don't (yet) * have the mechanics to replace the member variable with the holder's members or * to assign all of them when this happens. */ if (insn.getOpcode() == Opcodes.PUTFIELD) { if (value2.isReference() && (value1 instanceof ReplacingBasicValue)) { final ReplacingBasicValue possibleThis = (ReplacingBasicValue) value1; if (possibleThis.isThis() && (value2 instanceof ReplacingBasicValue)) { // if this is a reference for a holder class, we can't replace it if (HOLDERS.get(value2.getType().getDescriptor()) != null) { final ReplacingBasicValue localRef = (ReplacingBasicValue) value2; localRef.setAssignedToMember(); } } } } return super.binaryOperation(insn, value1, value2); }
@Override public BasicValue naryOperation(final AbstractInsnNode insn, final List<? extends BasicValue> values) throws AnalyzerException { if (insn instanceof MethodInsnNode) { boolean skipOne = insn.getOpcode() != Opcodes.INVOKESTATIC; // Note if the argument is a holder, and is used as a function argument for(BasicValue value : values) { // if non-static method, skip over the receiver if (skipOne) { skipOne = false; continue; } if (value instanceof ReplacingBasicValue) { final ReplacingBasicValue argument = (ReplacingBasicValue) value; argument.setFunctionArgument(); } } } return super.naryOperation(insn, values); }
/** {@inheritDoc} */ @Override public BasicValue newValue(Type type) { if (type == null) { return BasicValue.UNINITIALIZED_VALUE; } switch (type.getSort()) { case Type.ARRAY: String desc = type.getDescriptor(); if (desc.equals("[Z")) return BOOLEAN_ARRAY; else if (desc.equals("[I")) return INT_ARRAY; else if (desc.equals("[B")) return BYTE_ARRAY; else return super.newValue(type); default: return super.newValue(type); } }
/** {@inheritDoc} */ @Override public BasicValue newValue(final Type type) { if (type == null) { return BasicValue.UNINITIALIZED_VALUE; } switch (type.getSort()) { case Type.BOOLEAN: return BOOLEAN_VALUE; case Type.ARRAY: if (type.getElementType() == Type.BOOLEAN_TYPE) return BOOLEAN_ARRAY; default: return super.newValue(type); } }
/** {@inheritDoc} */ @Override public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException { if (insn.getOpcode() == ICONST_0) { return BOOLEAN_VALUE; } else if (insn.getOpcode() == ICONST_1) { return BOOLEAN_VALUE; } else if (insn.getOpcode() == Opcodes.GETSTATIC) { FieldInsnNode fieldNode = (FieldInsnNode) insn; if (BooleanTestabilityTransformation.isTransformedField(fieldNode.owner, fieldNode.name, fieldNode.desc)) return BOOLEAN_VALUE; } return super.newOperation(insn); }
/** {@inheritDoc} */ @Override public BasicValue copyOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { if (insn.getOpcode() == Opcodes.ILOAD) { VarInsnNode varNode = (VarInsnNode) insn; if (isStatic) { if (varNode.var < types.length) { if (types[varNode.var] == Type.BOOLEAN_TYPE) { return BOOLEAN_VALUE; } } } else { if (varNode.var > 0 && varNode.var - 1 < types.length) { if (types[varNode.var - 1] == Type.BOOLEAN_TYPE) { return BOOLEAN_VALUE; } } } } return super.copyOperation(insn, value); }
@SuppressWarnings("rawtypes") /** {@inheritDoc} */ @Override public BasicValue naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { if (insn instanceof MethodInsnNode) { MethodInsnNode mi = (MethodInsnNode) insn; if (Type.getReturnType(BooleanTestabilityTransformation.getOriginalDesc(mi.owner, mi.name, mi.desc)) == Type.BOOLEAN_TYPE) { return BOOLEAN_VALUE; } } return super.naryOperation(insn, values); }
private Object toFrameType(final BasicValue value) { final Type type = value.getType(); switch (type.getSort()) { case Type.BOOLEAN: case Type.BYTE: case Type.INT: case Type.CHAR: case Type.SHORT: return Opcodes.INTEGER; case Type.LONG: return Opcodes.LONG; case Type.FLOAT: return Opcodes.FLOAT; case Type.DOUBLE: return Opcodes.DOUBLE; } final String internalName = type.getInternalName(); if (ACONST_NULL_TYPE.equals(type)) { return Opcodes.NULL; } return internalName; }
private void pushArguments(final MethodVisitor mv, final SwitchEntry switchEntry, final List<Argument> lambdaArguments) { // stack: { ... } for (int i = 0, l = lambdaArguments.size() - 2; i < l; i++) { int iLocal = i < switchEntry.argumentToLocal.length ? switchEntry.argumentToLocal[i] : -1; final BasicValue value = lambdaArguments.get(i).value; if (iLocal >= 0) { mv.visitVarInsn(value.getType().getOpcode(ILOAD), iLocal); } else { pushDefault(mv, value); } } // stack: { ... lambdaArguments} }
@Override public boolean merge(final Frame<? extends BasicValue> frame, final Interpreter<BasicValue> interpreter) throws AnalyzerException { if (force) { // uses the current frame return true; } if (frame instanceof ExtendedFrame && ((ExtendedFrame) frame).force) { init(frame); return true; } return super.merge(frame, interpreter); }
@Override public BasicValue merge(BasicValue v, BasicValue w) { if (v != w && v != null && w != null && !v.equals(w)) { Type t = ((BasicValue) v).getType(); Type u = ((BasicValue) w).getType(); if (t != null && u != null && t.getSort() == Type.OBJECT && u.getSort() == Type.OBJECT) { // could find a common super type here, a bit expensive // TODO: test this with an assignment // like: local1 was CompletableFuture <- store Task ExtendedValue nv = (ExtendedValue) newValue(BasicValue.REFERENCE_VALUE.getType()); nv.undecided = new BasicValue[]{ v, w }; return nv; } } return super.merge(v, w); }
@Test public void uninitializedInTheStack() throws Exception { MethodNode mv = new MethodNode(ACC_PUBLIC, "apply", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null); mv.visitTypeInsn(NEW, "java/lang/Integer"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); // insn 3 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false); // insn 4 mv.visitInsn(ARETURN); mv.visitMaxs(3, 2); Frame<BasicValue>[] frames = new FrameAnalyzer().analyze("com/ea/async/Bogus", mv); assertEquals(3, frames[3].getStackSize()); assertTrue(((ExtendedValue) frames[3].getStack(0)).isUninitialized()); assertFalse(((ExtendedValue) frames[4].getStack(0)).isUninitialized()); }
@Test public void uninitializedInTheStackCheckOrder() throws Exception { MethodNode mv = new MethodNode(ACC_PUBLIC, "apply", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null); mv.visitIntInsn(SIPUSH, 99); mv.visitTypeInsn(NEW, "java/lang/Integer"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); // insn 4 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false); // insn 5 mv.visitInsn(SWAP); mv.visitInsn(POP); mv.visitInsn(ARETURN); mv.visitMaxs(4, 2); Frame<BasicValue>[] frames = new FrameAnalyzer().analyze("com/ea/async/Bogus", mv); assertEquals(3, frames[3].getStackSize()); assertTrue(((ExtendedValue) frames[4].getStack(1)).isUninitialized()); assertFalse(((ExtendedValue) frames[5].getStack(1)).isUninitialized()); }
@Override protected boolean isSubTypeOf(final BasicValue value, final BasicValue expected) { Type expectedType = expected.getType(); Type type = value.getType(); switch (expectedType.getSort()) { case Type.BOOLEAN: case Type.CHAR: case Type.BYTE: case Type.SHORT: case Type.INT: case Type.FLOAT: case Type.LONG: case Type.DOUBLE: return type.equals(expectedType); case Type.ARRAY: case Type.OBJECT: // We are transforming valid bytecode to (hopefully) valid bytecode // hence pairs of "value" and "expected" must be compatible return true;//isAssignableFrom(expectedType, type); default: throw new Error("Internal error"); } }
public InstructionGraphNode setGraphNode(final AbstractInsnNode insn, final BasicValue resultValue, final List<BasicValue> predecessors) { if (graphNodes == null) { // initialize with a list of null values graphNodes = Lists .newArrayList(new InstructionGraphNode[instructions.size()]); } final int index = instructions.indexOf(insn); InstructionGraphNode node = graphNodes.get(index); if (node == null) { node = new InstructionGraphNode(insn, resultValue); graphNodes.set(index, node); } node.addPredecessors(predecessors); return node; }
/** * Checks a given class. * * @param cr * a <code>ClassReader</code> that contains bytecode for the * analysis. * @param loader * a <code>ClassLoader</code> which will be used to load * referenced classes. This is useful if you are verifiying * multiple interdependent classes. * @param dump * true if bytecode should be printed out not only when errors * are found. * @param pw * write where results going to be printed */ public static void verify(final ClassReader cr, final ClassLoader loader, final boolean dump, final PrintWriter pw) { ClassNode cn = new ClassNode(); cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG); Type syperType = cn.superName == null ? null : Type .getObjectType(cn.superName); List<MethodNode> methods = cn.methods; List<Type> interfaces = new ArrayList<Type>(); for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) { interfaces.add(Type.getObjectType(i.next())); } for (int i = 0; i < methods.size(); ++i) { MethodNode method = methods.get(i); SimpleVerifier verifier = new SimpleVerifier( Type.getObjectType(cn.name), syperType, interfaces, (cn.access & Opcodes.ACC_INTERFACE) != 0); Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier); if (loader != null) { verifier.setClassLoader(loader); } try { a.analyze(cn.name, method); if (!dump) { continue; } } catch (Exception e) { e.printStackTrace(pw); } printAnalyzerResult(method, a, pw); } pw.flush(); }
static void printAnalyzerResult(MethodNode method, Analyzer<BasicValue> a, final PrintWriter pw) { Frame<BasicValue>[] frames = a.getFrames(); Textifier t = new Textifier(); TraceMethodVisitor mv = new TraceMethodVisitor(t); pw.println(method.name + method.desc); for (int j = 0; j < method.instructions.size(); ++j) { method.instructions.get(j).accept(mv); StringBuilder sb = new StringBuilder(); Frame<BasicValue> f = frames[j]; if (f == null) { sb.append('?'); } else { for (int k = 0; k < f.getLocals(); ++k) { sb.append(getShortName(f.getLocal(k).toString())) .append(' '); } sb.append(" : "); for (int k = 0; k < f.getStackSize(); ++k) { sb.append(getShortName(f.getStack(k).toString())) .append(' '); } } while (sb.length() < method.maxStack + method.maxLocals + 1) { sb.append(' '); } pw.print(Integer.toString(j + 100000).substring(1)); pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1)); } for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { method.tryCatchBlocks.get(j).accept(mv); pw.print(" " + t.text.get(t.text.size() - 1)); } pw.println(); }
/** * Constructs a new {@link CheckMethodAdapter} object. This method adapter * will perform basic data flow checks. For instance in a method whose * signature is <tt>void m ()</tt>, the invalid instruction IRETURN, or the * invalid sequence IADD L2I will be detected. * * @param access * the method's access flags. * @param name * the method's name. * @param desc * the method's descriptor (see {@link Type Type}). * @param cmv * the method visitor to which this adapter must delegate calls. * @param labels * a map of already visited labels (in other methods). */ public CheckMethodAdapter(final int access, final String name, final String desc, final MethodVisitor cmv, final Map<Label, Integer> labels) { this(new MethodNode(Opcodes.ASM5, access, name, desc, null, null) { @Override public void visitEnd() { Analyzer<BasicValue> a = new Analyzer<BasicValue>( new BasicVerifier()); try { a.analyze("dummy", this); } catch (Exception e) { if (e instanceof IndexOutOfBoundsException && maxLocals == 0 && maxStack == 0) { throw new RuntimeException( "Data flow checking option requires valid, non zero maxLocals and maxStack values."); } e.printStackTrace(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); CheckClassAdapter.printAnalyzerResult(this, a, pw); pw.close(); throw new RuntimeException(e.getMessage() + ' ' + sw.toString()); } accept(cmv); } }, labels); this.access = access; }
private void checkFixedArityArgs(MethodNode methodNode, Frame<BasicValue> logMessageFrame, int lineNumber, MethodInsnNode methodInsn, int messageIndex, int positionalArgsLength) { PlaceHolderStringBasicValue logMessageLength = checkLogMessageConsistency(methodNode, logMessageFrame, lineNumber, methodInsn, messageIndex, positionalArgsLength); if (logMessageLength == null) { return; } if (logMessageLength.minValue != positionalArgsLength) { wrongUsageCallback.accept(new WrongLoggerUsage(className, methodNode.name, methodInsn.name, lineNumber, "Expected " + logMessageLength.minValue + " arguments but got " + positionalArgsLength)); return; } }
@Override public BasicValue newOperation(AbstractInsnNode insnNode) throws AnalyzerException { if (insnNode.getOpcode() == Opcodes.LDC) { Object constant = ((LdcInsnNode) insnNode).cst; if (constant instanceof String) { return new PlaceHolderStringBasicValue(calculateNumberOfPlaceHolders((String) constant)); } } return super.newOperation(insnNode); }
@Override public BasicValue merge(BasicValue value1, BasicValue value2) { if (value1 instanceof PlaceHolderStringBasicValue && value2 instanceof PlaceHolderStringBasicValue && value1.equals(value2) == false) { PlaceHolderStringBasicValue c1 = (PlaceHolderStringBasicValue) value1; PlaceHolderStringBasicValue c2 = (PlaceHolderStringBasicValue) value2; return new PlaceHolderStringBasicValue(Math.min(c1.minValue, c2.minValue), Math.max(c1.maxValue, c2.maxValue)); } return super.merge(value1, value2); }
@Override public BasicValue unaryOperation(AbstractInsnNode insnNode, BasicValue value) throws AnalyzerException { if (insnNode.getOpcode() == Opcodes.ANEWARRAY && value instanceof IntegerConstantBasicValue) { IntegerConstantBasicValue constantBasicValue = (IntegerConstantBasicValue) value; String desc = ((TypeInsnNode) insnNode).desc; return new ArraySizeBasicValue(Type.getType("[" + Type.getObjectType(desc)), constantBasicValue.minValue, constantBasicValue.maxValue); } return super.unaryOperation(insnNode, value); }
@Override public BasicValue ternaryOperation(AbstractInsnNode insnNode, BasicValue value1, BasicValue value2, BasicValue value3) throws AnalyzerException { if (insnNode.getOpcode() == Opcodes.AASTORE && value1 instanceof ArraySizeBasicValue) { return value1; } return super.ternaryOperation(insnNode, value1, value2, value3); }
private static ReplacingBasicValue filterReplacement(final BasicValue basicValue) { if (basicValue instanceof ReplacingBasicValue) { final ReplacingBasicValue replacingValue = (ReplacingBasicValue) basicValue; if (replacingValue.isReplaceable()) { return replacingValue; } } return null; }
@Override public BasicValue newOperation(final AbstractInsnNode insn) throws AnalyzerException { if (insn.getOpcode() == Opcodes.NEW) { final TypeInsnNode t = (TypeInsnNode) insn; // if this is for a holder class, we'll replace it final ValueHolderIden iden = HOLDERS.get(t.desc); if (iden != null) { return ReplacingBasicValue.create(Type.getObjectType(t.desc), iden, index++, valueList); } } return super.newOperation(insn); }
@Override public BasicValue unaryOperation(final AbstractInsnNode insn, final BasicValue value) throws AnalyzerException { /* * We're looking for the assignment of an operator member variable that's a holder to a local * objectref. If we spot that, we can't replace the local objectref (at least not * until we do the work to replace member variable holders). * * Note that a GETFIELD does not call newValue(), as would happen for a local variable, so we're * emulating that here. */ if ((insn.getOpcode() == Opcodes.GETFIELD) && (value instanceof ReplacingBasicValue)) { final ReplacingBasicValue possibleThis = (ReplacingBasicValue) value; if (possibleThis.isThis()) { final FieldInsnNode fieldInsn = (FieldInsnNode) insn; if (HOLDERS.get(fieldInsn.desc) != null) { final BasicValue fetchedField = super.unaryOperation(insn, value); final ReplacingBasicValue replacingValue = ReplacingBasicValue.create(fetchedField.getType(), null, -1, valueList); replacingValue.setAssignedToMember(); return replacingValue; } } } return super.unaryOperation(insn, value); }
@Override public BasicValue newValue(Type type) { if(type == null) { return BasicValue.UNINITIALIZED_VALUE; } if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { return new BasicValue(type); } return super.newValue(type); }
@Override public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException { if(insn.getOpcode() == Opcodes.NEW) { return new NewValue(Type.getObjectType(((TypeInsnNode) insn).desc), false, insn); } return super.newOperation(insn); }
@Override public BasicValue copyOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { if(insn.getOpcode() == Opcodes.DUP) { if(value instanceof NewValue) { NewValue newValue = (NewValue)value; if(!newValue.isDupped) { return new NewValue(newValue.getType(), true, insn); } } } return super.copyOperation(insn, value); }