Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0fd0e09
Make internal classloader instrumentation indy compatible
JonasKunz Sep 13, 2024
d28efde
spotless
JonasKunz Sep 13, 2024
18b9029
Added missing comment
JonasKunz Sep 13, 2024
87f7d42
Checkstyle
JonasKunz Sep 13, 2024
116daaa
Fix test relying on injection of Constants class
JonasKunz Sep 13, 2024
106904e
Fix javadoc
JonasKunz Sep 13, 2024
f351043
Review fixes
JonasKunz Sep 13, 2024
e8a5ef2
Update instrumentation/internal/internal-class-loader/javaagent/src/m…
JonasKunz Sep 19, 2024
67b1d40
Remove redundant excludes
JonasKunz Oct 2, 2024
57d73cd
Replace copied bootstrap packages with access to BootstrapPackagesHolder
JonasKunz Oct 2, 2024
b46acad
Merge remote-tracking branch 'otel/HEAD' into indy-classloader-instru…
JonasKunz Oct 2, 2024
e54d881
Improved error output for bootstrapping recursion
JonasKunz Oct 18, 2024
393e3ef
Test: Disable eager loading of advices and see if it fails anywhere
JonasKunz Oct 18, 2024
cd7c0f9
Test 2: Remove DefineClassInstrumentation and see if anything fails
JonasKunz Oct 18, 2024
7fc8277
Revert "Test 2: Remove DefineClassInstrumentation and see if anything…
JonasKunz Oct 18, 2024
33b6130
Revert "Test: Disable eager loading of advices and see if it fails an…
JonasKunz Oct 18, 2024
2456716
Removed eager advice loading
JonasKunz Oct 22, 2024
dc26c83
Added support for nested invokedynamic bootstrapping using MutableCal…
JonasKunz Oct 22, 2024
0154cd3
Added Unit test for DefineClassInstrumentation
JonasKunz Oct 21, 2024
5f3f166
Fix problems with lambdas on recursive path
JonasKunz Oct 22, 2024
b01f387
Revert "Added Unit test for DefineClassInstrumentation"
JonasKunz Oct 22, 2024
712c70d
spotless
JonasKunz Oct 22, 2024
85beb7a
fix comments
JonasKunz Oct 22, 2024
3e03153
Remove invalid package visibility comment
JonasKunz Oct 22, 2024
05bc99f
Attempt to fix linkage errors
JonasKunz Oct 22, 2024
492c968
Merge remote-tracking branch 'otel/HEAD' into indy-classloader-instru…
JonasKunz Oct 23, 2024
6662850
Added pre-cautionary catch of linkage errors
JonasKunz Oct 23, 2024
1173824
Revert changes not needed anymore after self-review
JonasKunz Oct 23, 2024
43a4ae0
Revert unnecessary changes to AgentClassLoader
JonasKunz Oct 24, 2024
03932e4
Typo fix
JonasKunz Oct 24, 2024
add664a
Update javaagent-tooling/src/main/java/io/opentelemetry/javaagent/too…
trask Oct 24, 2024
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 @@ -47,10 +47,7 @@ public class BootDelegationInstrumentation implements TypeInstrumentation {
public ElementMatcher<TypeDescription> typeMatcher() {
// just an optimization to exclude common class loaders that are known to delegate to the
// bootstrap loader (or happen to _be_ the bootstrap loader)
return not(namedOneOf(
"java.lang.ClassLoader",
"com.ibm.oti.vm.BootstrapClassLoader",
"io.opentelemetry.javaagent.bootstrap.AgentClassLoader"))
return not(namedOneOf("java.lang.ClassLoader", "com.ibm.oti.vm.BootstrapClassLoader"))
.and(extendsClass(named("java.lang.ClassLoader")));
}

Expand Down Expand Up @@ -136,13 +133,14 @@ public static Class<?> onEnter(@Advice.Argument(0) String name) {
return null;
}

@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void onExit(
@Advice.Return(readOnly = false) Class<?> result,
@Advice.Enter Class<?> resultFromBootstrapLoader) {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
@Advice.AssignReturned.ToReturned
public static Class<?> onExit(
@Advice.Return Class<?> result, @Advice.Enter Class<?> resultFromBootstrapLoader) {
if (resultFromBootstrapLoader != null) {
result = resultFromBootstrapLoader;
return resultFromBootstrapLoader;
}
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class ClassLoaderInstrumentationModule extends InstrumentationModule {
public class ClassLoaderInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public ClassLoaderInstrumentationModule() {
super("internal-class-loader");
}
Expand All @@ -25,13 +27,10 @@ public boolean defaultEnabled(ConfigProperties config) {
return true;
}

@Override
public boolean isIndyModule() {
return false;
}

@Override
public boolean isHelperClass(String className) {
// TODO: this can be removed when we drop inlined-advice support
// The advices can directly access this class in the AgentClassLoader with invokedynamic Advice
return className.equals("io.opentelemetry.javaagent.tooling.Constants");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ public static DefineClassContext onEnter(
classLoader, className, classBytes, offset, length);
}

// TODO: the ToReturned does nothing except for signaling the AdviceTransformer that it must
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR but I think we need to add a method to the experimental interface that lets instrumentation module declare that it is compatible with non-inline advice so we could disable the advice rewriting for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of it but felt like this instrumentation was a special case where this was needed and that would only be temporary until we remove support for non-indy instrumentations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I was thinking that the main use for it would be to track the indy conversion progress and ensuring that modules that were made indy compatible once aren't broken by later changes. I'm pretty sure there was at least one more module that use the same trick to disable the advice rewriting.

// not touch this advice
// this is done because we do not want the return values to be wrapped in array types
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(@Advice.Enter DefineClassContext context) {
@Advice.AssignReturned.ToReturned
public static Class<?> onExit(
@Advice.Enter DefineClassContext context, @Advice.Return Class<?> returned) {
DefineClassHelper.afterDefineClass(context);
return returned;
}
}

Expand All @@ -68,9 +74,15 @@ public static DefineClassContext onEnter(
return DefineClassHelper.beforeDefineClass(classLoader, className, classBytes);
}

// TODO: the ToReturned does nothing except for signaling the AdviceTransformer that it must
// not touch this advice
// this is done because we do not want the return values to be wrapped in array types
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(@Advice.Enter DefineClassContext context) {
@Advice.AssignReturned.ToReturned
public static Class<?> onExit(
@Advice.Enter DefineClassContext context, @Advice.Return Class<?> returned) {
DefineClassHelper.afterDefineClass(context);
return returned;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ public static Class<?> onEnter(
}

@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void onExit(
@Advice.Return(readOnly = false) Class<?> result, @Advice.Enter Class<?> loadedClass) {
@Advice.AssignReturned.ToReturned
public static Class<?> onExit(
@Advice.Return Class<?> result, @Advice.Enter Class<?> loadedClass) {
if (loadedClass != null) {
result = loadedClass;
return loadedClass;
}
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.extendsClass;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

Expand All @@ -32,7 +34,12 @@ public class ResourceInjectionInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return extendsClass(named("java.lang.ClassLoader"));
return extendsClass(named("java.lang.ClassLoader"))
.and(
not(
namedOneOf(
"io.opentelemetry.javaagent.bootstrap.AgentClassLoader",
"io.opentelemetry.javaagent.tooling.instrumentation.indy.InstrumentationModuleClassLoader")));
}

@Override
Expand Down Expand Up @@ -61,37 +68,37 @@ public void transform(TypeTransformer transformer) {
public static class GetResourceAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.AssignReturned.ToReturned
public static URL onExit(
@Advice.This ClassLoader classLoader,
@Advice.Argument(0) String name,
@Advice.Return(readOnly = false) URL resource) {
if (resource != null) {
return;
}

URL helper = HelperResources.loadOne(classLoader, name);
if (helper != null) {
resource = helper;
@Advice.Return URL resource) {
if (resource == null) {
URL helper = HelperResources.loadOne(classLoader, name);
if (helper != null) {
return helper;
}
}
return resource;
}
}

@SuppressWarnings("unused")
public static class GetResourcesAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.AssignReturned.ToReturned
public static Enumeration<URL> onExit(
@Advice.This ClassLoader classLoader,
@Advice.Argument(0) String name,
@Advice.Return(readOnly = false) Enumeration<URL> resources) {
@Advice.Return Enumeration<URL> resources) {
List<URL> helpers = HelperResources.loadAll(classLoader, name);
if (helpers.isEmpty()) {
return;
return resources;
}

if (!resources.hasMoreElements()) {
resources = Collections.enumeration(helpers);
return;
return Collections.enumeration(helpers);
}

List<URL> result = Collections.list(resources);
Expand All @@ -108,31 +115,30 @@ public static void onExit(
result.add(helperUrl);
}
}

resources = Collections.enumeration(result);
return Collections.enumeration(result);
}
}

@SuppressWarnings("unused")
public static class GetResourceAsStreamAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.AssignReturned.ToReturned
public static InputStream onExit(
@Advice.This ClassLoader classLoader,
@Advice.Argument(0) String name,
@Advice.Return(readOnly = false) InputStream inputStream) {
if (inputStream != null) {
return;
}

URL helper = HelperResources.loadOne(classLoader, name);
if (helper != null) {
try {
inputStream = helper.openStream();
} catch (IOException ignored) {
// ClassLoader.getResourceAsStream also ignores io exceptions from opening the stream
@Advice.Return InputStream inputStream) {
if (inputStream == null) {
URL helper = HelperResources.loadOne(classLoader, name);
if (helper != null) {
try {
return helper.openStream();
} catch (IOException ignored) {
// ClassLoader.getResourceAsStream also ignores io exceptions from opening the stream
}
}
}
return inputStream;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ private static int getJavaVersion() {
return Integer.parseInt(javaSpecVersion);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// We explicitly override loadClass from ClassLoader to ensure
// that loadClass is properly excluded from our internal ClassLoader Instrumentations
// (e.g. LoadInjectedClassInstrumentation, BooDelegationInstrumentation)
// Otherwise this will cause recursion in invokedynamic linkage
return loadClass(name, false);
}

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// ContextStorageOverride is meant for library instrumentation we don't want it to apply to our
Expand All @@ -158,7 +167,14 @@ public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundExce
}
// search from parent and urls added to this loader
if (clazz == null) {
clazz = super.loadClass(name, false);
try {
clazz = Class.forName(name, false, this.getParent());
} catch (ClassNotFoundException e) {
// ignore
}
}
if (clazz == null) {
clazz = super.findClass(name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After changing the InstrumentationModuleClassLoader.defineClassWithPackage this probably isn't necessary any more. Either revert or add a comment that this is here to avoid calling super.loadClass.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also removed the loadClass override in AgentClassLoader. Remove it in InstrumentationModuleClassLoader is not possible, because then the LoadClassAdvice itself becomes recursive (not the bootstrapping this time):

[otel.javaagent 2024-10-24 12:42:55:462 +0200] [main] DEBUG io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyBootstrap - Fixing nested instrumentation invokedynamic instruction bootstrapping for instrumented class java.lang.ClassLoader and advice io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter, the instrumentaiton should be active now
[otel.javaagent 2024-10-24 12:42:55:463 +0200] [otel-javaagent-transform-safe-logger] DEBUG io.opentelemetry.javaagent.tooling.AgentInstaller$TransformLoggingListener - Failed to handle io.opentelemetry.javaagent.ClassLoaderData$$29539e36 for transformation on class loader io.opentelemetry.javaagent.tooling.util.ClassLoaderMap$1@29539e36
java.lang.StackOverflowError
	at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
	...
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	at io.opentelemetry.javaagent.tooling.ignore.IgnoredClassLoadersMatcher.loadsExpectedClass(IgnoredClassLoadersMatcher.java:78)
	at io.opentelemetry.javaagent.tooling.ignore.IgnoredClassLoadersMatcher.delegatesToBootstrap(IgnoredClassLoadersMatcher.java:69)

If you prefer to, we could also use CallDepth in the LoadClassAdvice instead I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine as it is for now.

}
if (resolve) {
resolveClass(clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public static CallSite bootstrap(
return callSite;
}

// package visibility for testing
static MethodHandle generateNoopMethodHandle(MethodType methodType) {
public static MethodHandle generateNoopMethodHandle(MethodType methodType) {
Class<?> returnType = methodType.returnType();
MethodHandle noopNoArg;
if (returnType == void.class) {
Expand Down
Loading
Loading