Skip to content
Draft
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
1 change: 1 addition & 0 deletions javaagent-tooling/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import net.ltgt.gradle.errorprone.errorprone

plugins {
id("otel.java-conventions")
id("otel.nullaway-conventions")
id("otel.publish-conventions")
id("otel.jmh-conventions")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("otel.java-conventions")
id("otel.nullaway-conventions")
id("otel.publish-conventions")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static io.opentelemetry.javaagent.tooling.SafeServiceLoader.loadOrdered;
import static io.opentelemetry.javaagent.tooling.Utils.getResourceName;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.SEVERE;
import static net.bytebuddy.matcher.ElementMatchers.any;
Expand Down Expand Up @@ -153,7 +154,9 @@ private static void installBytebuddyAgent(
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk =
installOpenTelemetrySdk(extensionClassLoader);

ConfigProperties sdkConfig = AutoConfigureUtil.getConfig(autoConfiguredSdk);
ConfigProperties sdkConfig =
requireNonNull(
AutoConfigureUtil.getConfig(autoConfiguredSdk), "SDK config must not be null");

setBootstrapPackages(sdkConfig, extensionClassLoader);
ConfiguredResourceAttributesHolder.initialize(
Expand Down Expand Up @@ -287,7 +290,7 @@ private static AgentBuilder configureIgnoredTypes(
IgnoredTypesBuilderImpl builder = new IgnoredTypesBuilderImpl();
for (IgnoredTypesConfigurer configurer :
loadOrdered(IgnoredTypesConfigurer.class, extensionClassLoader)) {
configurer.configure(builder, config != null ? config : EmptyConfigProperties.INSTANCE);
configurer.configure(builder, config);
}

Trie<Boolean> ignoredTasksTrie = builder.buildIgnoredTasksTrie();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.ServiceLoader;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
Expand All @@ -34,7 +35,7 @@ public class AgentStarterImpl implements AgentStarter {
private final Instrumentation instrumentation;
private final File javaagentFile;
private final boolean isSecurityManagerSupportEnabled;
private ClassLoader extensionClassLoader;
@Nullable private ClassLoader extensionClassLoader;

public AgentStarterImpl(
Instrumentation instrumentation,
Expand Down Expand Up @@ -142,6 +143,7 @@ private static void logUnrecognizedLoggerImplWarning(String loggerImplementation
+ "'. The agent will use the no-op implementation.");
}

@Nullable
@Override
public ClassLoader getExtensionClassLoader() {
return extensionClassLoader;
Expand All @@ -156,6 +158,7 @@ private static class LaunchHelperClassFileTransformer implements ClassFileTransf
boolean hookInserted = false;
boolean transformed = false;

@Nullable
@Override
public byte[] transform(
ClassLoader loader,
Expand Down Expand Up @@ -202,6 +205,7 @@ public void visitCode() {
private static class InetAddressClassFileTransformer implements ClassFileTransformer {
boolean hookInserted = false;

@Nullable
@Override
public byte[] transform(
ClassLoader loader,
Expand Down Expand Up @@ -257,6 +261,7 @@ public void visitMethodInsn(

private static class AgentBuilderDefaultClassFileTransformer implements ClassFileTransformer {

@Nullable
@Override
public byte[] transform(
ClassLoader loader,
Expand Down Expand Up @@ -291,6 +296,7 @@ public FieldVisitor visitField(

private static class CallbackRegistrationClassFileTransformer implements ClassFileTransformer {

@Nullable
@Override
public byte[] transform(
ClassLoader loader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassReader;

public class DefineClassHandler implements Handler {
Expand All @@ -27,7 +28,7 @@ public DefineClassContext beforeDefineClass(
if (classBytes == null
|| (classBytes.length == 40
&& new String(classBytes, ISO_8859_1).startsWith("J9ROMCLASSCOOKIE"))) {
return null;
return DefineClassContextImpl.NOP;
}

Set<String> superNames = new HashSet<>();
Expand Down Expand Up @@ -85,9 +86,7 @@ private static void addSuperNames(Set<String> superNames, Class<?> clazz) {

@Override
public void afterDefineClass(DefineClassContext context) {
if (context != null) {
context.exit();
}
context.exit();
}

/**
Expand All @@ -107,17 +106,24 @@ public static Set<String> getSuperTypes() {
}

private static class DefineClassContextImpl implements DefineClassContext {
private static final DefineClassContextImpl NOP = new DefineClassContextImpl();

private final DefineClassContextImpl previous;
String failedClassDotName;
Set<String> superDotNames;
// NOP is returned from beforeDefineClass without calling enter() (no frame pushed),
// so exit() must not mutate the ThreadLocal — otherwise a nested J9 defineClass would
// clobber an outer frame that is still active.
private static final DefineClassContextImpl NOP =
new DefineClassContextImpl() {
@Override
public void exit() {}
};

@Nullable private final DefineClassContextImpl previous;
@Nullable String failedClassDotName;
@Nullable Set<String> superDotNames;

private DefineClassContextImpl() {
previous = null;
}

private DefineClassContextImpl(DefineClassContextImpl previous) {
private DefineClassContextImpl(@Nullable DefineClassContextImpl previous) {
this.previous = previous;
}

Expand All @@ -130,7 +136,7 @@ static DefineClassContextImpl enter() {

@Override
public void exit() {
defineClassContext.set(previous);
defineClassContext.set(previous != null ? previous : NOP);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[General] Setting the ThreadLocal to NOP here can silently corrupt outer state.

beforeDefineClass now returns DefineClassContextImpl.NOP for the J9ROMCLASSCOOKIE path without calling enter() (so the ThreadLocal is not pushed). But afterDefineClass has been simplified to always call context.exit(), so NOP.exit() runs — and since NOP.previous is null, this line writes NOP into defineClassContext.

In a nested scenario (outer beforeDefineClass triggers a nested class definition via Class.forName during super-type resolution, and the nested class hits the J9 path), the inner NOP.exit() overwrites the outer context with NOP. Between that point and the outer afterDefineClass, isFailedClass() / getSuperTypes() will read NOP instead of the outer context.

Consider making exit() a no-op on NOP (e.g. early-return when this == NOP), or restore the previous behavior of skipping exit() when beforeDefineClass returned the no-op sentinel.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private static void includeEmbeddedExtensionsIfFound(List<URL> extensions, File
}
}

private static File ensureTempDirectoryExists(File tempDirectory) throws IOException {
private static File ensureTempDirectoryExists(@Nullable File tempDirectory) throws IOException {
if (tempDirectory == null) {
tempDirectory = Files.createTempDirectory("otel-extensions").toFile();
tempDirectory.deleteOnExit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.net.URL;
import java.net.URLConnection;
import java.security.Permission;
import javax.annotation.Nullable;

public class InputStreamUrlConnection extends URLConnection {
private final InputStream inputStream;
Expand All @@ -30,6 +31,7 @@ public InputStream getInputStream() {
return inputStream;
}

@Nullable
@Override
public Permission getPermission() {
// No permissions needed because all classes are in memory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.javaagent.tooling;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.config.bridge.ConfigPropertiesBackedConfigProvider;
Expand All @@ -16,6 +17,7 @@
import io.opentelemetry.sdk.autoconfigure.SdkAutoconfigureAccess;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.common.CompletableResultCode;

public final class OpenTelemetryInstaller {
Expand All @@ -36,15 +38,17 @@ public static AutoConfiguredOpenTelemetrySdk installOpenTelemetrySdk(
.build();
OpenTelemetrySdk sdk = autoConfiguredSdk.getOpenTelemetrySdk();
ConfigProperties configProperties = AutoConfigureUtil.getConfig(autoConfiguredSdk);
boolean declarativeConfigUsed = configProperties == null;

if (!declarativeConfigUsed) {
if (configProperties != null) {
// Provide a fake declarative configuration based on config properties
// so that declarative configuration API can be used everywhere
sdk =
new ExtendedOpenTelemetrySdkWrapper(
sdk, ConfigPropertiesBackedConfigProvider.create(configProperties));
AgentDistributionConfig.set(AgentDistributionConfig.fromConfigProperties(configProperties));
} else {
// Declarative config path: no ConfigProperties available, use empty defaults
configProperties = DefaultConfigProperties.createFromMap(emptyMap());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[General] Behavior change worth confirming: previously, in declarative-config mode, the returned AutoConfiguredOpenTelemetrySdk.getConfig() was null. Downstream callers (AgentInstaller.setBootstrapPackages, configureIgnoredTypes, BeforeAgentListener#beforeAgent, AgentExtension#extend) now receive an empty-map-backed ConfigProperties instead.

Any extension that disambiguated declarative mode via config == null will silently switch to the empty-config branch. Is this the intended contract going forward? If so, it's probably worth noting in the changelog / extension docs.

}

setForceFlush(sdk);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.security.Permission;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.ClassRemapper;
Expand Down Expand Up @@ -50,7 +51,7 @@ public class RemappingUrlConnection extends URLConnection {
private final JarFile delegateJarFile;
private final JarEntry entry;

private byte[] cacheClassBytes;
@Nullable private byte[] cacheClassBytes;

public RemappingUrlConnection(URL url, JarFile delegateJarFile, JarEntry entry) {
super(url);
Expand Down Expand Up @@ -89,6 +90,7 @@ private static byte[] remapClassBytes(InputStream in) throws IOException {
return cw.toByteArray();
}

@Nullable
@Override
public Permission getPermission() {
// No permissions needed because all classes are in memory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.logging.Logger;
import javax.annotation.Nullable;

public final class SafeServiceLoader {

Expand All @@ -30,13 +32,14 @@ public final class SafeServiceLoader {
*/
// Because we want to catch exception per iteration
@SuppressWarnings("ForEachIterable")
public static <T> List<T> load(Class<T> serviceClass, ClassLoader classLoader) {
public static <T> List<T> load(Class<T> serviceClass, @Nullable ClassLoader classLoader) {
List<T> result = new ArrayList<>();
ServiceLoader<T> services = ServiceLoader.load(serviceClass, classLoader);
for (Iterator<T> iterator = new SafeIterator<>(services.iterator()); iterator.hasNext(); ) {
T service = iterator.next();
if (service != null) {
result.add(service);
try {
result.add(iterator.next());
} catch (NoSuchElementException ignored) {
// UnsupportedClassVersionError was thrown and handled by SafeIterator
}
}
return result;
Expand All @@ -47,7 +50,7 @@ public static <T> List<T> load(Class<T> serviceClass, ClassLoader classLoader) {
* comparing their {@link Ordered#order()}.
*/
public static <T extends Ordered> List<T> loadOrdered(
Class<T> serviceClass, ClassLoader classLoader) {
Class<T> serviceClass, @Nullable ClassLoader classLoader) {
List<T> result = load(serviceClass, classLoader);
result.sort(Comparator.comparing(Ordered::order));
return result;
Expand Down Expand Up @@ -89,7 +92,7 @@ public T next() {
return delegate.next();
} catch (UnsupportedClassVersionError unsupportedClassVersionError) {
handleUnsupportedClassVersionError(unsupportedClassVersionError);
return null;
throw new NoSuchElementException(unsupportedClassVersionError.getMessage());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.opentelemetry.javaagent.bootstrap.AgentClassLoader.BootstrapClassLoaderProxy;
import io.opentelemetry.javaagent.bootstrap.AgentInitializer;
import java.security.PrivilegedAction;
import javax.annotation.Nullable;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;

Expand All @@ -24,6 +25,7 @@ public static ClassLoader getAgentClassLoader() {
return AgentInstaller.class.getClassLoader();
}

@Nullable
public static ClassLoader getExtensionsClassLoader() {
return AgentInitializer.getExtensionsClassLoader();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

final class ConfigurationFile {

private static Map<String, String> configFileContents;
@Nullable private static Map<String, String> configFileContents;

// this class is used early, and must not use logging in most of its methods
// in case any file loading/parsing error occurs, we save the error message and log it later, when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.DistributionPropertyModel;
import java.io.IOException;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* Allows access to the Javaagent distribution node, which cannot be accessed using the {@link
Expand Down Expand Up @@ -60,7 +61,7 @@ public void customize(DeclarativeConfigurationCustomizer customizer) {
});
}

private static AgentDistributionConfig parseConfig(DistributionModel distribution) {
private static AgentDistributionConfig parseConfig(@Nullable DistributionModel distribution) {
if (distribution != null) {
DistributionPropertyModel javaagent = distribution.getAdditionalProperties().get("javaagent");
if (javaagent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasSuperType;
import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getFieldAccessorInterfaceName;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.FINEST;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.named;
Expand All @@ -27,6 +28,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.AsmVisitorWrapper;
Expand Down Expand Up @@ -78,8 +80,10 @@ public FieldBackedImplementationInstaller(
Class<?> instrumenterClass, VirtualFieldMappings virtualFieldMappings) {
this.instrumenterClass = instrumenterClass;
this.virtualFieldMappings = virtualFieldMappings;
// This class is used only when running with javaagent, thus this calls is safe
this.instrumentation = InstrumentationHolder.getInstrumentation();
// This class is used only when running with javaagent, thus this call is safe
this.instrumentation =
requireNonNull(
InstrumentationHolder.getInstrumentation(), "Instrumentation must not be null");

ByteBuddy byteBuddy = new ByteBuddy();
fieldAccessorInterfaces =
Expand Down Expand Up @@ -146,6 +150,7 @@ private AgentBuilder.Transformer bootstrapHelperInjector(
final HelperInjector injector =
HelperInjector.forDynamicTypes(getClass().getSimpleName(), helpers, instrumentation);

@Nullable
@Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
Expand Down
Loading
Loading