com.newrelic.weave.weavepackage.WeavePackage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of newrelic-weaver Show documentation
Show all versions of newrelic-weaver Show documentation
The Weaver of the Java agent.
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.newrelic.weave.weavepackage;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.WeaveWithAnnotation;
import com.newrelic.weave.MethodProcessors;
import com.newrelic.weave.utils.BootstrapLoader;
import com.newrelic.weave.utils.ClassCache;
import com.newrelic.weave.utils.ClassInformation;
import com.newrelic.weave.utils.ReferenceUtils;
import com.newrelic.weave.utils.Streams;
import com.newrelic.weave.utils.SynchronizedClassNode;
import com.newrelic.weave.utils.WeaveClassInfo;
import com.newrelic.weave.utils.WeaveUtils;
import com.newrelic.weave.violation.WeaveViolation;
import com.newrelic.weave.weavepackage.language.LanguageAdapter;
import com.newrelic.weave.weavepackage.language.LanguageAdapterResult;
import com.newrelic.weave.weavepackage.language.RegisteredLanguageAdapters;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import static com.newrelic.api.agent.weaver.MatchType.Interface;
/**
* A WeavePackage does the following:
*
* - Stores the weave bytes from the weave module. Separates bytes into exact and base matches.
* - Stores the utility class bytes (non-weaved) from the weave module.
*
*
* For the agent, there is one WeavePackage per instrumentation module.
*/
public class WeavePackage {
/**
* Create a WeavePackage from a jar source.
*
* @param jarStream steam of a JAR file
* @param config {@link WeavePackageConfig} containing metadata about the package
* @return package containing
*/
public static WeavePackage createWeavePackage(JarInputStream jarStream, WeavePackageConfig config) throws Exception {
List classBytes = new ArrayList<>();
JarEntry entry = null;
while ((entry = jarStream.getNextJarEntry()) != null) {
if (entry.getName().endsWith(".class")) {
classBytes.add(Streams.read(jarStream, false));
}
}
return new WeavePackage(config, classBytes);
}
// the weaves maps store the source bytes for weave classes
private final Map exactWeaves = new ConcurrentHashMap<>();
private final Map baseWeaves = new ConcurrentHashMap<>();
private final Map utilClasses = new ConcurrentHashMap<>();
private final Map weaveMatches = new ConcurrentHashMap<>();
/**
* This is to support matches that have no name. That is, {@link WeaveWithAnnotation} with no @Weave(originalName)
*/
private final Map allClassAnnotationWeaves = new ConcurrentHashMap<>();
private final Map allMethodAnnotationWeaves = new ConcurrentHashMap<>();
private final Map baseAnnotationWeaves = new ConcurrentHashMap<>();
private final Map> requiredClassAnnotationsLookup = new ConcurrentHashMap<>();
private final Map> requiredMethodAnnotationsLookup = new ConcurrentHashMap<>();
private final Map references = new ConcurrentHashMap<>();
private final Set skipIfPresentClasses = Sets.newConcurrentHashSet();
private final Set methodSignatures = Sets.newConcurrentHashSet();
private final Queue packageViolations = Queues.newConcurrentLinkedQueue();
private final Map renames = new ConcurrentHashMap<>();
private final WeavePackageConfig config;
private volatile boolean weavesBootstrap = false;
private ClassNode errorHandler;
private ClassNode extensionTemplate;
/**
* Create a WeavePackage from a list of weave class bytes.
* @param config {@link WeavePackageConfig} containing metadata about the package
* @param weavePackageBytes list of all class bytes in the package
*/
public WeavePackage(WeavePackageConfig config, List weavePackageBytes) {
this.config = config;
processWeaveBytes(weavePackageBytes);
}
/**
* Processes all classes in the package. Part of package initialization.
* @param weavePackageBytes list of all class bytes in the package
* @return list of weave violations found during package initialization
*/
protected final List processWeaveBytes(List weavePackageBytes) {
List violations = new ArrayList<>();
for (LanguageAdapter adapter : RegisteredLanguageAdapters.getLanguageAdapters()) {
try {
LanguageAdapterResult result = adapter.adapt(weavePackageBytes);
weavePackageBytes = result.getAdaptedBytes();
violations.addAll(result.getViolations());
} catch (Throwable ignored) {
}
}
// read weave annotations on each class bytes and put in appropriate map
for (byte[] weaveClassBytes : weavePackageBytes) {
ClassNode weaveNode = WeaveUtils.convertToClassNode(weaveClassBytes);
WeaveClassInfo weave = new WeaveClassInfo(weaveNode);
violations.addAll(weave.getViolations());
final boolean isClassAnnotationMatch = !weave.getRequiredClassAnnotations().isEmpty();
final boolean isMethodAnnotationMatch = !weave.getRequiredMethodAnnotations().isEmpty();
if (isClassAnnotationMatch || isMethodAnnotationMatch) {
if (isClassAnnotationMatch) {
for (String requiredAnnotation : weave.getRequiredClassAnnotations()) {
if (weave.getMatchType().equals(Interface)) {
baseAnnotationWeaves.put(requiredAnnotation, weaveNode);
}
allClassAnnotationWeaves.put(requiredAnnotation, weaveNode);
}
requiredClassAnnotationsLookup.put(weaveNode.name, weave.getRequiredClassAnnotations());
}
if (isMethodAnnotationMatch) {
for (String requiredAnnotation : weave.getRequiredMethodAnnotations()) {
if (!isClassAnnotationMatch) {
allMethodAnnotationWeaves.put(requiredAnnotation, weaveNode);
} else if (!allMethodAnnotationWeaves.containsKey(requiredAnnotation)) {
allMethodAnnotationWeaves.put(requiredAnnotation, weaveNode);
}
}
requiredMethodAnnotationsLookup.put(weaveNode.name, weave.getRequiredMethodAnnotations());
}
} else if (null == weave.getMatchType()) {
if (weave.isSkipIfPresent()) {
skipIfPresentClasses.add(weave.getOriginalName());
} else {
utilClasses.put(weaveNode.name, weaveNode);
}
} else {
if (!weaveNode.name.equals(weave.getOriginalName())) {
renames.put(weaveNode.name, weave.getOriginalName());
}
// check if added and merge
weaveMatches.put(weave.getOriginalName(), weave.getMatchType());
switch (weave.getMatchType()) {
case BaseClass:
case Interface:
baseWeaves.put(weave.getOriginalName(), weaveNode);
break;
case ExactClass:
default:
exactWeaves.put(weave.getOriginalName(), weaveNode);
break;
}
}
}
// preprocess
this.preprocessAllWeaveCode();
this.packageViolations.addAll(violations);
if (isBootstrapClassName(this.exactWeaves.keySet()) || isBootstrapClassName(this.baseWeaves.keySet())) {
this.weavesBootstrap = true;
}
return violations;
}
/**
* Indicates whether any of the specified class names are loaded on the bootstrap.
* @param names collection of class names
* @return true
if any of the classes are loaded on the bootstrap
*/
public boolean isBootstrapClassName(Collection names) {
for (String name : names) {
if (BootstrapLoader.get().isBootstrapClass(name)) {
return true;
}
}
return false;
}
/**
* Runs the pre-processor on all of the classes in the package. Part of package initialization.
*/
private void preprocessAllWeaveCode() {
// AgentPreprocessors may change the name of utility classes.
Set renamedUtilityClassesToRemove = new HashSet<>();
for (ClassNode node : this.utilClasses.values()) {
String nameBefore = node.name;
node = preprocess(node);
utilClasses.put(node.name, node);
if (!node.name.equals(nameBefore)) {
renamedUtilityClassesToRemove.add(nameBefore);
}
}
for (Entry entry : this.exactWeaves.entrySet()) {
ClassNode node = preprocess(entry.getValue());
entry.setValue(node);
}
for (Entry entry : this.baseWeaves.entrySet()) {
ClassNode node = preprocess(entry.getValue());
entry.setValue(node);
}
for (Entry entry : this.allClassAnnotationWeaves.entrySet()) {
ClassNode node = preprocess(entry.getValue());
entry.setValue(node);
}
for (Entry entry : this.allMethodAnnotationWeaves.entrySet()) {
ClassNode node = preprocess(entry.getValue());
entry.setValue(node);
}
for (Entry entry : this.baseAnnotationWeaves.entrySet()) {
ClassNode node = preprocess(entry.getValue());
entry.setValue(node);
}
this.errorHandler = preprocess(config.getErrorHandleClassNode());
this.extensionTemplate = preprocess(config.getExtensionTemplate());
for (String renamedUtilityClass : renamedUtilityClassesToRemove) {
utilClasses.remove(renamedUtilityClass);
}
// now that all the preprocessing is done, we can create our references
for (ClassNode node : this.utilClasses.values()) {
checkReferences(node);
}
for (Entry entry : this.exactWeaves.entrySet()) {
checkReferences(entry.getValue());
for (MethodNode methodNode : entry.getValue().methods) {
if (WeaveUtils.isEmptyConstructor(methodNode)) {
// Generated empty constructor
continue;
}
methodSignatures.add(methodNode.name + methodNode.desc);
}
}
for (Entry entry : this.baseWeaves.entrySet()) {
checkReferences(entry.getValue());
for (MethodNode methodNode : entry.getValue().methods) {
if (WeaveUtils.isEmptyConstructor(methodNode)) {
// Generated empty constructor
continue;
}
methodSignatures.add(methodNode.name + methodNode.desc);
}
}
}
/**
* Find types/fields/methods referenced by the specified class
*
* @param node ClassNode to check
*/
private void checkReferences(ClassNode node) {
Set referencedClasses = Reference.create(node);
for (Reference reference : referencedClasses) {
if (reference.className.startsWith("java/") || reference.className.startsWith("javax/")
|| reference.className.startsWith("com/newrelic/api/")
|| reference.className.startsWith("com/newrelic/agent/")
|| utilClasses.containsKey(reference.className) || exactWeaves.containsKey(reference.className)
|| baseWeaves.containsKey(reference.className)) {
// compiler already validated most of these references
continue;
}
if (this.references.containsKey(reference.className)) {
Reference otherReference = this.references.get(reference.className);
otherReference.merge(reference);
} else {
this.references.put(reference.className, reference);
}
}
}
/**
* Run the pre-processor on a specific class in the package. Part of package initialization.
*
* @param input class to process
* @return processed class
*/
private ClassNode preprocess(ClassNode input) {
ClassNode result = new SynchronizedClassNode(WeaveUtils.ASM_API_LEVEL);
// user configured preprocessors go first
ClassVisitor preprocessChain = config.getPreprocessor().preprocess(result, utilClasses.keySet(), this);
preprocessChain = MethodProcessors.fixInvocationInstructions(preprocessChain, weaveMatches);
preprocessChain = MethodProcessors.replaceGetImplementationTitle(preprocessChain, this.getName());
if (renames.size() > 0) {
preprocessChain = ReferenceUtils.getRenamingVisitor(renames, preprocessChain);
}
input.accept(preprocessChain);
return result;
}
/**
* Returns true if this package contains at least one matcher for a class.
*/
public boolean hasMatcher(String className, String[] superNames, String[] interfaceNames,
Set classAnnotations, Set methodAnnotations, ClassCache classCache) throws IOException {
if (this.exactWeaves.containsKey(className) || this.baseWeaves.containsKey(className))
return true;
for (int i = 0; i < superNames.length; ++i) {
if (this.baseWeaves.containsKey(superNames[i]))
return true;
}
for (int i = 0; i < interfaceNames.length; ++i) {
if (this.baseWeaves.containsKey(interfaceNames[i]))
return true;
}
// Check to see if any of the annotations on the class exist in the set of required Exact match annotations
Set matchingAnnotations = Sets.intersection(allClassAnnotationWeaves.keySet(), classAnnotations);
if (!matchingAnnotations.isEmpty()) {
return true;
}
// If we have any base/interface annotation weaves check to see if the interfaces have the correct annotation(s)
if (!baseAnnotationWeaves.isEmpty()) {
Set requiredBaseAnnotations = baseAnnotationWeaves.keySet();
for (String interfaceName : interfaceNames) {
ClassInformation classInformation = classCache.getClassInformation(interfaceName);
if (classHasRequiredAnnotations(classInformation, requiredBaseAnnotations)) {
return true;
}
}
}
// Check to see if any of the annotations on the methods exist in the set of required method annotations
Set matchingMethodAnnotations = Sets.intersection(allMethodAnnotationWeaves.keySet(), methodAnnotations);
if (!matchingMethodAnnotations.isEmpty()) {
return true;
}
return false;
}
/**
* Returns true if the provided class (represented by ClassInformation) has any of the provided requiredAnnotations
*
* @param classInformation the class to look for the annotations on
* @param requiredAnnotations the required annotations to look for
* @return true if the provided class has at least one of the required annotations, false otherwise
*/
private boolean classHasRequiredAnnotations(ClassInformation classInformation, Set requiredAnnotations) {
return classInformation != null &&
!Sets.intersection(classInformation.classAnnotationNames, requiredAnnotations).isEmpty();
}
/**
* Get violations present at the package level. Violations here means that this weave package will not validate
* against any classloader.
*
* @return a list of {@link WeaveViolation}s
*/
public List getPackageViolations() {
return new ArrayList<>(packageViolations);
}
/**
* Validate this package using the specified {@link ClassCache} for finding class metadata.
*
* @param cache {@link ClassCache} for finding class metadata
* @return result of validation
* @throws IOException
*/
public PackageValidationResult validate(ClassCache cache) throws IOException {
if (packageViolations.size() == 0) {
return new PackageValidationResult(this, cache, references.values(), exactWeaves, baseWeaves,
allClassAnnotationWeaves, baseAnnotationWeaves, allMethodAnnotationWeaves, utilClasses,
skipIfPresentClasses, errorHandler, extensionTemplate);
} else {
// if the package already has violations it will fail against every classloader. Return a failed result.
return new PackageValidationResult(this, packageViolations);
}
}
/**
* Metadata about this package.
*/
public WeavePackageConfig getConfig() {
return config;
}
/**
* Name of this package.
*/
public String getName() {
return config.getName();
}
/**
* Version of this package.
*/
public float getVersion() {
return config.getVersion();
}
/**
* Map of class name to match type, for all classes with a {@link com.newrelic.api.agent.weaver.Weave} annotation.
*/
public Map getMatchTypes() {
return this.weaveMatches;
}
/**
* Names of classes that are referenced by classes in this package.
*/
public Set getReferencedClassNames() {
return references.keySet();
}
public Map getRenames() {
return renames;
}
/**
* All the annotations specified in all {@link WeaveWithAnnotation#annotationClasses()} in this package.
*/
public Set getAllRequiredAnnotationClasses() {
return allClassAnnotationWeaves.keySet();
}
public Set getAllRequiredMethodAnnotationClasses() {
return allMethodAnnotationWeaves.keySet();
}
// package private for testing
Map getAllClassAnnotationWeaves() {
return allClassAnnotationWeaves;
}
// package private for testing
Map getAllMethodAnnotationWeaves() {
return allMethodAnnotationWeaves;
}
/**
* Returns the set of required annotation classes listed on the provided annotation weave class
*
* @param annotationWeaveClassName the name of the annotation weave class
* @return the set of required class annotations for this annotation weave
*/
public Set getRequiredAnnotationClassesForAnnotationWeave(String annotationWeaveClassName) {
final Set requiredAnnotations = requiredClassAnnotationsLookup.get(annotationWeaveClassName);
return requiredAnnotations != null ? requiredAnnotations : Collections.emptySet();
}
public Set getRequiredAnnotationClassesForMethodAnnotationWeave(String annotationWeaveClassName) {
final Set requiredAnnotations = requiredMethodAnnotationsLookup.get(annotationWeaveClassName);
return requiredAnnotations != null ? requiredAnnotations : Collections.emptySet();
}
/**
* Class names that are required to exist on the classpath for this package to be valid.
*/
public Set getRequiredClasses() {
Set required = new HashSet<>();
required.addAll(exactWeaves.keySet());
required.addAll(baseWeaves.keySet());
required.addAll(references.keySet());
required.addAll(allClassAnnotationWeaves.keySet());
required.addAll(allMethodAnnotationWeaves.keySet());
return required;
}
/**
* Class names that cannot exist on the classpath for this package to be valid.
*/
public Set getIllegalClasses() {
return skipIfPresentClasses;
}
/**
* @return true if this weave package tries to weave into a class on the bootstrap classloader.
*/
public boolean weavesBootstrap() {
return this.weavesBootstrap;
}
public Set getMethodSignatures() {
return methodSignatures;
}
@Override
public String toString() {
return "WeavePackage [config=" + config + "]";
}
Map getExactWeaves() {
return exactWeaves;
}
Map getBaseWeaves() {
return baseWeaves;
}
Map getUtilClasses() {
return utilClasses;
}
Map getReferences() {
return references;
}
void setWeavesBootstrap() {
this.weavesBootstrap = true;
}
public ClassNode getExtensionTemplate() {
return extensionTemplate;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy