pax_global_header00006660000000000000000000000064127164132410014513gustar00rootroot0000000000000052 comment=dba968db6d7c90cae616a873ce09bb382d6d6f1c allocation-instrumenter-java-allocation-instrumenter-3.0.1/000077500000000000000000000000001271641324100241755ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/.gitignore000066400000000000000000000001101271641324100261550ustar00rootroot00000000000000.idea/ *.ims *.iml .classpath .project .settings/ target/ bin/ out/ allocation-instrumenter-java-allocation-instrumenter-3.0.1/AUTHORS000066400000000000000000000000141271641324100252400ustar00rootroot00000000000000Google Inc. allocation-instrumenter-java-allocation-instrumenter-3.0.1/COPYING000066400000000000000000000261361271641324100252400ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. allocation-instrumenter-java-allocation-instrumenter-3.0.1/README.md000066400000000000000000000041201271641324100254510ustar00rootroot00000000000000The Allocation Instrumenter is a Java agent written using the [java.lang.instrument][] API and [ASM][]. Each allocation in your Java program is instrumented; a user-defined callback is invoked on each allocation. ## How to get it The [latest release][] is available from [Maven Central][] as: ```xml com.google.code.java-allocation-instrumenter java-allocation-instrumenter 3.0 ``` ## Basic usage In order to write your own allocation tracking code, you have to implement the `Sampler` interface and pass an instance of that to `AllocationRecorder.addSampler()`: ```java AllocationRecorder.addSampler(new Sampler() { public void sampleAllocation(int count, String desc, Object newObj, long size) { System.out.println("I just allocated the object " + newObj + " of type " + desc + " whose size is " + size); if (count != -1) { System.out.println("It's an array of size " + count); } } }); ``` You can also use the allocation instrumenter to instrument constructors of particular classes. You do this by instantiating a `ConstructorCallback` and passing it to `ConstructorInstrumenter.instrumentClass()`: ```java try { ConstructorInstrumenter.instrumentClass( Thread.class, new ConstructorCallback() { @Override public void sample(Thread t) { System.out.println("Instantiating a thread"); } }); } catch (UnmodifiableClassException e) { System.out.println("Class cannot be modified"); } ``` For more information on how to get or use the allocation instrumenter, see [Getting Started][]. [java.lang.instrument]: http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html [ASM]: http://asm.ow2.org/ [latest release]: https://github.com/google/allocation-instrumenter/releases/tag/java-allocation-instrumenter-3.0 [Maven Central]: http://search.maven.org/#artifactdetails%7Ccom.google.code.java-allocation-instrumenter%7Cjava-allocation-instrumenter%7C3.0%7Cjar [Getting Started]: https://github.com/google/allocation-instrumenter/wiki allocation-instrumenter-java-allocation-instrumenter-3.0.1/pom.xml000066400000000000000000000230731271641324100255170ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 com.google.code.java-allocation-instrumenter java-allocation-instrumenter jar 3.0.1 java-allocation-instrumenter A Java agent that rewrites bytecode to instrument allocation sites. 2009 https://github.com/google/allocation-instrumenter/ Google, Inc. http://www.google.com Jeremy Manson Google Inc. The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/google/allocation-instrumenter.git scm:git:git@github.com:google/allocation-instrumenter.git https://github.com/google/allocation-instrumenter/ Google Code Issue Tracking http://code.google.com/p/java-allocation-instrumenter/issues/list UTF-8 5.0.3 true org.ow2.asm asm ${projectAsmVersion} org.ow2.asm asm-analysis ${projectAsmVersion} org.ow2.asm asm-commons ${projectAsmVersion} org.ow2.asm asm-tree ${projectAsmVersion} org.ow2.asm asm-util ${projectAsmVersion} org.ow2.asm asm-xml ${projectAsmVersion} com.google.guava guava 18.0 junit junit 3.8.2 test package org.apache.maven.plugins maven-compiler-plugin 3.2 1.6 1.6 org.apache.maven.plugins maven-eclipse-plugin 2.9 true true ../eclipse-ws/ org.apache.maven.plugins maven-release-plugin 2.5.1 -DenableCiProfile=true org.apache.maven.plugins maven-source-plugin 2.4 attach-sources jar org.apache.maven.plugins maven-javadoc-plugin 2.10.1 attach-javadocs jar http://docs.oracle.com/javase/8/docs/api/ true public org.sonatype.plugins jarjar-maven-plugin 1.9 embed-jars prepare-package jarjar org.ow2.asm:asm org.ow2.asm:asm-analysis org.ow2.asm:asm-commons org.ow2.asm:asm-tree org.ow2.asm:asm-util org.ow2.asm:asm-xml com.google.guava:guava org.objectweb.asm.** com.google.monitoring.runtime.instrumentation.asm.@1 com.google.common.** com.google.monitoring.runtime.instrumentation.common.@0 com.google.monitoring.runtime.instrumentation.common.collect.ComputingCache com.google.monitoring.runtime.instrumentation.common.collect.ComputingConcurrentHashMap com.google.monitoring.runtime.instrumentation.common.collect.ForwardingMap com.google.monitoring.runtime.instrumentation.common.collect.MapMaker com.google.monitoring.runtime.instrumentation.common.base.FunctionalEquivalence com.google.monitoring.runtime.instrumentation.common.base.PairwiseEquivalence com.google.monitoring.runtime.instrumentation.common.base.Supplier com.google.monitoring.runtime.instrumentation.common.base.Suppliers com.google.monitoring.runtime.instrumentation.common.primitives.Ints com.google.monitoring.runtime.instrumentation.asm.** com.google.monitoring.runtime.instrumentation.* org.apache.maven.plugins maven-jar-plugin 2.5 ./${project.artifactId}-${project.version}.${project.packaging} com.google.monitoring.runtime.instrumentation.AllocationInstrumenter true true NotSuitableAsMain org.apache.maven.plugins maven-gpg-plugin 1.4 sign-artifacts verify sign allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/000077500000000000000000000000001271641324100247645ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/000077500000000000000000000000001271641324100257105ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/000077500000000000000000000000001271641324100266315ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/000077500000000000000000000000001271641324100274075ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/000077500000000000000000000000001271641324100306635ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/000077500000000000000000000000001271641324100330505ustar00rootroot00000000000000000077500000000000000000000000001271641324100344545ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime000077500000000000000000000000001271641324100377175ustar00rootroot00000000000000allocation-instrumenter-java-allocation-instrumenter-3.0.1/src/main/java/com/google/monitoring/runtime/instrumentationAllocationClassAdapter.java000066400000000000000000000050751271641324100451450ustar00rootroot00000000000000allocation-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.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.LocalVariablesSorter; import org.objectweb.asm.commons.JSRInlinerAdapter; /** * Instruments bytecodes that allocate heap memory to call a recording hook. * A 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.java000066400000000000000000000175251271641324100453010ustar00rootroot00000000000000allocation-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; List args = Arrays.asList( agentArgs == null ? new String[0] : agentArgs.split(",")); // When "subclassesAlso" is specified, samplers are also invoked when // SubclassOfA. is called while only class A is specified to be // instrumented. ConstructorInstrumenter.subclassesAlso = args.contains("subclassesAlso"); inst.addTransformer(new ConstructorInstrumenter(), inst.isRetransformClassesSupported()); if (!args.contains("manualOnly")) { bootstrap(inst); } } private static void bootstrap(Instrumentation inst) { inst.addTransformer(new AllocationInstrumenter(), inst.isRetransformClassesSupported()); if (!canRewriteBootstrap) { return; } // Get the set of already loaded classes that can be rewritten. Class[] classes = inst.getAllLoadedClasses(); ArrayList> classList = new ArrayList>(); for (int i = 0; i < classes.length; i++) { if (inst.isModifiableClass(classes[i])) { classList.add(classes[i]); } } // Reload classes, if possible. Class[] workaround = new Class[classList.size()]; try { inst.retransformClasses(classList.toArray(workaround)); } catch (UnmodifiableClassException e) { System.err.println("AllocationInstrumenter was unable to " + "retransform early loaded classes."); } } @Override public byte[] transform( ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] origBytes) { if (!canRewriteClass(className, loader)) { return null; } return instrument(origBytes, loader); } /** * Given the bytes representing a class, go through all the bytecode in it and * instrument any occurences of new/newarray/anewarray/multianewarray with * pre- and post-allocation hooks. Even more fun, intercept calls to the * reflection API's Array.newInstance() and instrument those too. * * @param originalBytes the original byte[] 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.java000066400000000000000000000573171271641324100453260ustar00rootroot00000000000000allocation-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: *
    *
  1. an 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 toArray()-type methods that require an array * type argument (see ArrayList.toArray() for example).
  2. *
  3. a String descriptor of the class/primitive type being allocated.
  4. *
  5. an Object reference to the just-allocated Object.
  6. *
