diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java index e27bdd413..3db3e46db 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java @@ -16,6 +16,14 @@ private ConfigConstants() { public static final String ALLOW_TIME_FROM = "arex.allow.time.from"; public static final String ALLOW_TIME_TO = "arex.allow.time.to"; + /** + * Assign values via -Darex.ignore.type.prefixes=xxx.type, separate multiple values with commas. + */ + public static final String IGNORED_TYPE_PREFIXES = "arex.ignore.type.prefixes"; + /** + * Assign values via -Darex.ignore.classloader.prefixes=xxx.classloader, separate multiple values with commas. + */ + public static final String IGNORED_CLASS_LOADER_PREFIXES = "arex.ignore.classloader.prefixes"; public static final String DISABLE_MODULE = "arex.disable.instrumentation.module"; public static final String RETRANSFORM_MODULE = "arex.retransform.instrumentation.module"; diff --git a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java index f829a3d2c..312223c07 100644 --- a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java +++ b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java @@ -8,7 +8,7 @@ import io.arex.foundation.config.ConfigManager; import io.arex.agent.bootstrap.util.CollectionUtil; -import io.arex.inst.extension.matcher.IgnoredTypesMatcher; +import io.arex.inst.extension.matcher.IgnoredRawMatcher; import io.arex.inst.runtime.model.DynamicClassEntity; import io.arex.inst.runtime.model.DynamicClassStatusEnum; import io.arex.agent.bootstrap.util.ServiceLoader; @@ -181,11 +181,13 @@ private AgentBuilder.Identified.Extendable installMethod(AgentBuilder.Identified private AgentBuilder getAgentBuilder() { // config may use to add some classes to be ignored in future long buildBegin = System.currentTimeMillis(); - AgentBuilder builder = new AgentBuilder.Default( + + return new AgentBuilder.Default( new ByteBuddy().with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE)) .enableNativeMethodPrefix("arex_") .disableClassFormatChanges() - .ignore(new IgnoredTypesMatcher()) + .ignore(new IgnoredRawMatcher(ConfigManager.INSTANCE.getIgnoreTypePrefixes(), + ConfigManager.INSTANCE.getIgnoreClassLoaderPrefixes())) .with(new TransformListener()) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) @@ -194,8 +196,6 @@ private AgentBuilder getAgentBuilder() { .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST) .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG .withFallbackTo(ClassFileLocator.ForClassLoader.ofSystemLoader())); - - return builder; } private boolean disabledModule(String moduleName) { diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java index 2364e1356..3ea0f69d5 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java @@ -1,12 +1,21 @@ package io.arex.inst.extension.matcher; +import io.arex.agent.bootstrap.util.CollectionUtil; import io.arex.agent.bootstrap.util.StringUtil; +import java.util.List; import net.bytebuddy.matcher.ElementMatcher; public class IgnoreClassloaderMatcher extends ElementMatcher.Junction.AbstractBase { static final String BYTE_BUDDY_PREFIX = StringUtil.removeShadePrefix("net.bytebuddy."); - private final ElementMatcher matcher; + + private static final List IGNORED_CLASSLOADER_PREFIXES = CollectionUtil.newArrayList( + "sun.reflect.", "jdk.internal.reflect.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX); + private ElementMatcher matcher; + + public IgnoreClassloaderMatcher(List ignoreClassLoaderPrefixes) { + IGNORED_CLASSLOADER_PREFIXES.addAll(ignoreClassLoaderPrefixes); + } public IgnoreClassloaderMatcher(ElementMatcher matcher) { this.matcher = matcher; @@ -19,9 +28,14 @@ public boolean matches(ClassLoader loader) { } String loaderName = loader.getClass().getName(); - if (loaderName.startsWith("sun.reflect") || - loaderName.startsWith("jdk.internal.reflect") || - loaderName.startsWith(BYTE_BUDDY_PREFIX)) { + + for (String ignored : IGNORED_CLASSLOADER_PREFIXES) { + if (loaderName.startsWith(ignored)) { + return true; + } + } + + if (matcher == null) { return false; } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java new file mode 100644 index 000000000..c8cdcfb67 --- /dev/null +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java @@ -0,0 +1,29 @@ +package io.arex.inst.extension.matcher; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.utility.JavaModule; +import net.bytebuddy.utility.nullability.MaybeNull; + +import java.security.ProtectionDomain; +import java.util.List; + +public class IgnoredRawMatcher implements AgentBuilder.RawMatcher { + private final ElementMatcher.Junction typeMatcher; + private final ElementMatcher.Junction classLoaderMatcher; + + public IgnoredRawMatcher(List ignoreTypePrefixes, List ignoreClassLoaderPrefixes) { + this.typeMatcher = new IgnoredTypesMatcher(ignoreTypePrefixes); + this.classLoaderMatcher = new IgnoreClassloaderMatcher(ignoreClassLoaderPrefixes); + } + + @Override + public boolean matches(TypeDescription typeDescription, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, + ProtectionDomain protectionDomain) { + return typeMatcher.matches(typeDescription) || classLoaderMatcher.matches(classLoader); + } +} diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java index 9c9c07445..960ba1dac 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java @@ -1,15 +1,22 @@ package io.arex.inst.extension.matcher; import io.arex.agent.bootstrap.cache.AdviceInjectorCache; +import io.arex.agent.bootstrap.util.CollectionUtil; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import java.util.List; + public class IgnoredTypesMatcher extends ElementMatcher.Junction.AbstractBase { - private static final String[] IGNORED_STARTS_WITH_NAME = new String[]{ + private static final List IGNORED_TYPE_PREFIXES = CollectionUtil.newArrayList( "io.arex.", "shaded.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX, - "sun.reflect.", "org.springframework.boot.autoconfigure", "com.intellij."}; + "sun.reflect.", "org.springframework.boot.autoconfigure", "com.intellij."); + + private static final String[] IGNORED_CONTAINS_NAME = new String[]{"javassist.", ".asm.", ".reflectasm.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX}; - private static final String[] IGNORED_CONTAINS_NAME = new String[]{"javassist.", ".asm.", ".reflectasm."}; + public IgnoredTypesMatcher(List ignoreTypePrefixes) { + IGNORED_TYPE_PREFIXES.addAll(ignoreTypePrefixes); + } @Override public boolean matches(TypeDescription target) { @@ -18,7 +25,7 @@ public boolean matches(TypeDescription target) { } String name = target.getActualName(); - for (String ignored : IGNORED_STARTS_WITH_NAME) { + for (String ignored : IGNORED_TYPE_PREFIXES) { if (name.startsWith(ignored)) { return true; } diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java index a30dcdf58..b8d1ab8c7 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java @@ -2,13 +2,15 @@ import static org.junit.jupiter.api.Assertions.*; -import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Collections; + import org.junit.jupiter.api.Test; class IgnoreClassloaderMatcherTest { @Test - void matches() { + void testMatchesMatcher() { IgnoreClassloaderMatcher matcher = new IgnoreClassloaderMatcher( new HasClassNameMatcher("io.arex.inst.extension.matcher.HasClassNameMatcher")); @@ -16,4 +18,21 @@ void matches() { assertTrue(matcher.matches(Thread.currentThread().getContextClassLoader())); } -} \ No newline at end of file + + @Test + void testMatchesIgnoredLoaders() { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + IgnoreClassloaderMatcher matcher = new IgnoreClassloaderMatcher(Collections.singletonList(contextClassLoader.getClass().getName())); + + assertFalse(matcher.matches(null)); + + // url class loader not matches ignored class loaders + assertFalse(matcher.matches(URLClassLoader.class.getClassLoader())); + + // app class loader, matches ignored class loaders + assertTrue(matcher.matches(contextClassLoader), () -> "contextClassLoader: " + contextClassLoader.getClass().getName()); + + // matcher is null + assertFalse(matcher.matches(contextClassLoader.getParent())); + } +} diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java new file mode 100644 index 000000000..359633e4e --- /dev/null +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java @@ -0,0 +1,48 @@ +package io.arex.inst.extension.matcher; + +import net.bytebuddy.description.type.TypeDescription; +import org.junit.jupiter.api.Test; + +import java.security.ProtectionDomain; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class IgnoredRawMatcherTest { + + @Test + void matches() { + ProtectionDomain protectionDomain = new ProtectionDomain(null, null); + TypeDescription.ForLoadedType typeDescription = new TypeDescription.ForLoadedType(String.class); + ClassLoader classLoader = IgnoredRawMatcherTest.class.getClassLoader(); + + // case 1 + // ignore type: false (classloader is null) + IgnoredRawMatcher matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.emptyList()); + assertFalse(matcher.matches(typeDescription, null, null, null, protectionDomain)); + + // case 2 + // ignore type: false + // ignore classloader: false + matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.emptyList()); + assertFalse(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 3 + // ignore type: true + // ignore classloader: false + matcher = new IgnoredRawMatcher(Collections.singletonList(typeDescription.getActualName()), Collections.emptyList()); + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 4 + // ignore type: false + // ignore classloader: true + matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.singletonList(classLoader.getClass().getName())); + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 3 + // ignore type: true + // ignore classloader: true + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + } +} diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java index 4ef8ec4ec..e5218078c 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java @@ -3,13 +3,15 @@ import net.bytebuddy.description.type.TypeDescription; import org.junit.jupiter.api.Test; +import java.util.Collections; + import static org.junit.jupiter.api.Assertions.*; class IgnoredTypesMatcherTest { @Test void matches() { - IgnoredTypesMatcher matcher = new IgnoredTypesMatcher(); + IgnoredTypesMatcher matcher = new IgnoredTypesMatcher(Collections.emptyList()); assertTrue(matcher.matches(new TypeDescription.ForLoadedType(IgnoredTypesMatcher.class))); assertFalse(matcher.matches(new TypeDescription.ForLoadedType(String.class))); } diff --git a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java index 9c01f3e7f..b970d8169 100644 --- a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java +++ b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java @@ -53,6 +53,8 @@ public class ConfigManager { private EnumSet allowDayOfWeeks; private LocalTime allowTimeOfDayFrom; private LocalTime allowTimeOfDayTo; + private List ignoreTypePrefixes; + private List ignoreClassLoaderPrefixes; private List disabledModules; private List retransformModules; private Set excludeServiceOperations; @@ -263,6 +265,8 @@ void init() { setAllowDayOfWeeks(Integer.parseInt(System.getProperty(ALLOW_DAY_WEEKS, "127"))); setAllowTimeOfDayFrom(System.getProperty(ALLOW_TIME_FROM, "00:01")); setAllowTimeOfDayTo(System.getProperty(ALLOW_TIME_TO, "23:59")); + setIgnoreTypePrefixes(System.getProperty(IGNORED_TYPE_PREFIXES)); + setIgnoreClassLoaderPrefixes(System.getProperty(IGNORED_CLASS_LOADER_PREFIXES)); setDisabledModules(System.getProperty(DISABLE_MODULE)); setRetransformModules(System.getProperty(RETRANSFORM_MODULE, "dynamic-class")); setExcludeServiceOperations(System.getProperty(EXCLUDE_SERVICE_OPERATION)); @@ -500,6 +504,30 @@ private long nextWorkTime() { return Duration.between(LocalDateTime.now(), nextTime).toMillis(); } + public List getIgnoreTypePrefixes() { + return ignoreTypePrefixes; + } + + private void setIgnoreTypePrefixes(String ignoredTypes) { + if (StringUtil.isEmpty(ignoredTypes)) { + this.ignoreTypePrefixes = Collections.emptyList(); + } else { + this.ignoreTypePrefixes = Arrays.asList(StringUtil.split(ignoredTypes, ',')); + } + } + + public List getIgnoreClassLoaderPrefixes() { + return ignoreClassLoaderPrefixes; + } + + private void setIgnoreClassLoaderPrefixes(String ignoreClassLoaderPrefixes) { + if (StringUtil.isEmpty(ignoreClassLoaderPrefixes)) { + this.ignoreClassLoaderPrefixes = Collections.emptyList(); + } else { + this.ignoreClassLoaderPrefixes = Arrays.asList(StringUtil.split(ignoreClassLoaderPrefixes, ',')); + } + } + public List getDisabledModules() { return disabledModules; }