com.newrelic.agent.util.asm.ClassStructure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of newrelic-agent Show documentation
Show all versions of newrelic-agent Show documentation
The New Relic Java agent for full-stack observability
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.newrelic.agent.util.asm;
import com.google.common.collect.ImmutableMap;
import com.newrelic.agent.Agent;
import com.newrelic.agent.bridge.reflect.ClassReflection;
import com.newrelic.weave.utils.SynchronizedFieldNode;
import com.newrelic.weave.utils.WeaveUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.tree.FieldNode;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
public class ClassStructure {
/**
* This flag indicates the method names of the class should be collected.
*/
public static final int METHODS = 0x0001;
/**
* This flag indicates the fields of the class should be collected.
*/
public static final int FIELDS = 0x0002;
/**
* This flag indicates the class annotations on the class should be collected.
*/
public static final int CLASS_ANNOTATIONS = 0x0004;
/**
* This flag indicates the method annotations on all of the methods should be collected. It implies the
* {@link ClassStructure#METHODS} flag.
*/
public static final int METHOD_ANNOTATIONS = 0x0008;
/**
* This flag indicates that all details about the class should be collected.
*/
public static final int ALL = METHODS + FIELDS + CLASS_ANNOTATIONS + METHOD_ANNOTATIONS;
private Map methods;
private Map fields;
private final Type type;
protected final int access;
protected final String superName;
protected final String[] interfaces;
protected Map classAnnotations;
private ClassStructure(String className, int access, String superName, String[] interfaceNames) {
type = Type.getObjectType(className);
this.access = access;
this.superName = superName;
this.interfaces = interfaceNames;
}
public int getAccess() {
return access;
}
/**
* Returns the super class internal name, or null if there is no super class.
*/
public String getSuperName() {
return superName;
}
public Type getType() {
return type;
}
/**
* Returns the class' methods (if the {@link #CLASS_ANNOTATIONS} flag was set).
*/
public Set getMethods() {
return methods.keySet();
}
public Map getFields() {
return fields;
}
/**
* Returns the annotations for a method (if the {@link #METHOD_ANNOTATIONS} flag was set). The key is the annotation
* descriptor.
*
* @see Type#getDescriptor(Class)
*/
public Map getMethodAnnotations(Method method) {
MethodDetails methodDetails = methods.get(method);
if (methodDetails == null) {
return Collections.emptyMap();
}
return methodDetails.annotations;
}
public String[] getInterfaces() {
return interfaces;
}
/**
* Returns the annotations for the class (if the {@link #CLASS_ANNOTATIONS} flag was set). The key is the annotation
* descriptor.
*
* @see Type#getDescriptor(Class)
*/
public Map getClassAnnotations() {
return classAnnotations;
}
@Override
public String toString() {
return type.getClassName();
}
public static ClassStructure getClassStructure(URL url) throws IOException {
return getClassStructure(url, METHODS);
}
public static ClassStructure getClassStructure(URL url, int flags) throws IOException {
return getClassStructure(Utils.getClassReaderFromResource(url.getPath(), url), flags);
}
public static ClassStructure getClassStructure(ClassReader cr, int flags) throws IOException {
ClassStructure structure = new ClassStructure(cr.getClassName(), cr.getAccess(), cr.getSuperName(),
cr.getInterfaces());
ClassVisitor cv = structure.createClassVisitor(flags);
if (cv != null) {
cr.accept(cv, ClassReader.SKIP_CODE);
}
structure.methods = structure.methods == null ? Collections.emptyMap()
: Collections.unmodifiableMap(structure.methods);
structure.classAnnotations = structure.classAnnotations == null ? Collections.emptyMap()
: Collections.unmodifiableMap(structure.classAnnotations);
structure.fields = structure.fields == null ? Collections.emptyMap()
: Collections.unmodifiableMap(structure.fields);
return structure;
}
private ClassVisitor createClassVisitor(final int flags) {
ClassVisitor cv = null;
if (isMethodFlagSet(flags)) {
cv = new ClassVisitor(WeaveUtils.ASM_API_LEVEL, cv) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
if (null == methods) {
methods = new HashMap<>();
}
boolean isStatic = (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
Method method = new Method(name, desc);
if ((flags & METHOD_ANNOTATIONS) == METHOD_ANNOTATIONS) {
final MethodDetails details = new MethodDetails(new HashMap(),
isStatic);
methods.put(method, details);
return new MethodVisitor(WeaveUtils.ASM_API_LEVEL, super.visitMethod(access, name, desc,
signature, exceptions)) {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationDetails annotation = new AnnotationDetails(super.visitAnnotation(desc,
visible), desc);
details.annotations.put(desc, annotation);
return annotation;
}
};
} else {
methods.put(method, isStatic ? EMPTY_METHOD_DEFAULTS_STATIC : EMPTY_METHOD_DEFAULTS_MEMBER);
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
};
}
if ((flags & CLASS_ANNOTATIONS) == CLASS_ANNOTATIONS) {
cv = new ClassVisitor(WeaveUtils.ASM_API_LEVEL, cv) {
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (null == classAnnotations) {
classAnnotations = new HashMap<>();
}
AnnotationDetails annotation = new AnnotationDetails(super.visitAnnotation(desc, visible), desc);
classAnnotations.put(desc, annotation);
return annotation;
}
};
}
if (isFieldFlagSet(flags)) {
cv = new ClassVisitor(WeaveUtils.ASM_API_LEVEL, cv) {
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
FieldNode field = new SynchronizedFieldNode(access, name, desc, signature, value);
if (fields == null) {
fields = new HashMap<>();
}
fields.put(name, field);
return super.visitField(access, name, desc, signature, value);
}
};
}
return cv;
}
public static ClassStructure getClassStructure(final Class clazz) {
return getClassStructure(clazz, METHODS);
}
public static ClassStructure getClassStructure(final Class clazz, final int flags) {
int access = 0;
int modifiers = clazz.getModifiers();
String superName = null;
if (clazz.isAnnotation()) {
access |= Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE;
// the access modifiers for class visibility are wonky for Annotations
if (!Modifier.isPrivate(modifiers)) {
access |= Opcodes.ACC_PUBLIC;
}
superName = "java/lang/Object";
} else if (clazz.isInterface()) {
access |= Opcodes.ACC_INTERFACE;
superName = "java/lang/Object";
} else if (clazz.isEnum()) {
access |= Opcodes.ACC_ENUM | Opcodes.ACC_SUPER;
} else {
access |= Opcodes.ACC_SUPER;
}
if (Modifier.isAbstract(modifiers)) {
access |= Opcodes.ACC_ABSTRACT;
}
if (!clazz.isAnnotation()) {
if (Modifier.isPublic(modifiers)) {
access |= Opcodes.ACC_PUBLIC;
} else if (Modifier.isPrivate(modifiers)) {
access |= Opcodes.ACC_PRIVATE;
} else if (Modifier.isProtected(modifiers)) {
access |= Opcodes.ACC_PROTECTED;
}
}
if (Modifier.isFinal(modifiers)) {
access |= Opcodes.ACC_FINAL;
}
if (clazz.getSuperclass() != null) {
superName = Type.getType(clazz.getSuperclass()).getInternalName();
}
// int ACC_SYNTHETIC = 0x1000; // class, field, method
// int ACC_ANNOTATION = 0x2000; // class
// int ACC_ENUM = 0x4000; // class(?) field inner
String[] interfaces = new String[clazz.getInterfaces().length];
for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = Type.getType(clazz.getInterfaces()[i]).getInternalName();
}
final ClassStructure structure = new ClassStructure(Type.getType(clazz).getInternalName(), access, superName,
interfaces);
if ((flags & CLASS_ANNOTATIONS) == CLASS_ANNOTATIONS) {
Annotation[] annotations = clazz.getAnnotations();
if (annotations.length > 0) {
structure.classAnnotations = new HashMap<>();
for (Annotation annotation : annotations) {
AnnotationDetails annotationDetails = getAnnotationDetails(annotation);
structure.classAnnotations.put(annotationDetails.desc, annotationDetails);
}
}
}
if (structure.classAnnotations == null) {
structure.classAnnotations = Collections.emptyMap();
}
if (isFieldFlagSet(flags)) {
structure.fields = new HashMap<>();
Field[] declaredFields = ClassReflection.getDeclaredFields(clazz);
for (Field f : declaredFields) {
FieldNode field = new SynchronizedFieldNode(0, f.getName(), Type.getDescriptor(f.getDeclaringClass()), null, null);
structure.fields.put(f.getName(), field);
}
} else {
structure.fields = ImmutableMap.of();
}
if (isMethodFlagSet(flags)) {
structure.methods = new HashMap<>();
try {
AccessController.doPrivileged(new PrivilegedExceptionAction