*/ public static final String RECORDER_SIGNATURE = "(ILjava/lang/String;Ljava/lang/Object;)V"; /** * Like RECORDER_SIGNATURE, but for a method that extracts all of * the information dynamically from a class. */ public static final String CLASS_RECORDER_SIG = "(Ljava/lang/Class;Ljava/lang/Object;)V"; // A helper struct for describing the scope of temporary local variables we // create as part of the instrumentation. private static class VariableScope { public final int index; public final Label start; public final Label end; public final String desc; public VariableScope(int index, Label start, Label end, String desc) { this.index = index; this.start = start; this.end = end; this.desc = desc; } } // Dictionary of primitive type opcode to english name. private static final String[] primitiveTypeNames = new String[] { "INVALID0", "INVALID1", "INVALID2", "INVALID3", "boolean", "char", "float", "double", "byte", "short", "int", "long" }; // To track the difference between 's called as the result of a NEW // and 's called because of superclass initialization, we track the // number of NEWs that still need to have their 's called. private int outstandingAllocs = 0; // We need to set the scope of any local variables we materialize; // accumulate the scopes here and set them all at the end of the visit to // ensure all labels have been resolved. Allocated on-demand. private List localScopes = null; private List getLocalScopes() { if (localScopes == null) { localScopes = new LinkedList(); } return localScopes; } private final String recorderClass; private final String recorderMethod; /** * The LocalVariablesSorter used in this adapter. Lame that it's public but * the ASM architecture requires setting it from the outside after this * AllocationMethodAdapter is fully constructed and the LocalVariablesSorter * constructor requires a reference to this adapter. The only setter of * this should be AllocationClassAdapter.visitMethod(). */ public LocalVariablesSorter lvs = null; /** * A new AllocationMethodAdapter is created for each method that gets visited. */ public AllocationMethodAdapter(MethodVisitor mv, String recorderClass, String recorderMethod) { super(Opcodes.ASM5, mv); this.recorderClass = recorderClass; this.recorderMethod = recorderMethod; } /** * newarray shows up as an instruction taking an int operand (the primitive * element type of the array) so we hook it here. */ @Override public void visitIntInsn(int opcode, int operand) { if (opcode == Opcodes.NEWARRAY) { // instack: ... count // outstack: ... aref if (operand >= 4 && operand <= 11) { super.visitInsn(Opcodes.DUP); // -> stack: ... count count super.visitIntInsn(opcode, operand); // -> stack: ... count aref invokeRecordAllocation(primitiveTypeNames[operand]); // -> stack: ... aref } else { AllocationInstrumenter.logger.severe("NEWARRAY called with an invalid operand " + operand + ". Not instrumenting this allocation!"); super.visitIntInsn(opcode, operand); } } else { super.visitIntInsn(opcode, operand); } } // Helper method to compute class name as a String and push it on the stack. // pre: stack: ... class // post: stack: ... class className private void pushClassNameOnStack() { super.visitInsn(Opcodes.DUP); // -> stack: ... class class super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false); // -> stack: ... class classNameDotted super.visitLdcInsn('.'); // -> stack: ... class classNameDotted '.' super.visitLdcInsn('/'); // -> stack: ... class classNameDotted '.' '/' super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "replace", "(CC)Ljava/lang/String;", false); // -> stack: ... class className } // Helper method to compute the product of an integer array and push it on // the stack. // pre: stack: ... intArray // post: stack: ... intArray product private void pushProductOfIntArrayOnStack() { Label beginScopeLabel = new Label(); Label endScopeLabel = new Label(); int dimsArrayIndex = newLocal("[I", beginScopeLabel, endScopeLabel); int counterIndex = newLocal("I", beginScopeLabel, endScopeLabel); int productIndex = newLocal("I", beginScopeLabel, endScopeLabel); Label loopLabel = new Label(); Label endLabel = new Label(); super.visitLabel(beginScopeLabel); // stack: ... intArray super.visitVarInsn(Opcodes.ASTORE, dimsArrayIndex); // -> stack: ... // counter = 0 super.visitInsn(Opcodes.ICONST_0); super.visitVarInsn(Opcodes.ISTORE, counterIndex); // product = 1 super.visitInsn(Opcodes.ICONST_1); super.visitVarInsn(Opcodes.ISTORE, productIndex); // loop: super.visitLabel(loopLabel); // if index >= arraylength goto end: super.visitVarInsn(Opcodes.ILOAD, counterIndex); super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); super.visitInsn(Opcodes.ARRAYLENGTH); super.visitJumpInsn(Opcodes.IF_ICMPGE, endLabel); // product = product * max(array[counter],1) super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); super.visitVarInsn(Opcodes.ILOAD, counterIndex); super.visitInsn(Opcodes.IALOAD); super.visitInsn(Opcodes.DUP); Label nonZeroDimension = new Label(); super.visitJumpInsn(Opcodes.IFNE, nonZeroDimension); super.visitInsn(Opcodes.POP); super.visitInsn(Opcodes.ICONST_1); super.visitLabel(nonZeroDimension); super.visitVarInsn(Opcodes.ILOAD, productIndex); super.visitInsn(Opcodes.IMUL); // if overflow happens it happens. super.visitVarInsn(Opcodes.ISTORE, productIndex); // iinc counter 1 super.visitIincInsn(counterIndex, 1); // goto loop super.visitJumpInsn(Opcodes.GOTO, loopLabel); // end: super.visitLabel(endLabel); // re-push dimensions array super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); // push product super.visitVarInsn(Opcodes.ILOAD, productIndex); super.visitLabel(endScopeLabel); } /** * Reflection-based allocation (@see java.lang.reflect.Array#newInstance) is * triggered with a static method call (INVOKESTATIC), so we hook it here. * Class initialization is triggered with a constructor call (INVOKESPECIAL) * so we hook that here too as a proxy for the new bytecode which leaves an * uninitialized object on the stack that we're not allowed to touch. * {@link java.lang.Object#clone} is also a call to INVOKESPECIAL, * and is hooked here. {@link java.lang.Class#newInstance} and * {@link java.lang.reflect.Constructor#newInstance} are both * INVOKEVIRTUAL calls, so they are hooked here, as well. */ @Override public void visitMethodInsn(int opcode, String owner, String name, String signature, boolean itf) { if (opcode == Opcodes.INVOKESTATIC && // Array does its own native allocation. Grr. owner.equals("java/lang/reflect/Array") && name.equals("newInstance")) { if (signature.equals("(Ljava/lang/Class;I)Ljava/lang/Object;")) { Label beginScopeLabel = new Label(); Label endScopeLabel = new Label(); super.visitLabel(beginScopeLabel); // stack: ... class count int countIndex = newLocal("I", beginScopeLabel, endScopeLabel); super.visitVarInsn(Opcodes.ISTORE, countIndex); // -> stack: ... class pushClassNameOnStack(); // -> stack: ... class className int typeNameIndex = newLocal("Ljava/lang/String;", beginScopeLabel, endScopeLabel); super.visitVarInsn(Opcodes.ASTORE, typeNameIndex); // -> stack: ... class super.visitVarInsn(Opcodes.ILOAD, countIndex); // -> stack: ... class count super.visitMethodInsn(opcode, owner, name, signature, itf); // -> stack: ... newobj super.visitInsn(Opcodes.DUP); // -> stack: ... newobj newobj super.visitVarInsn(Opcodes.ILOAD, countIndex); // -> stack: ... newobj newobj count super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj count newobj super.visitVarInsn(Opcodes.ALOAD, typeNameIndex); super.visitLabel(endScopeLabel); // -> stack: ... newobj count newobj className super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj count className newobj super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, recorderMethod, RECORDER_SIGNATURE, false); // -> stack: ... newobj return; } else if (signature.equals("(Ljava/lang/Class;[I)Ljava/lang/Object;")){ Label beginScopeLabel = new Label(); Label endScopeLabel = new Label(); super.visitLabel(beginScopeLabel); int dimsArrayIndex = newLocal("[I", beginScopeLabel, endScopeLabel); // stack: ... class dimsArray pushProductOfIntArrayOnStack(); // -> stack: ... class dimsArray product int productIndex = newLocal("I", beginScopeLabel, endScopeLabel); super.visitVarInsn(Opcodes.ISTORE, productIndex); // -> stack: ... class dimsArray super.visitVarInsn(Opcodes.ASTORE, dimsArrayIndex); // -> stack: ... class pushClassNameOnStack(); // -> stack: ... class className int typeNameIndex = newLocal("Ljava/lang/String;", beginScopeLabel, endScopeLabel); super.visitVarInsn(Opcodes.ASTORE, typeNameIndex); // -> stack: ... class super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); // -> stack: ... class dimsArray super.visitMethodInsn(opcode, owner, name, signature, itf); // -> stack: ... newobj super.visitInsn(Opcodes.DUP); // -> stack: ... newobj newobj super.visitVarInsn(Opcodes.ILOAD, productIndex); // -> stack: ... newobj newobj product super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj product newobj super.visitVarInsn(Opcodes.ALOAD, typeNameIndex); super.visitLabel(endScopeLabel); // -> stack: ... newobj product newobj className super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj product className newobj super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, recorderMethod, RECORDER_SIGNATURE, false); // -> stack: ... newobj return; } } if (opcode == Opcodes.INVOKEVIRTUAL) { if ("clone".equals(name) && owner.startsWith("[")) { super.visitMethodInsn(opcode, owner, name, signature, itf); int i = 0; while (i < owner.length()) { if (owner.charAt(i) != '[') { break; } i++; } if (i > 1) { // -> stack: ... newobj super.visitTypeInsn(Opcodes.CHECKCAST, owner); // -> stack: ... arrayref calculateArrayLengthAndDispatch(owner.substring(i), i); } else { // -> stack: ... newobj super.visitInsn(Opcodes.DUP); // -> stack: ... newobj newobj super.visitTypeInsn(Opcodes.CHECKCAST, owner); // -> stack: ... newobj arrayref super.visitInsn(Opcodes.ARRAYLENGTH); // -> stack: ... newobj length super.visitInsn(Opcodes.SWAP); // -> stack: ... length newobj invokeRecordAllocation(owner.substring(i)); } return; } else if ("newInstance".equals(name)) { if ("java/lang/Class".equals(owner) && "()Ljava/lang/Object;".equals(signature)) { super.visitInsn(Opcodes.DUP); // -> stack: ... Class Class super.visitMethodInsn(opcode, owner, name, signature, itf); // -> stack: ... Class newobj super.visitInsn(Opcodes.DUP_X1); // -> stack: ... newobj Class newobj super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, recorderMethod, CLASS_RECORDER_SIG, false); // -> stack: ... newobj return; } else if ("java/lang/reflect/Constructor".equals(owner) && "([Ljava/lang/Object;)Ljava/lang/Object;".equals(signature)) { buildRecorderFromObject(opcode, owner, name, signature, itf); return; } } } if (opcode == Opcodes.INVOKESPECIAL) { if ("clone".equals(name) && "java/lang/Object".equals(owner)) { buildRecorderFromObject(opcode, owner, name, signature, itf); return; } else if ("".equals(name) && outstandingAllocs > 0) { // Tricky because superclass initializers mean there can be more calls // to than calls to NEW; hence outstandingAllocs. --outstandingAllocs; // Most of the time (i.e. in bytecode generated by javac) it is the case // that following an call the top of the stack has a reference ot // the newly-initialized object. But nothing in the JVM Spec requires // this, so we need to play games with the stack to make an explicit // extra copy (and then discard it). dupStackElementBeforeSignatureArgs(signature); super.visitMethodInsn(opcode, owner, name, signature, itf); super.visitLdcInsn(-1); super.visitInsn(Opcodes.SWAP); invokeRecordAllocation(owner); super.visitInsn(Opcodes.POP); return; } } super.visitMethodInsn(opcode, owner, name, signature, itf); } // This is the instrumentation that occurs when there is no static // information about the class we are instantiating. First we build the // object, then we get the class and invoke the recorder. private void buildRecorderFromObject( int opcode, String owner, String name, String signature, boolean itf) { super.visitMethodInsn(opcode, owner, name, signature, itf); // -> stack: ... newobj super.visitInsn(Opcodes.DUP); // -> stack: ... newobj newobj super.visitInsn(Opcodes.DUP); // -> stack: ... newobj newobj newobj // We could be instantiating this class or a subclass, so we // have to get the class the hard way. super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); // -> stack: ... newobj newobj Class super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj Class newobj super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, recorderMethod, CLASS_RECORDER_SIG, false); // -> stack: ... newobj } // Given a method signature interpret the top of the stack as the arguments // to the method, dup the top-most element preceding these arguments, and // leave the arguments alone. This is done by inspecting each parameter // type, popping off the stack elements using the type information, // duplicating the target element, and pushing the arguments back on the // stack. private void dupStackElementBeforeSignatureArgs(final String sig) { final Label beginScopeLabel = new Label(); final Label endScopeLabel = new Label(); super.visitLabel(beginScopeLabel); Type[] argTypes = Type.getArgumentTypes(sig); int[] args = new int[argTypes.length]; for (int i = argTypes.length - 1; i >= 0; --i) { args[i] = newLocal(argTypes[i], beginScopeLabel, endScopeLabel); super.visitVarInsn(argTypes[i].getOpcode(Opcodes.ISTORE), args[i]); } super.visitInsn(Opcodes.DUP); for (int i = 0; i < argTypes.length; ++i) { int op = argTypes[i].getOpcode(Opcodes.ILOAD); super.visitVarInsn(op, args[i]); if (op == Opcodes.ALOAD) { super.visitInsn(Opcodes.ACONST_NULL); super.visitVarInsn(Opcodes.ASTORE, args[i]); } } super.visitLabel(endScopeLabel); } /** * new and anewarray bytecodes take a String operand for the type of * the object or array element so we hook them here. Note that new doesn't * actually result in any instrumentation here; we just do a bit of * book-keeping and do the instrumentation following the constructor call * (because we're not allowed to touch the object until it is initialized). */ @Override public void visitTypeInsn(int opcode, String typeName) { if (opcode == Opcodes.NEW) { // We can't actually tag this object right after allocation because it // must be initialized with a ctor before we can touch it (Verifier // enforces this). Instead, we just note it and tag following // initialization. super.visitTypeInsn(opcode, typeName); ++outstandingAllocs; } else if (opcode == Opcodes.ANEWARRAY) { super.visitInsn(Opcodes.DUP); super.visitTypeInsn(opcode, typeName); invokeRecordAllocation(typeName); } else { super.visitTypeInsn(opcode, typeName); } } /** * Called by the ASM framework once the class is done being visited to * compute stack & local variable count maximums. */ @Override public void visitMaxs(int maxStack, int maxLocals) { if (localScopes != null) { for (VariableScope scope : localScopes) { super.visitLocalVariable("xxxxx$" + scope.index, scope.desc, null, scope.start, scope.end, scope.index); } } super.visitMaxs(maxStack, maxLocals); } // Helper method to allocate a new local variable and account for its scope. private int newLocal(Type type, String typeDesc, Label begin, Label end) { int newVar = lvs.newLocal(type); getLocalScopes().add(new VariableScope(newVar, begin, end, typeDesc)); return newVar; } // Sometimes I happen to have a string descriptor and sometimes a type; // these alternate versions let me avoid recomputing whatever I already // know. private int newLocal(String typeDescriptor, Label begin, Label end) { return newLocal(Type.getType(typeDescriptor), typeDescriptor, begin, end); } private int newLocal(Type type, Label begin, Label end) { return newLocal(type, type.getDescriptor(), begin, end); } private static final Pattern namePattern = Pattern.compile("^\\[*L([^;]+);$"); // Helper method to actually invoke the recorder function for an allocation // event. // pre: stack: ... count newobj // post: stack: ... newobj private void invokeRecordAllocation(String typeName) { Matcher matcher = namePattern.matcher(typeName); if (matcher.find()) { typeName = matcher.group(1); } // stack: ... count newobj super.visitInsn(Opcodes.DUP_X1); // -> stack: ... newobj count newobj super.visitLdcInsn(typeName); // -> stack: ... newobj count newobj typename super.visitInsn(Opcodes.SWAP); // -> stack: ... newobj count typename newobj super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, recorderMethod, RECORDER_SIGNATURE, false); // -> stack: ... newobj } /** * multianewarray gets its very own visit method in the ASM framework, so we * hook it here. This bytecode is different from most in that it consumes a * variable number of stack elements during execution. The number of stack * elements consumed is specified by the dimCount operand. */ @Override public void visitMultiANewArrayInsn(String typeName, int dimCount) { // stack: ... dim1 dim2 dim3 ... dimN super.visitMultiANewArrayInsn(typeName, dimCount); // -> stack: ... aref calculateArrayLengthAndDispatch(typeName, dimCount); } void calculateArrayLengthAndDispatch(String typeName, int dimCount) { // Since the dimensions of the array are not known at instrumentation // time, we take the created multi-dimensional array and peel off nesting // levels from the left. For each nesting layer we probe the array length // and accumulate a partial product which we can then feed the recording // function. // below we note the partial product of dimensions 1 to X-1 as productToX // (so productTo1 == 1 == no dimensions yet). We denote by aref0 the // array reference at the current nesting level (the containing aref's [0] // element). If we hit a level whose arraylength is 0 there's no point // continuing so we shortcut out. Label zeroDimension = new Label(); super.visitInsn(Opcodes.DUP); // -> stack: ... origaref aref0 super.visitLdcInsn(1); // -> stack: ... origaref aref0 productTo1 for (int i = 0; i < dimCount; ++i) { // pre: stack: ... origaref aref0 productToI super.visitInsn(Opcodes.SWAP); // -> stack: ... origaref productToI aref super.visitInsn(Opcodes.DUP_X1); // -> stack: ... origaref aref0 productToI aref super.visitInsn(Opcodes.ARRAYLENGTH); // -> stack: ... origaref aref0 productToI dimI Label nonZeroDimension = new Label(); super.visitInsn(Opcodes.DUP); // -> stack: ... origaref aref0 productToI dimI dimI super.visitJumpInsn(Opcodes.IFNE, nonZeroDimension); // -> stack: ... origaref aref0 productToI dimI super.visitInsn(Opcodes.POP); // -> stack: ... origaref aref0 productToI super.visitJumpInsn(Opcodes.GOTO, zeroDimension); super.visitLabel(nonZeroDimension); // -> stack: ... origaref aref0 productToI max(dimI,1) super.visitInsn(Opcodes.IMUL); // -> stack: ... origaref aref0 productTo{I+1} if (i < dimCount - 1) { super.visitInsn(Opcodes.SWAP); // -> stack: ... origaref productTo{I+1} aref0 super.visitInsn(Opcodes.ICONST_0); // -> stack: ... origaref productTo{I+1} aref0 0 super.visitInsn(Opcodes.AALOAD); // -> stack: ... origaref productTo{I+1} aref0' super.visitInsn(Opcodes.SWAP); } // post: stack: ... origaref aref0 productTo{I+1} } super.visitLabel(zeroDimension); super.visitInsn(Opcodes.SWAP); // -> stack: ... origaref product aref0 super.visitInsn(Opcodes.POP); // -> stack: ... origaref product super.visitInsn(Opcodes.SWAP); // -> stack: ... product origaref invokeRecordAllocation(typeName); } } AllocationRecorder.java000066400000000000000000000241741271641324100443450ustar00rootroot00000000000000allocation-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 java.lang.instrument.Instrumentation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.ForwardingMap; import com.google.common.collect.MapMaker; /** * The logic for recording allocations, called from bytecode rewritten by * {@link AllocationInstrumenter}. * * @author jeremymanson@google.com (Jeremy Manson) * @author fischman@google.com (Ami Fischman) */ public class AllocationRecorder { static { // Sun's JVMs in 1.5.0_06 and 1.6.0{,_01} have a bug where calling // Instrumentation.getObjectSize() during JVM shutdown triggers a // JVM-crashing assert in JPLISAgent.c, so we make sure to not call it after // shutdown. There can still be a race here, depending on the extent of the // JVM bug, but this seems to be good enough. // instrumentation is volatile to make sure the threads reading it (in // recordAllocation()) see the updated value; we could do more // synchronization but it's not clear that it'd be worth it, given the // ambiguity of the bug we're working around in the first place. Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { setInstrumentation(null); } }); } // See the comment above the addShutdownHook in the static block above // for why this is volatile. private static volatile Instrumentation instrumentation = null; static Instrumentation getInstrumentation() { return instrumentation; } static void setInstrumentation(Instrumentation inst) { instrumentation = inst; } // Mostly because, yes, arrays are faster than collections. private static volatile Sampler [] additionalSamplers; // Protects mutations of additionalSamplers. Reads are okay because // the field is volatile, so anyone who reads additionalSamplers // will get a consistent view of it. private static final Object samplerLock = new Object(); // List of packages that can add samplers. private static final List classNames = new ArrayList(); static { classNames.add("com.google.monitoring.runtime."); } // Used for reentrancy checks private static final ThreadLocal recordingAllocation = new ThreadLocal(); // Stores the object sizes for the last ~100000 encountered classes private static final ForwardingMap, Long> classSizesMap = new ForwardingMap, Long>() { private final ConcurrentMap, Long> map = new MapMaker() .weakKeys() .makeMap(); @Override public Map, Long> delegate() { return map; } // The approximate maximum size of the map private static final int MAX_SIZE = 100000; // The approximate current size of the map; since this is not an AtomicInteger // and since we do not synchronize the updates to this field, it will only be // an approximate size of the map; it's good enough for our purposes though, // and not synchronizing the updates saves us some time private int approximateSize = 0; @Override public Long put(Class key, Long value) { // if we have too many elements, delete about 10% of them // this is expensive, but needs to be done to keep the map bounded // we also need to randomize the elements we delete: if we remove the same // elements all the time, we might end up adding them back to the map // immediately after, and then remove them again, then add them back, etc. // which will cause this expensive code to be executed too often if (approximateSize >= MAX_SIZE) { for (Iterator> it = keySet().iterator(); it.hasNext(); ) { it.next(); if (Math.random() < 0.1) { it.remove(); } } // get the exact size; another expensive call, but we need to correct // approximateSize every once in a while, or the difference between // approximateSize and the actual size might become significant over time; // the other solution is synchronizing every time we update approximateSize, // which seems even more expensive approximateSize = size(); } approximateSize++; return super.put(key, value); } }; /** * Adds a {@link Sampler} that will get run every time an allocation is * performed from Java code. Use this with extreme judiciousness! * * @param sampler The sampler to add. */ public static void addSampler(Sampler sampler) { synchronized (samplerLock) { Sampler[] samplers = additionalSamplers; /* create a new list of samplers from the old, adding this sampler */ if (samplers != null) { Sampler [] newSamplers = new Sampler[samplers.length + 1]; System.arraycopy(samplers, 0, newSamplers, 0, samplers.length); newSamplers[samplers.length] = sampler; additionalSamplers = newSamplers; } else { Sampler[] newSamplers = new Sampler[1]; newSamplers[0] = sampler; additionalSamplers = newSamplers; } } } /** * Removes the given {@link Sampler}. * * @param sampler The sampler to remove. */ public static void removeSampler(Sampler sampler) { synchronized (samplerLock) { Sampler[] samplers = additionalSamplers; int samplerCount = samplers.length; for (Sampler s : samplers) { if (s.equals(sampler)) { samplerCount--; } } Sampler[] newSamplers = new Sampler[samplerCount]; int i = 0; for (Sampler s : samplers) { if (!s.equals(sampler)) { newSamplers[i++] = s; } } additionalSamplers = newSamplers; } } /** * Returns the size of the given object. If the object is not an array, we * check the cache first, and update it as necessary. * * @param obj the object. * @param isArray indicates if the given object is an array. * @param instr the instrumentation object to use for finding the object size * @return the size of the given object. */ private static long getObjectSize(Object obj, boolean isArray, Instrumentation instr) { if (isArray) { return instr.getObjectSize(obj); } Class clazz = obj.getClass(); Long classSize = classSizesMap.get(clazz); if (classSize == null) { classSize = instr.getObjectSize(obj); classSizesMap.put(clazz, classSize); } return classSize; } public static void recordAllocation(Class cls, Object newObj) { // The use of replace makes calls to this method relatively ridiculously // expensive. String typename = cls.getName().replace('.', '/'); recordAllocation(-1, typename, newObj); } /** * Records the allocation. This method is invoked on every allocation * performed by the system. * * @param count the count of how many instances are being * allocated, if an array is being allocated. If an array is not being * allocated, then this value will be -1. * @param desc the descriptor of the class/primitive type * being allocated. * @param newObj the new 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.java000066400000000000000000000033011271641324100445210ustar00rootroot00000000000000allocation-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 The class that will be sampled with this ConstructorCallback * * @author Jeremy Manson */ public interface ConstructorCallback { /** * When an object implementing interface * 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.java000066400000000000000000000223611271641324100455330ustar00rootroot00000000000000allocation-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 ConcurrentHashMap, List>> samplerMap = new ConcurrentHashMap, List>>(); /** * We have a read-modify-write operation when doing a put in samplerMap * (above) and retransforming the class. This lock protects multiple threads * from performing that operation concurrently. */ private static final Object samplerPutAtomicityLock = new Object(); /** * Whether to call the samplers when subclasses of the given class are * constructed. */ static boolean subclassesAlso; // Only for package access (specifically, AllocationInstrumenter) ConstructorInstrumenter() { } /** * Ensures that the given sampler will be invoked every time a constructor * for class c is invoked. * * @param c The class to be tracked * @param sampler the code to be invoked when an instance of c is constructed * @throws UnmodifiableClassException if c cannot be modified. */ public static void instrumentClass(Class c, ConstructorCallback sampler) throws UnmodifiableClassException { // IMPORTANT: Don't forget that other threads may be accessing this // class while this code is running. Specifically, the class may be // executed directly after the retransformClasses is called. Thus, we need // to be careful about what happens after the retransformClasses call. synchronized (samplerPutAtomicityLock) { List> list = samplerMap.get(c); if (list == null) { CopyOnWriteArrayList> samplerList = new CopyOnWriteArrayList>(); samplerList.add(sampler); samplerMap.put(c, samplerList); Instrumentation inst = AllocationRecorder.getInstrumentation(); Class[] cs = new Class[1]; cs[0] = c; inst.retransformClasses(c); } else { list.add(sampler); } } } /** * {@inheritDoc} */ @Override public byte[] transform( ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if ((classBeingRedefined == null) || (!samplerMap.containsKey(classBeingRedefined))) { return null; } if (!AllocationInstrumenter.canRewriteClass(className, loader)) { throw new RuntimeException( new UnmodifiableClassException("cannot instrument " + className)); } return instrument(classfileBuffer, classBeingRedefined); } /** * Given the bytes representing a class, add invocations of the * ConstructorCallback method to the constructor. * * @param originalBytes the original byte[] 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 * methods. */ static class ConstructorMethodAdapter extends MethodVisitor { /** * The LocalVariablesSorter used in this adapter. Lame that it's public but * the ASM architecture requires setting it from the outside after this * AllocationMethodAdapter is fully constructed and the LocalVariablesSorter * constructor requires a reference to this adapter. The only setter of * this should be AllocationClassAdapter.visitMethod(). */ public LocalVariablesSorter lvs = null; Class cl; ConstructorMethodAdapter(MethodVisitor mv, Class cl) { super(Opcodes.ASM5, mv); this.cl = cl; } /** * Inserts the appropriate INVOKESTATIC call */ @Override public void visitInsn(int opcode) { if ((opcode == Opcodes.ARETURN) || (opcode == Opcodes.IRETURN) || (opcode == Opcodes.LRETURN) || (opcode == Opcodes.FRETURN) || (opcode == Opcodes.DRETURN)) { throw new RuntimeException(new UnmodifiableClassException( "Constructors are supposed to return void")); } if (opcode == Opcodes.RETURN) { super.visitVarInsn(Opcodes.ALOAD, 0); super.visitMethodInsn( Opcodes.INVOKESTATIC, "com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter", "invokeSamplers", "(Ljava/lang/Object;)V", false); } super.visitInsn(opcode); } } /** * Bytecode is rewritten to invoke this method; it calls the sampler for * the given class. Note that, unless the javaagent command line argument * "subclassesAlso" is specified, it won't do anything if o is a subclass of * the class that was supposed to be tracked. * @param o the object passed to the samplers. */ @SuppressWarnings("unchecked") public static void invokeSamplers(Object o) { Class currentClass = o.getClass(); while (currentClass != null) { List> samplers = samplerMap.get(currentClass); if (samplers != null) { for (ConstructorCallback sampler : samplers) { sampler.sample(o); } // Return once the first list of registered samplers are found and invoked. return; } else { // When subclassesAlso is not specified (default), return if no // samplers are registered with the type of the currently-constructed // object. Otherwise, traverse upward the class hierarchy. if (!subclassesAlso) { return; } currentClass = currentClass.getSuperclass(); } } } /** * The class that deals with per-class transformations. Basically, invokes * the per-method transformer above if the method is an {@code } method. */ static class ConstructorClassAdapter extends ClassVisitor { Class cl; public ConstructorClassAdapter(ClassVisitor cv, Class cl) { super(Opcodes.ASM5, cv); this.cl = cl; } /** * 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 name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if ((mv != null) && "".equals(name)){ ConstructorMethodAdapter aimv = new ConstructorMethodAdapter(mv, cl); LocalVariablesSorter lvs = new LocalVariablesSorter(access, desc, aimv); aimv.lvs = lvs; mv = lvs; } return mv; } } } Sampler.java000066400000000000000000000034751271641324100421760ustar00rootroot00000000000000allocation-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; /** * This interface describes a function that is used to sample an allocation. * * @author jeremymanson@google.com (Jeremy Manson) */ public interface Sampler { /** * Determines whether the object currently being allocated, with the given * size, should be traced. * * CAUTION: DO NOT DO ALLOCATION IN THIS METHOD WITHOUT ENSURING THAT * THE SAMPLER WILL NOT BE INVOKED ON THE RESULTING ALLOCATION. * Otherwise, you will get an infinite regress of calls to the sampler. * * @param count the 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.java000066400000000000000000000213061271641324100441760ustar00rootroot00000000000000allocation-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.java000066400000000000000000000070461271641324100450220ustar00rootroot00000000000000allocation-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(); } }