blob: 1e26d4c44344771ba01c2aac44e63799ce1c571e [file] [log] [blame]
/***
* 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;")));
}
}
}