Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to JavaAgent so that it can exclude entire ClassLoaders. #331

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@
* @author Matthias Mann
*/
public class JavaAgent {
private static final String USAGE = "Usage: vdmcbx(exclusion;...)l(exclusion;...) (verbose, debug, allow monitors, check class, allow blocking)";
private static volatile boolean ACTIVE;
private static final Set<WeakReference<ClassLoader>> classLoaders = Collections.newSetFromMap(MapUtil.<WeakReference<ClassLoader>, Boolean>newConcurrentHashMap());

public static void premain(String agentArguments, Instrumentation instrumentation) {
if (!instrumentation.isRetransformClassesSupported())
System.err.println("Retransforming classes is not supported!");

final ClassLoader cl = Thread.currentThread().getContextClassLoader();
final QuasarInstrumentor instrumentor = new QuasarInstrumentor(false);
ACTIVE = true;
SuspendableHelper.javaAgent = true;
Expand Down Expand Up @@ -135,11 +135,10 @@ public static void premain(String agentArguments, Instrumentation instrumentatio
i++;
c = agentArguments.charAt(i);
if (c != '(')
throw new IllegalStateException("Usage: vdmcbx(exclusion;...) (verbose, debug, allow monitors, check class, allow blocking)");
i++;
throw new IllegalStateException(USAGE);
StringBuilder sb = new StringBuilder();
while(true) {
c = agentArguments.charAt(i++);
c = agentArguments.charAt(++i);
if (c == ')')
break;
sb.append(c);
Expand All @@ -149,8 +148,24 @@ public static void premain(String agentArguments, Instrumentation instrumentatio
instrumentor.addExcludedPackage(x);
break;

case 'l':
++i;
c = agentArguments.charAt(i);
if (c != '(') {
throw new IllegalStateException(USAGE);
}
int j = agentArguments.indexOf(')', ++i);
if (j == -1) {
throw new IllegalStateException(USAGE);
}
String[] classLoaderExclusions = agentArguments.substring(i, j).split(";", 0);
for (String x : classLoaderExclusions) {
instrumentor.addExcludedClassLoader(x);
}
i = j;
break;
default:
throw new IllegalStateException("Usage: vdmcbx(exclusion;...) (verbose, debug, allow monitors, check class, allow blocking)");
throw new IllegalStateException(USAGE);
}
}
}
Expand Down Expand Up @@ -192,6 +207,17 @@ public Transformer(QuasarInstrumentor instrumentor) {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
}

if (!instrumentor.shouldInstrument(loader)) {
return null;
}

if (className != null && className.startsWith("clojure/lang/Compiler"))
return crazyClojureOnceDisable(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public final class QuasarInstrumentor {
private boolean allowMonitors;
private boolean allowBlocking;
private final Collection<Pattern> exclusions = new ArrayList<>();
private final Collection<Pattern> excludedClassLoaders = new ArrayList<>();
private Log log;
private boolean verbose;
private boolean debug;
Expand All @@ -68,6 +69,11 @@ public boolean isAOT() {
return aot;
}

@SuppressWarnings("WeakerAccess")
public boolean shouldInstrument(ClassLoader loader) {
return loader != null && !isExcludedClassLoader(loader.getClass().getName());
}

@SuppressWarnings("WeakerAccess")
public boolean shouldInstrument(String className) {
if (className != null) {
Expand Down Expand Up @@ -274,6 +280,54 @@ public synchronized boolean isExcluded(String className) {
return false;
}

synchronized boolean isExcludedClassLoader(String classLoaderName) {
for (Pattern pattern : excludedClassLoaders) {
if (pattern.matcher(classLoaderName).matches())
return true;
}
return false;
}

public synchronized void addExcludedClassLoader(String glob) {
excludedClassLoaders.add(classLoaderPattern(glob));
}

private static Pattern classLoaderPattern(String glob) {
StringBuilder out = new StringBuilder(glob.length() + 5).append('^');
int i = 0;
while (i < glob.length()) {
final char c = glob.charAt(i);
switch (c) {
case '.':
out.append("\\.");
break;
case '?':
out.append('.');
break;
case '*':
int j = i + 1;
if (j < glob.length()) {
char next = glob.charAt(j);
if (next == '*') {
out.append(".*");
++i;
break;
}
}
out.append("[^.]+");
break;
case '$':
out.append("\\$");
break;
default:
out.append(c);
}
++i;
}
out.append('$');
return Pattern.compile(out.toString());
}

private synchronized void setLogLevelMask() {
logLevelMask = (1 << LogLevel.WARNING.ordinal());
if (verbose || debug)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package co.paralleluniverse.fibers.instrument;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class ClassLoaderExclusionTest {
private QuasarInstrumentor instrumentor;

@Before
public void setup() {
instrumentor = new QuasarInstrumentor(false);
}

@Test
public void testAcceptWhenNoExclusions() {
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingEverything() {
instrumentor.addExcludedClassLoader("**");
assertTrue(instrumentor.isExcludedClassLoader("X"));
assertTrue(instrumentor.isExcludedClassLoader("a.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingDefaultPackageOnly() {
instrumentor.addExcludedClassLoader("*");
assertTrue(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingFirstLevel() {
instrumentor.addExcludedClassLoader("*.*");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertTrue(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingFirstLevelAndBelow() {
instrumentor.addExcludedClassLoader("*.**");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertTrue(instrumentor.isExcludedClassLoader("a.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingSecondLevel() {
instrumentor.addExcludedClassLoader("*.*.*");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingSecondLevelAndBelow() {
instrumentor.addExcludedClassLoader("*.*.**");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingThirdLevel() {
instrumentor.addExcludedClassLoader("*.*.*.*");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingThirdLevelAndBelow() {
instrumentor.addExcludedClassLoader("*.*.*.**");
assertFalse(instrumentor.isExcludedClassLoader("X"));
assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.d.X"));
}

@Test
public void testRejectingSpecificLoader() {
instrumentor.addExcludedClassLoader("**.LOADER");
assertTrue(instrumentor.isExcludedClassLoader("a.LOADER"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.LOADER"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.LOADER"));

assertFalse(instrumentor.isExcludedClassLoader("a.LOAD"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.LOAD"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.LOAD"));
}

@Test
public void testRejectingBySingleCharcterGlobs() {
instrumentor.addExcludedClassLoader("**.?X");
assertTrue(instrumentor.isExcludedClassLoader("a.1X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.1X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.1X"));

assertTrue(instrumentor.isExcludedClassLoader("a.2X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.2X"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.2X"));

assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}

@Test
public void testRejectingnamesWithDollars() {
instrumentor.addExcludedClassLoader("**.X$Y");
assertTrue(instrumentor.isExcludedClassLoader("a.X$Y"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.X$Y"));
assertTrue(instrumentor.isExcludedClassLoader("a.b.c.X$Y"));

assertFalse(instrumentor.isExcludedClassLoader("a.XY"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.XY"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.XY"));

assertFalse(instrumentor.isExcludedClassLoader("a.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.X"));
assertFalse(instrumentor.isExcludedClassLoader("a.b.c.X"));
}
}