blob: 0d10a039bb2f65abcaa5827b6261e84576f9485d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 net.bytebuddy.agent.builder;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.*;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* A DescriptionStrategy to get the original class description by removing dynamic field and method tokens
* generated by SkyWalking.
*/
public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
/**
* A cache of type descriptions for commonly used types to avoid unnecessary allocations.
*/
private static final Map<Class<?>, TypeDescription> TYPE_CACHE;
/*
* Initializes the type cache.
*/
static {
TYPE_CACHE = new HashMap<Class<?>, TypeDescription>();
TYPE_CACHE.put(TargetType.class, new TypeDescription.ForLoadedType(TargetType.class));
TYPE_CACHE.put(Class.class, new TypeDescription.ForLoadedType(Class.class));
TYPE_CACHE.put(Throwable.class, new TypeDescription.ForLoadedType(Throwable.class));
TYPE_CACHE.put(Annotation.class, new TypeDescription.ForLoadedType(Annotation.class));
TYPE_CACHE.put(Object.class, new TypeDescription.ForLoadedType(Object.class));
TYPE_CACHE.put(String.class, new TypeDescription.ForLoadedType(String.class));
TYPE_CACHE.put(Boolean.class, new TypeDescription.ForLoadedType(Boolean.class));
TYPE_CACHE.put(Byte.class, new TypeDescription.ForLoadedType(Byte.class));
TYPE_CACHE.put(Short.class, new TypeDescription.ForLoadedType(Short.class));
TYPE_CACHE.put(Character.class, new TypeDescription.ForLoadedType(Character.class));
TYPE_CACHE.put(Integer.class, new TypeDescription.ForLoadedType(Integer.class));
TYPE_CACHE.put(Long.class, new TypeDescription.ForLoadedType(Long.class));
TYPE_CACHE.put(Float.class, new TypeDescription.ForLoadedType(Float.class));
TYPE_CACHE.put(Double.class, new TypeDescription.ForLoadedType(Double.class));
TYPE_CACHE.put(void.class, new TypeDescription.ForLoadedType(void.class));
TYPE_CACHE.put(boolean.class, new TypeDescription.ForLoadedType(boolean.class));
TYPE_CACHE.put(byte.class, new TypeDescription.ForLoadedType(byte.class));
TYPE_CACHE.put(short.class, new TypeDescription.ForLoadedType(short.class));
TYPE_CACHE.put(char.class, new TypeDescription.ForLoadedType(char.class));
TYPE_CACHE.put(int.class, new TypeDescription.ForLoadedType(int.class));
TYPE_CACHE.put(long.class, new TypeDescription.ForLoadedType(long.class));
TYPE_CACHE.put(float.class, new TypeDescription.ForLoadedType(float.class));
TYPE_CACHE.put(double.class, new TypeDescription.ForLoadedType(double.class));
}
private AgentBuilder.DescriptionStrategy delegate = Default.HYBRID;
private String nameTrait;
public SWDescriptionStrategy(String nameTrait) {
this.nameTrait = nameTrait;
}
@Override
public boolean isLoadedFirst() {
return true;
}
@Override
public TypeDescription apply(String name,
@MaybeNull Class<?> type,
TypePool typePool,
AgentBuilder.CircularityLock circularityLock,
@MaybeNull ClassLoader classLoader,
@MaybeNull JavaModule module) {
// find from type cache
if (type != null) {
TypeDescription typeDescription = TYPE_CACHE.get(type);
if (typeDescription != null) {
return typeDescription;
}
}
// wrap result
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait, classLoader, name);
}
/**
* A TypeDescription wrapper to remove fields, methods, interface generated by SkyWalking.
*/
static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase implements Serializable {
/**
* The class's serial version UID.
*/
private static final long serialVersionUID = 1L;
/**
* Original type cache.
* ClassLoader -> (typeName -> TypeCache)
* Using WeakHashMap to automatically clean up cache when ClassLoader is garbage collected.
*/
private static final Map<ClassLoader, Map<String, TypeCache>> CLASS_LOADER_TYPE_CACHE =
Collections.synchronizedMap(new WeakHashMap<>());
/**
* Bootstrap ClassLoader cache (null key cannot be stored in WeakHashMap)
*/
private static final Map<String, TypeCache> BOOTSTRAP_TYPE_CACHE = new ConcurrentHashMap<>();
private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());
private MethodList<MethodDescription.InDefinedShape> methods;
private FieldList<FieldDescription.InDefinedShape> fields;
private final String nameTrait;
private ClassLoader classLoader;
private String typeName;
private TypeList.Generic interfaces;
private TypeDescription delegate;
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait, ClassLoader classLoader, String typeName) {
this.delegate = delegate;
this.nameTrait = nameTrait;
this.classLoader = classLoader;
this.typeName = typeName;
}
private TypeCache getTypeCache() {
if (classLoader == null) {
// Bootstrap ClassLoader - use separate cache since null key cannot be stored in WeakHashMap
return BOOTSTRAP_TYPE_CACHE.computeIfAbsent(typeName, k -> new TypeCache(typeName));
} else {
// Regular ClassLoader - use WeakHashMap for automatic cleanup
Map<String, TypeCache> typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent(
classLoader, k -> new ConcurrentHashMap<>());
return typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName));
}
}
@Override
public TypeList.Generic getInterfaces() {
if (this.interfaces == null) {
TypeList.Generic allInterfaces = delegate.getInterfaces();
if (allInterfaces.stream().anyMatch(s -> IGNORED_INTERFACES.contains(s.getTypeName()))) {
// remove interfaces added by SkyWalking
List<Generic> list = allInterfaces.stream()
.filter(s -> !IGNORED_INTERFACES.contains(s.getTypeName()))
.collect(Collectors.toList());
this.interfaces = new TypeList.Generic.Explicit(list);
} else {
this.interfaces = allInterfaces;
}
}
return this.interfaces;
}
@Override
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
if (this.fields == null) {
FieldList<FieldDescription.InDefinedShape> declaredFields = delegate.getDeclaredFields();
TypeCache typeCache = getTypeCache();
if (typeCache.fieldNames == null) {
// save origin fields
typeCache.fieldNames = declaredFields.stream().map(WithRuntimeName::getName).collect(Collectors.toSet());
fields = declaredFields;
} else {
// return origin fields
fields = new FieldList.Explicit<>(declaredFields.stream()
.filter(f -> typeCache.fieldNames.contains(f.getName()))
.collect(Collectors.toList()));
}
}
return fields;
}
@Override
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
if (this.methods == null) {
MethodList<MethodDescription.InDefinedShape> declaredMethods = delegate.getDeclaredMethods();
TypeCache typeCache = getTypeCache();
if (typeCache.methodCodes == null) {
// save original methods
typeCache.methodCodes = declaredMethods.stream().map(m -> m.toString().hashCode()).collect(Collectors.toSet());
methods = declaredMethods;
} else {
// return original methods in the same order, remove dynamic method tokens generated by SkyWalking and ByteBuddy
// remove generated methods for delegating superclass methods, such as Jedis.
methods = new MethodList.Explicit<>(declaredMethods.stream()
.filter(m -> typeCache.methodCodes.contains(m.toString().hashCode()))
.collect(Collectors.toList()));
}
}
return methods;
}
@Override
public boolean isAssignableTo(Class<?> type) {
// ignore interface added by SkyWalking
if (IGNORED_INTERFACES.contains(type.getName())) {
return false;
}
return delegate.isAssignableTo(type);
}
@Override
public boolean isAccessibleTo(TypeDescription typeDescription) {
// ignore interface added by SkyWalking
if (IGNORED_INTERFACES.contains(typeDescription.getName())) {
return false;
}
return delegate.isAccessibleTo(typeDescription);
}
@Override
public RecordComponentList<RecordComponentDescription.InDefinedShape> getRecordComponents() {
return delegate.getRecordComponents();
}
@Override
public TypeDescription getComponentType() {
return delegate.getComponentType();
}
@Override
public TypeDescription getDeclaringType() {
return delegate.getDeclaringType();
}
@Override
public TypeList getDeclaredTypes() {
return delegate.getDeclaredTypes();
}
@Override
public MethodDescription.InDefinedShape getEnclosingMethod() {
return delegate.getEnclosingMethod();
}
@Override
public TypeDescription getEnclosingType() {
return delegate.getEnclosingType();
}
@Override
public String getSimpleName() {
return delegate.getSimpleName();
}
@Override
public String getCanonicalName() {
return delegate.getCanonicalName();
}
@Override
public boolean isAnonymousType() {
return delegate.isAnonymousType();
}
@Override
public boolean isLocalType() {
return delegate.isLocalType();
}
@Override
public PackageDescription getPackage() {
return delegate.getPackage();
}
@Override
public TypeDescription getNestHost() {
return delegate.getNestHost();
}
@Override
public TypeList getNestMembers() {
return delegate.getNestMembers();
}
@Override
public TypeList getPermittedSubtypes() {
return delegate.getPermittedSubtypes();
}
@Override
public String getDescriptor() {
return delegate.getDescriptor();
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public TypeList.Generic getTypeVariables() {
return delegate.getTypeVariables();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return delegate.getDeclaredAnnotations();
}
@Override
public Generic getSuperClass() {
return delegate.getSuperClass();
}
@Override
public StackSize getStackSize() {
return delegate.getStackSize();
}
@Override
public boolean isArray() {
return delegate.isArray();
}
@Override
public boolean isRecord() {
return delegate.isRecord();
}
@Override
public boolean isPrimitive() {
return delegate.isPrimitive();
}
@Override
public int getModifiers() {
return delegate.getModifiers();
}
}
static class TypeCache {
private String typeName;
private Set<Integer> methodCodes;
private Set<String> fieldNames;
public TypeCache(String typeName) {
this.typeName = typeName;
}
}
}