Java tutorial
/* * Copyright 2009-2012 Michael Dalton * * Licensed 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 jtaint; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; /* In many classes in JavaTaint, we perform instrumentation or analysis * on a particular set of methods. For example, we update taint information * each time append() or insert() is called on a StringBuffer. However, * often-times these methods are implemented in terms of one another. * For example, StringBuffer.deleteCharAt(i) may be defined as * StringBuffer.delete(i, i+1). We don't want to 'double-count' our * instrumentation -- in the above example, we could naively perform the delete * operation _twice_ - once when deleteCharAt() is called, and once when * delete() is called in the body of deleteCharAt(). * * To avoid this situation we increment a simple per-object counter when * entering an instrumented method, and then invoke the original method. When * the original method finishes, we decrement the counter. If the counter is * now zero, then and only then do we execute the instrumentation code * (e.g., updating taint in the case of StringBuffer). * * Effectively our counter acts as a simple recursive lock. However, its * purpose is to prevent instrumentation code from being executed * twice (or more) for a given instrumented method -- this 'lock' is not * intended to provide any form of synchronization between multiple threads. * * As a final caveat, we often instrument a hierarchy of classes which * together implement an interface, such as ServletOutputStream. Each * instrumented class will have its own lock field, which is a recipe for * disaster (i.e. if class A has a lock and its superclass B has a lock, then * A may think the object is locked when B does not or vice-versa because there * are two different lock fields, one used by methods defined in A, and one * by methods defined in B). * * To prevent this from occuring, we access lock fields solely invoking * getter/setter methods via the invokevirtual instruction. Any instrumented * class will override the getter/setter methods in its superclasses, thus * ensuring that all superclasses use the same lock. * * Here is a sample instrumentation of StringBuffer.deleteCharAt(i) * public StringBuffer deleteCharAt(int i) { * onMethodEnter(); * incLock(); * try { * realDeleteCharAt(i); * if (decAndTestLock() == 0); * onUnlocked(); * return this; * } catch (Throwable th) { * decAndTestLock(); * throw th; * } * } * * If an exception occurs, it is assumed that no data was updated and * thus no instrumentation code should be executed, so we only decrement the * lock accordingly. */ public abstract class InstrumentationLockBuilder implements Opcodes { protected final MethodVisitor mv; protected final int version; protected final String className, methodName, methodDesc; public InstrumentationLockBuilder(MethodVisitor mv, int version, String className, String methodName, String methodDesc) { this.mv = mv; this.version = version; this.className = className; this.methodName = methodName; this.methodDesc = methodDesc; } public final void build() { mv.visitCode(); boolean isVoid = Type.VOID_TYPE.equals(Type.getReturnType(methodDesc)); onMethodEnter(); /* Increment lock */ mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, className, ByteCodeUtil.internalName("incLock"), "()V"); /* Now wrap the real method invocation */ Label start = new Label(), end = new Label(), handler = new Label(); mv.visitTryCatchBlock(start, end, handler, null); mv.visitLabel(start); mv.visitVarInsn(ALOAD, 0); int l = 1; Type[] t = Type.getArgumentTypes(methodDesc); for (int i = 0; i < t.length; l += t[i].getSize(), i++) mv.visitVarInsn(t[i].getOpcode(ILOAD), l); mv.visitMethodInsn(INVOKEVIRTUAL, className, ByteCodeUtil.internalName(methodName), methodDesc); if (!isVoid) mv.visitInsn(POP); /* Now decrement lock, and see if the result is zero. If so, * we're now unlocked and can update the taint information */ mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, className, ByteCodeUtil.internalName("decAndTestLock"), "()I"); Label l0 = new Label(); mv.visitJumpInsn(IFNE, l0); onUnlocked(); mv.visitLabel(l0); if (version == V1_6) mv.visitFrame(F_SAME, 0, null, 0, null); /* XXX TODO All classes that use InstrumentationLockBuilder currently * return either void or the current class. We do not handle the * general case correctly at the moment. */ if (!isVoid) { mv.visitVarInsn(ALOAD, 0); mv.visitInsn(ARETURN); } else { mv.visitInsn(RETURN); } /* Otherwise an exception occurred, decrement lock and re-throw */ mv.visitLabel(end); mv.visitLabel(handler); if (version == V1_6) mv.visitFrame(F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" }); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, className, ByteCodeUtil.internalName("decAndTestLock"), "()I"); mv.visitInsn(POP); mv.visitInsn(ATHROW); /* We want to call the appropriate TaintUtil function with * with the arguments supplied to the instrumented method. * However, any arugments that were originally String or * StringBuilder are transformed into Taint arguments with * an additional 'length' argument, so at worst we require twice * the number of original arguments */ mv.visitMaxs(Math.max(2 * l + 1, 5), l); mv.visitEnd(); } protected void onMethodEnter() { } protected abstract void onUnlocked(); private static final void buildIncLock(ClassVisitor cv, String className) { MethodVisitor mv; mv = cv.visitMethod(ACC_PUBLIC, ByteCodeUtil.internalName("incLock"), "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(DUP); mv.visitFieldInsn(GETFIELD, className, ByteCodeUtil.internalName("lock"), "I"); mv.visitInsn(ICONST_1); mv.visitInsn(IADD); mv.visitFieldInsn(PUTFIELD, className, ByteCodeUtil.internalName("lock"), "I"); mv.visitInsn(RETURN); mv.visitMaxs(3, 1); mv.visitEnd(); } private static final void buildDecAndTestLock(ClassVisitor cv, String className) { MethodVisitor mv; mv = cv.visitMethod(ACC_PUBLIC, ByteCodeUtil.internalName("decAndTestLock"), "()I", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(DUP); mv.visitFieldInsn(GETFIELD, className, ByteCodeUtil.internalName("lock"), "I"); mv.visitInsn(ICONST_1); mv.visitInsn(ISUB); mv.visitInsn(DUP_X1); mv.visitFieldInsn(PUTFIELD, className, ByteCodeUtil.internalName("lock"), "I"); mv.visitInsn(IRETURN); mv.visitMaxs(3, 1); mv.visitEnd(); } public static final void visitEnd(ClassVisitor cv, String className) { cv.visitField(ACC_PRIVATE + ACC_TRANSIENT, ByteCodeUtil.internalName("lock"), "I", null, null).visitEnd(); buildIncLock(cv, className); buildDecAndTestLock(cv, className); } }