| /*** |
| * 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. |
| * |
| * <p>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. |
| * |
| * <p>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;"))); |
| } |
| } |
| |
| } |