ClassVisitor
that processes methods with a
* AllocationMethodAdapter
to instrument heap allocations.
*
* @author jeremymanson@google.com (Jeremy Manson)
* @author fischman@google.com (Ami Fischman) (Original Author)
*/
class AllocationClassAdapter extends ClassVisitor {
private final String recorderClass;
private final String recorderMethod;
public AllocationClassAdapter(ClassVisitor cv, String recorderClass,
String recorderMethod) {
super(Opcodes.ASM5, cv);
this.recorderClass = recorderClass;
this.recorderMethod = recorderMethod;
}
/**
* For each method in the class being instrumented, visitMethod
* is called and the returned MethodVisitor is used to visit the method.
* Note that a new MethodVisitor is constructed for each method.
*/
@Override
public MethodVisitor visitMethod(int access, String base, String desc,
String signature, String[] exceptions) {
MethodVisitor mv =
cv.visitMethod(access, base, desc, signature, exceptions);
if (mv != null) {
// We need to compute stackmaps (see
// AllocationInstrumenter#instrument). This can't really be
// done for old bytecode that contains JSR and RET instructions.
// So, we remove JSRs and RETs.
JSRInlinerAdapter jsria = new JSRInlinerAdapter(
mv, access, base, desc, signature, exceptions);
AllocationMethodAdapter aimv =
new AllocationMethodAdapter(jsria, recorderClass, recorderMethod);
LocalVariablesSorter lvs = new LocalVariablesSorter(access, desc, aimv);
aimv.lvs = lvs;
mv = lvs;
}
return mv;
}
}
AllocationInstrumenter.java 0000664 0000000 0000000 00000017525 12716413241 0045301 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /*
* Copyright (C) 2007 Google Inc.
*
* 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 com.google.monitoring.runtime.instrumentation;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Instruments bytecodes that allocate heap memory to call a recording hook.
* This will add a static invocation to a recorder function to any bytecode that
* looks like it will be allocating heap memory allowing users to implement heap
* profiling schemes.
*
* @author Ami Fischman
* @author Jeremy Manson
*/
public class AllocationInstrumenter implements ClassFileTransformer {
static final Logger logger =
Logger.getLogger(AllocationInstrumenter.class.getName());
// We can rewrite classes loaded by the bootstrap class loader
// iff the agent is loaded by the bootstrap class loader. It is
// always *supposed* to be loaded by the bootstrap class loader, but
// this relies on the Boot-Class-Path attribute in the JAR file always being
// set to the name of the JAR file that contains this agent, which we cannot
// guarantee programmatically.
private static volatile boolean canRewriteBootstrap;
static boolean canRewriteClass(String className, ClassLoader loader) {
// There are two conditions under which we don't rewrite:
// 1. If className was loaded by the bootstrap class loader and
// the agent wasn't (in which case the class being rewritten
// won't be able to call agent methods).
// 2. If it is java.lang.ThreadLocal, which can't be rewritten because the
// JVM depends on its structure.
if (((loader == null) && !canRewriteBootstrap) ||
className.startsWith("java/lang/ThreadLocal")) {
return false;
}
// third_party/java/webwork/*/ognl.jar contains bad class files. Ugh.
if (className.startsWith("ognl/")) {
return false;
}
return true;
}
// No instantiating me except in premain() or in {@link JarClassTransformer}.
AllocationInstrumenter() { }
public static void premain(String agentArgs, Instrumentation inst) {
AllocationRecorder.setInstrumentation(inst);
// Force eager class loading here; we need these classes in order to do
// instrumentation, so if we don't do the eager class loading, we
// get a ClassCircularityError when trying to load and instrument
// this class.
try {
Class.forName("sun.security.provider.PolicyFile");
Class.forName("java.util.ResourceBundle");
Class.forName("java.util.Date");
} catch (Throwable t) {
// NOP
}
if (!inst.isRetransformClassesSupported()) {
System.err.println("Some JDK classes are already loaded and " +
"will not be instrumented.");
}
// Don't try to rewrite classes loaded by the bootstrap class
// loader if this class wasn't loaded by the bootstrap class
// loader.
if (AllocationRecorder.class.getClassLoader() != null) {
canRewriteBootstrap = false;
// The loggers aren't installed yet, so we use println.
System.err.println("Class loading breakage: " +
"Will not be able to instrument JDK classes");
return;
}
canRewriteBootstrap = true;
Listbyte[]
code.
* @param recorderClass the String
internal name of the class
* containing the recorder method to run.
* @param recorderMethod the String
name of the recorder method
* to run.
* @param loader the ClassLoader
for this class.
* @return the instrumented byte[]
code.
*/
public static byte[] instrument(byte[] originalBytes, String recorderClass,
String recorderMethod, ClassLoader loader) {
try {
ClassReader cr = new ClassReader(originalBytes);
// The verifier in JDK7+ requires accurate stackmaps, so we use
// COMPUTE_FRAMES.
ClassWriter cw =
new StaticClassWriter(cr, ClassWriter.COMPUTE_FRAMES, loader);
VerifyingClassAdapter vcw =
new VerifyingClassAdapter(cw, originalBytes, cr.getClassName());
ClassVisitor adapter =
new AllocationClassAdapter(vcw, recorderClass, recorderMethod);
cr.accept(adapter, ClassReader.SKIP_FRAMES);
return vcw.toByteArray();
} catch (RuntimeException e) {
logger.log(Level.WARNING, "Failed to instrument class.", e);
throw e;
} catch (Error e) {
logger.log(Level.WARNING, "Failed to instrument class.", e);
throw e;
}
}
/**
* @see #instrument(byte[], String, String, ClassLoader)
* documentation for the 4-arg version. This is a convenience
* version that uses the recorder in this class.
* @param originalBytes The original version of the class.
* @param loader The ClassLoader of this class.
* @return the instrumented version of this class.
*/
public static byte[] instrument(byte[] originalBytes, ClassLoader loader) {
return instrument(
originalBytes,
"com/google/monitoring/runtime/instrumentation/AllocationRecorder",
"recordAllocation",
loader);
}
}
AllocationMethodAdapter.java 0000664 0000000 0000000 00000057317 12716413241 0045326 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /*
* Copyright (C) 2009 Google Inc.
*
* 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 com.google.monitoring.runtime.instrumentation;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A MethodVisitor
that instruments all heap allocation bytecodes
* to record the allocation being done for profiling.
* Instruments bytecodes that allocate heap memory to call a recording hook.
*
* @author Ami Fischman
*/
class AllocationMethodAdapter extends MethodVisitor {
/**
* The signature string the recorder method must have. The method must be
* static, return void, and take as arguments:
* Object
whose allocation is being
* recorded.
*/
public static void recordAllocation(int count, String desc, Object newObj) {
if (recordingAllocation.get() == Boolean.TRUE) {
return;
} else {
recordingAllocation.set(Boolean.TRUE);
}
// NB: This could be smaller if the defaultSampler were merged with the
// optional samplers. However, you don't need the optional samplers in
// the common case, so I thought I'd save some space.
// Copy value into local variable to prevent NPE that occurs when
// instrumentation field is set to null by this class's shutdown hook
// after another thread passed the null check but has yet to call
// instrumentation.getObjectSize()
Instrumentation instr = instrumentation;
if (instr != null) {
// calling getObjectSize() could be expensive,
// so make sure we do it only once per object
long objectSize = -1;
Sampler[] samplers = additionalSamplers;
if (samplers != null) {
if (objectSize < 0) {
objectSize = getObjectSize(newObj, (count >= 0), instr);
}
for (Sampler sampler : samplers) {
sampler.sampleAllocation(count, desc, newObj, objectSize);
}
}
}
recordingAllocation.set(Boolean.FALSE);
}
/**
* Helper method to force recording; for unit tests only.
* @param count the number of objects being allocated.
* @param desc the descriptor of the class of the object being allocated.
* @param newObj the object being allocated.
*/
public static void recordAllocationForceForTest(int count, String desc,
Object newObj) {
// Make sure we get the right number of elided frames
recordAllocationForceForTestReal(count, desc, newObj, 2);
}
/**
* Helper method to force recording; for unit tests only.
* @param count the number of objects being allocated.
* @param desc the descriptor of the class of the object being allocated.
* @param newObj the object being allocated.
* @param recurse A recursion count.
*/
public static void recordAllocationForceForTestReal(
int count, String desc, Object newObj, int recurse) {
if (recurse != 0) {
recordAllocationForceForTestReal(count, desc, newObj, recurse - 1);
return;
}
}
}
ConstructorCallback.java 0000664 0000000 0000000 00000003301 12716413241 0044521 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /*
* Copyright (C) 2011 Google Inc.
*
* 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 com.google.monitoring.runtime.instrumentation;
/**
* This interface describes a function that is used to sample a
* constructor. It is intended to be invoked every time a constructor
* for class T is invoked. This will not be invoked when subclasses of
* T are instantiated.
*
* This mechanism works independently of whether the class is part of the
* JDK core library.
*
* @param ConstructorCallback
is passed to
* {@link ConstructorInstrumenter#instrumentClass(Class, ConstructorCallback)},
* it will get executed
* whenever a constructor for type T is invoked.
*
* @param newObj the new Object
whose construction
* we're recording. The object is not fully constructed; any
* references to this object that are stored in this callback are
* subject to the memory model constraints related to such
* objects.
*/
public void sample(T newObj);
}
ConstructorInstrumenter.java 0000664 0000000 0000000 00000022361 12716413241 0045533 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /*
* Copyright (C) 2011 Google Inc.
*
* 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 com.google.monitoring.runtime.instrumentation;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.LocalVariablesSorter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Instruments bytecode by inserting a specified call in the
* constructor of a given class. This class is intended to be loaded
* by a javaagent; end-users will want to add {@link ConstructorCallback}s by
* invoking {@link #instrumentClass(Class, ConstructorCallback)}.
*
* @author Jeremy Manson
*/
public class ConstructorInstrumenter implements ClassFileTransformer {
// Implementation details: uses the java.lang.instrument API to
// insert an INVOKESTATIC call to a specified method directly prior to
// constructor return for the given class.
private static final Logger logger =
Logger.getLogger(ConstructorInstrumenter.class.getName());
private static ConcurrentHashMapbyte[]
code.
* @param classBeingRedefined the class being redefined.
* @return the instrumented byte[]
code.
*/
public static byte[] instrument(
byte[] originalBytes, Class> classBeingRedefined) {
try {
ClassReader cr = new ClassReader(originalBytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
VerifyingClassAdapter vcw =
new VerifyingClassAdapter(cw, originalBytes, cr.getClassName());
ClassVisitor adapter =
new ConstructorClassAdapter(vcw, classBeingRedefined);
cr.accept(adapter, ClassReader.SKIP_FRAMES);
return vcw.toByteArray();
} catch (RuntimeException e) {
logger.log(Level.WARNING, "Failed to instrument class.", e);
throw e;
} catch (Error e) {
logger.log(Level.WARNING, "Failed to instrument class.", e);
throw e;
}
}
/**
* The per-method transformations to make. Really only affects the
* visitMethod
is called and the returned
* MethodVisitor is used to visit the method. Note that a new
* MethodVisitor is constructed for each method.
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv =
cv.visitMethod(access, name, desc, signature, exceptions);
if ((mv != null) && "int
count of how many instances are being
* allocated. -1 means a simple new to distinguish from a 1-element array. 0
* shows up as a value here sometimes; one reason is T[] toArray()-type
* methods that require an array type argument (see ArrayList.toArray() for
* example).
* @param desc the String
descriptor of the class/primitive type
* being allocated.
* @param newObj the new Object
whose allocation we're
* recording.
* @param size the size of the object being allocated.
*/
public void sampleAllocation(int count, String desc,
Object newObj, long size);
}
StaticClassWriter.java 0000664 0000000 0000000 00000021306 12716413241 0044176 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /***
* ASM tests
* Copyright (c) 2002-2005 France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
// Portions Copyright 2011 Google, Inc.
//
// This is an extracted version of the ClassInfo and ClassWriter
// portions of ClassWriterComputeFramesTest in the set of ASM tests.
// We have done a fair bit of rewriting for readability, and changed
// the comments. The original author is Eric Bruneton.
package com.google.monitoring.runtime.instrumentation;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.InputStream;
import java.io.IOException;
/**
* A {@link ClassWriter} that looks for static class data in the
* classpath when the classes are not available at runtime.
*
* ClassWriter uses class hierarchy information, which it gets by * looking at loaded classes, to make some decisions about the best * way to write classes. The problem with this is that it fails if * the superclass hasn't been loaded yet. StaticClassWriter fails * over to looking for the class hierarchy information in the * ClassLoader's resources (usually the classpath) if the class it * needs hasn't been loaded yet. * *
This class was heavily influenced by ASM's * org.objectweb.asm.util.ClassWriterComputeFramesTest, which contains * the same logic in a subclass. The code here has been slightly * cleaned up for readability. * * @author jeremymanson@google.com (Jeremy Manson) */ class StaticClassWriter extends ClassWriter { /* The classloader that we use to look for the unloaded class */ private final ClassLoader classLoader; /** * {@inheritDoc} * @param classLoader the class loader that loaded this class */ public StaticClassWriter( ClassReader classReader, int flags, ClassLoader classLoader) { super(classReader, flags); this.classLoader = classLoader; } /** * {@inheritDoc} */ @Override protected String getCommonSuperClass( final String type1, final String type2) { try { return super.getCommonSuperClass(type1, type2); } catch (Throwable e) { // Try something else... } // Exactly the same as in ClassWriter, but gets the superclass // directly from the class file. ClassInfo ci1, ci2; try { ci1 = new ClassInfo(type1, classLoader); ci2 = new ClassInfo(type2, classLoader); } catch (Throwable e) { throw new RuntimeException(e); } if (ci1.isAssignableFrom(ci2)) { return type1; } if (ci2.isAssignableFrom(ci1)) { return type2; } if (ci1.isInterface() || ci2.isInterface()) { return "java/lang/Object"; } do { // Should never be null, because if ci1 were the Object class // or an interface, it would have been caught above. ci1 = ci1.getSuperclass(); } while (!ci1.isAssignableFrom(ci2)); return ci1.getType().getInternalName(); } /** * For a given class, this stores the information needed by the * getCommonSuperClass test. This determines if the class is * available at runtime, and then, if it isn't, it tries to get the * class file, and extract the appropriate information from that. */ static class ClassInfo { private final Type type; private final ClassLoader loader; private final boolean isInterface; private final String superClass; private final String[] interfaces; public ClassInfo(String type, ClassLoader loader) { Class> cls = null; // First, see if we can extract the information from the class... try { cls = Class.forName(type); } catch (Exception e) { // failover... } if (cls != null) { this.type = Type.getType(cls); this.loader = loader; this.isInterface = cls.isInterface(); this.superClass = cls.getSuperclass().getName(); Class[] ifs = cls.getInterfaces(); this.interfaces = new String[ifs.length]; for (int i = 0; i < ifs.length; i++) { this.interfaces[i] = ifs[i].getName(); } return; } // The class isn't loaded. Try to get the class file, and // extract the information from that. this.loader = loader; this.type = Type.getObjectType(type); String fileName = type.replace('.', '/') + ".class"; InputStream is = null; ClassReader cr; try { is = (loader == null) ? ClassLoader.getSystemResourceAsStream(fileName) : loader.getResourceAsStream(fileName); cr = new ClassReader(is); } catch (IOException e) { throw new RuntimeException(e); } finally { if (is != null) { try { is.close(); } catch (Exception e) { } } } int offset = cr.header; isInterface = (cr.readUnsignedShort(offset) & Opcodes.ACC_INTERFACE) != 0; char[] buf = new char[2048]; // Read the superclass offset += 4; superClass = readConstantPoolString(cr, offset, buf); // Read the interfaces offset += 2; int numInterfaces = cr.readUnsignedShort(offset); interfaces = new String[numInterfaces]; offset += 2; for (int i = 0; i < numInterfaces; i++) { interfaces[i] = readConstantPoolString(cr, offset, buf); offset += 2; } } String readConstantPoolString(ClassReader cr, int offset, char[] buf) { int cpIndex = cr.getItem(cr.readUnsignedShort(offset)); if (cpIndex == 0) { return null; // throw new RuntimeException("Bad constant pool index"); } return cr.readUTF8(cpIndex, buf); } Type getType() { return type; } ClassInfo getSuperclass() { if (superClass == null) { return null; } return new ClassInfo(superClass, loader); } /** * Same as {@link Class#getInterfaces()} */ ClassInfo[] getInterfaces() { if (interfaces == null) { return new ClassInfo[0]; } ClassInfo[] result = new ClassInfo[interfaces.length]; for (int i = 0; i < result.length; ++i) { result[i] = new ClassInfo(interfaces[i], loader); } return result; } /** * Same as {@link Class#isInterface} */ boolean isInterface() { return isInterface; } private boolean implementsInterface(ClassInfo that) { for (ClassInfo c = this; c != null; c = c.getSuperclass()) { for (ClassInfo iface : c.getInterfaces()) { if (iface.type.equals(that.type) || iface.implementsInterface(that)) { return true; } } } return false; } private boolean isSubclassOf(ClassInfo that) { for (ClassInfo ci = this; ci != null; ci = ci.getSuperclass()) { if (ci.getSuperclass() != null && ci.getSuperclass().type.equals(that.type)) { return true; } } return false; } /** * Same as {@link Class#isAssignableFrom(Class)} */ boolean isAssignableFrom(ClassInfo that) { return (this == that || that.isSubclassOf(this) || that.implementsInterface(this) || (that.isInterface() && getType().getDescriptor().equals("Ljava/lang/Object;"))); } } } VerifyingClassAdapter.java 0000664 0000000 0000000 00000007046 12716413241 0045022 0 ustar 00root root 0000000 0000000 allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentation /* * Copyright (C) 2008 Google Inc. * * 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 com.google.monitoring.runtime.instrumentation; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.CodeSizeEvaluator; import java.util.logging.Level; import java.util.logging.Logger; /** * This is a class writer that gets used in place of the existing * {@link org.objectweb.asm.ClassWriter}, * and verifies properties of the class getting written. * * Currently, it only checks to see if the methods are of the correct length * for Java methods (<64K). * * @author jeremymanson@google.com (Jeremy Manson) */ public class VerifyingClassAdapter extends ClassVisitor { private static final Logger logger = Logger.getLogger(VerifyingClassAdapter.class.getName()); /** * An enum which indicates whether the class in question is verified. */ public enum State { PASS, UNKNOWN, FAIL_TOO_LONG; } final ClassWriter cw; final byte [] original; final String className; String message; State state; /** * @param cw A class writer that is wrapped by this class adapter * @param original the original bytecode * @param className the name of the class being examined. */ public VerifyingClassAdapter(ClassWriter cw, byte [] original, String className) { super(Opcodes.ASM5, cw); state = State.UNKNOWN; message = "The class has not finished being examined"; this.cw = cw; this.original = original; this.className = className.replace('/', '.'); } /** * {@inheritDoc} * * In addition, the returned {@link org.objectweb.asm.MethodVisitor} * will throw an exception if the method is greater than 64K in length. */ @Override public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); return new CodeSizeEvaluator(mv) { @Override public void visitEnd() { super.visitEnd(); if (getMaxSize() > 64 * 1024) { state = State.FAIL_TOO_LONG; message = "the method " + name + " was too long."; } } }; } /** * {@inheritDoc} */ @Override public void visitEnd() { super.visitEnd(); if (state == State.UNKNOWN) { state = State.PASS; } } /** * Gets the verification state of this class. * * @return true iff the class passed inspection. */ public boolean isVerified() { return state == State.PASS; } /** * Returns the byte array that contains the byte code for this class. * * @return a byte array. */ public byte[] toByteArray() { if (state != State.PASS) { logger.log(Level.WARNING, "Failed to instrument class " + className + " because " + message); return original; } return cw.toByteArray(); } }