diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index d72c325bb044..5a3ad2e41188 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -247,6 +247,7 @@ jobs: - 17 - 21 - 25 # renovate: datasource=java-version + - 25-deny-unsafe vm: - hotspot - openj9 @@ -260,6 +261,8 @@ jobs: - true exclude: - vm: ${{ inputs.skip-openj9-tests && 'openj9' || '' }} + - test-java-version: 25-deny-unsafe + vm: openj9 fail-fast: false steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -273,7 +276,7 @@ jobs: with: # using zulu because new releases get published quickly distribution: ${{ matrix.vm == 'hotspot' && 'zulu' || 'adopt-openj9'}} - java-version: ${{ matrix.test-java-version }} + java-version: ${{ matrix.test-java-version != '25-deny-unsafe' && matrix.test-java-version || '25' }} - name: Set up JDK for running Gradle uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 @@ -329,6 +332,7 @@ jobs: -PtestJavaVersion=${{ matrix.test-java-version }} -PtestJavaVM=${{ matrix.vm }} -PtestIndy=${{ matrix.test-indy }} + -PdenyUnsafe=${{ matrix.test-java-version == '25-deny-unsafe' && 'true' || 'false' }} -Porg.gradle.java.installations.paths=${{ steps.setup-test-java.outputs.path }} -Porg.gradle.java.installations.auto-download=false ${{ inputs.no-build-cache && ' --no-build-cache' || '' }} diff --git a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-testing.gradle.kts b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-testing.gradle.kts index bfeb0ccbd4f7..d5df64fa7d71 100644 --- a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-testing.gradle.kts +++ b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-testing.gradle.kts @@ -6,6 +6,9 @@ plugins { id("io.opentelemetry.instrumentation.javaagent-shadowing") } +val denyUnsafe = gradle.startParameter.projectProperties["denyUnsafe"] == "true" +extra["denyUnsafe"] = denyUnsafe + dependencies { /* Dependencies added to this configuration will be found by the muzzle gradle plugin during code @@ -72,30 +75,40 @@ class JavaagentTestArgumentsProvider( @PathSensitive(PathSensitivity.RELATIVE) val shadowJar: File, ) : CommandLineArgumentProvider { - override fun asArguments(): Iterable = listOf( - "-Dotel.javaagent.debug=true", - "-javaagent:${agentShadowJar.absolutePath}", - // make the path to the javaagent available to tests - "-Dotel.javaagent.testing.javaagent-jar-path=${agentShadowJar.absolutePath}", - "-Dotel.javaagent.experimental.initializer.jar=${shadowJar.absolutePath}", - "-Dotel.javaagent.testing.additional-library-ignores.enabled=false", - "-Dotel.javaagent.testing.fail-on-context-leak=${findProperty("failOnContextLeak") != false}", - // prevent sporadic gradle deadlocks, see SafeLogger for more details - "-Dotel.javaagent.testing.transform-safe-logging.enabled=true", - // Reduce noise in assertion messages since we don't need to verify this in most tests. We check - // in smoke tests instead. - "-Dotel.javaagent.add-thread-details=false", - "-Dotel.javaagent.experimental.indy=${findProperty("testIndy") == "true"}", - // suppress repeated logging of "No metric data to export - skipping export." - // since PeriodicMetricReader is configured with a short interval - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.opentelemetry.sdk.metrics.export.PeriodicMetricReader=INFO", - // suppress a couple of verbose ClassNotFoundException stack traces logged at debug level - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.internal.ServerImplBuilder=INFO", - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.internal.ManagedChannelImplBuilder=INFO", - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.perfmark.PerfMark=INFO", - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.Context=INFO", - "-Dotel.java.experimental.span-attributes.copy-from-baggage.include=test-baggage-key-1,test-baggage-key-2" - ) + override fun asArguments(): Iterable { + val list = mutableListOf( + "-Dotel.javaagent.debug=true", + "-javaagent:${agentShadowJar.absolutePath}", + // make the path to the javaagent available to tests + "-Dotel.javaagent.testing.javaagent-jar-path=${agentShadowJar.absolutePath}", + "-Dotel.javaagent.experimental.initializer.jar=${shadowJar.absolutePath}", + "-Dotel.javaagent.testing.additional-library-ignores.enabled=false", + "-Dotel.javaagent.testing.fail-on-context-leak=${findProperty("failOnContextLeak") != false}", + // prevent sporadic gradle deadlocks, see SafeLogger for more details + "-Dotel.javaagent.testing.transform-safe-logging.enabled=true", + // Reduce noise in assertion messages since we don't need to verify this in most tests. We check + // in smoke tests instead. + "-Dotel.javaagent.add-thread-details=false", + "-Dotel.javaagent.experimental.indy=${findProperty("testIndy") == "true"}", + // suppress repeated logging of "No metric data to export - skipping export." + // since PeriodicMetricReader is configured with a short interval + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.opentelemetry.sdk.metrics.export.PeriodicMetricReader=INFO", + // suppress a couple of verbose ClassNotFoundException stack traces logged at debug level + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.internal.ServerImplBuilder=INFO", + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.internal.ManagedChannelImplBuilder=INFO", + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.perfmark.PerfMark=INFO", + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.log.io.grpc.Context=INFO", + "-Dotel.java.experimental.span-attributes.copy-from-baggage.include=test-baggage-key-1,test-baggage-key-2" + ) + if (denyUnsafe) { + list += listOf( + "-Dsun.misc.unsafe.memory.access=deny", + "-Dotel.instrumentation.deny-unsafe.enabled=true", + "-Dio.netty.noUnsafe=true" + ) + } + return list + } } // need to run this after evaluate because testSets plugin adds new test tasks diff --git a/instrumentation/akka/akka-actor-2.3/javaagent/build.gradle.kts b/instrumentation/akka/akka-actor-2.3/javaagent/build.gradle.kts index 746d88d9a076..fe61d2c95e32 100644 --- a/instrumentation/akka/akka-actor-2.3/javaagent/build.gradle.kts +++ b/instrumentation/akka/akka-actor-2.3/javaagent/build.gradle.kts @@ -41,3 +41,9 @@ if (findProperty("testLatestDeps") as Boolean) { } } } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/akka/akka-actor-fork-join-2.5/javaagent/build.gradle.kts b/instrumentation/akka/akka-actor-fork-join-2.5/javaagent/build.gradle.kts index 77275f533569..85e1196b8454 100644 --- a/instrumentation/akka/akka-actor-fork-join-2.5/javaagent/build.gradle.kts +++ b/instrumentation/akka/akka-actor-fork-join-2.5/javaagent/build.gradle.kts @@ -32,3 +32,9 @@ dependencies { testImplementation(project(":instrumentation:executors:testing")) } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts b/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts index b29d53d34cd7..1c42b308dbd4 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts +++ b/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts @@ -71,6 +71,12 @@ tasks { check { dependsOn(testing.suites) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } if (findProperty("testLatestDeps") as Boolean) { diff --git a/instrumentation/armeria/armeria-1.3/javaagent/build.gradle.kts b/instrumentation/armeria/armeria-1.3/javaagent/build.gradle.kts index 6ead7cc5c1e3..97e6060170d5 100644 --- a/instrumentation/armeria/armeria-1.3/javaagent/build.gradle.kts +++ b/instrumentation/armeria/armeria-1.3/javaagent/build.gradle.kts @@ -29,4 +29,10 @@ tasks { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/armeria/armeria-grpc-1.14/javaagent/build.gradle.kts b/instrumentation/armeria/armeria-grpc-1.14/javaagent/build.gradle.kts index 1b798164b322..118a3a579133 100644 --- a/instrumentation/armeria/armeria-grpc-1.14/javaagent/build.gradle.kts +++ b/instrumentation/armeria/armeria-grpc-1.14/javaagent/build.gradle.kts @@ -62,3 +62,9 @@ afterEvaluate { tasks.test { systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts index d6b9dec89bc7..c2e3939596b5 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-1.11/javaagent/build.gradle.kts @@ -167,6 +167,13 @@ tasks { check { dependsOn(testStableSemconv) } + + if (findProperty("denyUnsafe") as Boolean) { + // org.elasticmq:elasticmq-rest-sqs_2.13 uses unsafe. Future versions are likely to fix this. + withType().configureEach { + enabled = false + } + } } if (!(findProperty("testLatestDeps") as Boolean)) { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts index a8ee21799985..b1d6d70f5747 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts +++ b/instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts @@ -237,4 +237,12 @@ tasks { duplicatesStrategy = DuplicatesStrategy.INCLUDE } } + + if (findProperty("denyUnsafe") as Boolean) { + // Aws2SqsTracingTest uses org.elasticmq:elasticmq-rest-sqs_2.13 that uses unsafe. Future + // versions are likely to fix this. + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/camel-2.20/javaagent/build.gradle.kts b/instrumentation/camel-2.20/javaagent/build.gradle.kts index 27fc5cb09e5b..b1166690fa58 100644 --- a/instrumentation/camel-2.20/javaagent/build.gradle.kts +++ b/instrumentation/camel-2.20/javaagent/build.gradle.kts @@ -104,6 +104,12 @@ tasks { check { dependsOn(testStableSemconv, testExperimental) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } configurations.testRuntimeClasspath { diff --git a/instrumentation/cassandra/cassandra-3.0/javaagent/build.gradle.kts b/instrumentation/cassandra/cassandra-3.0/javaagent/build.gradle.kts index e09e209b9baf..96c54409ab8c 100644 --- a/instrumentation/cassandra/cassandra-3.0/javaagent/build.gradle.kts +++ b/instrumentation/cassandra/cassandra-3.0/javaagent/build.gradle.kts @@ -57,4 +57,10 @@ tasks { check { dependsOn(testStableSemconv) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-2.0/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-2.0/javaagent/build.gradle.kts index 5dde6084b951..7280e933c961 100644 --- a/instrumentation/couchbase/couchbase-2.0/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-2.0/javaagent/build.gradle.kts @@ -57,4 +57,10 @@ tasks { check { dependsOn(testStableSemconv) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-2.6/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-2.6/javaagent/build.gradle.kts index a847ea55e641..2350248888c6 100644 --- a/instrumentation/couchbase/couchbase-2.6/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-2.6/javaagent/build.gradle.kts @@ -66,4 +66,10 @@ tasks { check { dependsOn(testStableSemconv, testExperimental) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-3.1.6/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-3.1.6/javaagent/build.gradle.kts index 005b3ea6779e..194c3f4ca3ad 100644 --- a/instrumentation/couchbase/couchbase-3.1.6/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-3.1.6/javaagent/build.gradle.kts @@ -48,4 +48,10 @@ tasks { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-3.1/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-3.1/javaagent/build.gradle.kts index 636a193dcab4..d4f531c093c4 100644 --- a/instrumentation/couchbase/couchbase-3.1/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-3.1/javaagent/build.gradle.kts @@ -49,4 +49,10 @@ tasks { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-3.2/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-3.2/javaagent/build.gradle.kts index ac39bf69ae74..a8d8f0f5a449 100644 --- a/instrumentation/couchbase/couchbase-3.2/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-3.2/javaagent/build.gradle.kts @@ -50,4 +50,10 @@ tasks { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/couchbase/couchbase-3.4/javaagent/build.gradle.kts b/instrumentation/couchbase/couchbase-3.4/javaagent/build.gradle.kts index 1f0e3a869cb3..0df8137c0ff6 100644 --- a/instrumentation/couchbase/couchbase-3.4/javaagent/build.gradle.kts +++ b/instrumentation/couchbase/couchbase-3.4/javaagent/build.gradle.kts @@ -48,4 +48,10 @@ tasks { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts b/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts index c955214ed6be..086a63aacb5d 100644 --- a/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts +++ b/instrumentation/dropwizard/dropwizard-testing/build.gradle.kts @@ -31,3 +31,9 @@ configurations.testRuntimeClasspath { tasks.withType().configureEach { jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/elasticsearch/elasticsearch-transport-5.3/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-transport-5.3/javaagent/build.gradle.kts index efec8e0fa74b..89b51e4510a3 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-5.3/javaagent/build.gradle.kts +++ b/instrumentation/elasticsearch/elasticsearch-transport-5.3/javaagent/build.gradle.kts @@ -96,4 +96,10 @@ tasks { check { dependsOn(testStableSemconv, testExperimental) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/elasticsearch/elasticsearch-transport-6.0/javaagent/build.gradle.kts b/instrumentation/elasticsearch/elasticsearch-transport-6.0/javaagent/build.gradle.kts index aa1aea8fd49f..f0b6edba89f5 100644 --- a/instrumentation/elasticsearch/elasticsearch-transport-6.0/javaagent/build.gradle.kts +++ b/instrumentation/elasticsearch/elasticsearch-transport-6.0/javaagent/build.gradle.kts @@ -126,4 +126,10 @@ tasks { check { dependsOn(testing.suites, stableSemconvSuites, experimentalSuites) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts index 6e0b0a8e5d0d..506deb130b65 100644 --- a/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts +++ b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts @@ -48,4 +48,10 @@ tasks { systemProperty("metadataConfig", "otel.instrumentation.http.client.emit-experimental-telemetry=true,otel.instrumentation.http.server.emit-experimental-telemetry=true") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/finatra-2.9/javaagent/build.gradle.kts b/instrumentation/finatra-2.9/javaagent/build.gradle.kts index c8671e6c4c13..103c2943ddc3 100644 --- a/instrumentation/finatra-2.9/javaagent/build.gradle.kts +++ b/instrumentation/finatra-2.9/javaagent/build.gradle.kts @@ -91,6 +91,12 @@ tasks { systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") systemProperty("metadataConfig", "otel.instrumentation.common.experimental.controller-telemetry.enabled=true") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } if (findProperty("testLatestDeps") as Boolean) { diff --git a/instrumentation/grails-3.0/javaagent/build.gradle.kts b/instrumentation/grails-3.0/javaagent/build.gradle.kts index 5f941b75d5be..cb9291a6caaf 100644 --- a/instrumentation/grails-3.0/javaagent/build.gradle.kts +++ b/instrumentation/grails-3.0/javaagent/build.gradle.kts @@ -81,4 +81,10 @@ tasks { systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") systemProperty("metadataConfig", "otel.instrumentation.common.experimental.controller-telemetry.enabled=true") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/grpc-1.6/javaagent/build.gradle.kts b/instrumentation/grpc-1.6/javaagent/build.gradle.kts index 88f75c8b3fc9..d4eca5080f1d 100644 --- a/instrumentation/grpc-1.6/javaagent/build.gradle.kts +++ b/instrumentation/grpc-1.6/javaagent/build.gradle.kts @@ -90,4 +90,10 @@ if (!(findProperty("testLatestDeps") as Boolean)) { } } } + + if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/hystrix-1.4/javaagent/build.gradle.kts b/instrumentation/hystrix-1.4/javaagent/build.gradle.kts index 67fa6b7b68fe..81da59016b7d 100644 --- a/instrumentation/hystrix-1.4/javaagent/build.gradle.kts +++ b/instrumentation/hystrix-1.4/javaagent/build.gradle.kts @@ -42,4 +42,10 @@ tasks { check { dependsOn(testExperimental) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts b/instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts index e86a67640402..9591dce74c94 100644 --- a/instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts +++ b/instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0/javaagent/build.gradle.kts @@ -60,4 +60,10 @@ tasks { check { dependsOn(testExperimental) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/log4j/log4j-appender-2.17/javaagent/build.gradle.kts b/instrumentation/log4j/log4j-appender-2.17/javaagent/build.gradle.kts index b70672938292..df42b16ff820 100644 --- a/instrumentation/log4j/log4j-appender-2.17/javaagent/build.gradle.kts +++ b/instrumentation/log4j/log4j-appender-2.17/javaagent/build.gradle.kts @@ -56,6 +56,12 @@ tasks { check { dependsOn(testAsync) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } tasks.withType().configureEach { diff --git a/instrumentation/pekko/pekko-actor-1.0/javaagent/build.gradle.kts b/instrumentation/pekko/pekko-actor-1.0/javaagent/build.gradle.kts index c6250ab62e93..df1fc28f61fd 100644 --- a/instrumentation/pekko/pekko-actor-1.0/javaagent/build.gradle.kts +++ b/instrumentation/pekko/pekko-actor-1.0/javaagent/build.gradle.kts @@ -42,3 +42,9 @@ if (findProperty("testLatestDeps") as Boolean) { } } } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/pekko/pekko-http-1.0/javaagent/build.gradle.kts b/instrumentation/pekko/pekko-http-1.0/javaagent/build.gradle.kts index fa768c279c46..6fcbb79aa6b4 100644 --- a/instrumentation/pekko/pekko-http-1.0/javaagent/build.gradle.kts +++ b/instrumentation/pekko/pekko-http-1.0/javaagent/build.gradle.kts @@ -98,6 +98,12 @@ tasks { check { dependsOn(testing.suites) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } if (findProperty("testLatestDeps") as Boolean) { diff --git a/instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts b/instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts index e55989c935ba..6eea0d621704 100644 --- a/instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts +++ b/instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts @@ -44,4 +44,10 @@ tasks { test { systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts b/instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts index 6fe7a94b9a65..3d3bd41fb6ec 100644 --- a/instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts +++ b/instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts @@ -50,4 +50,10 @@ tasks { test { systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts b/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts index 18963782b51b..9219b1cd9b9f 100644 --- a/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts @@ -74,4 +74,10 @@ tasks { check { dependsOn(testing.suites) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts index 6eee94d7a6ec..f1136e402b84 100644 --- a/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts +++ b/instrumentation/pulsar/pulsar-2.8/javaagent/build.gradle.kts @@ -39,6 +39,12 @@ tasks { check { dependsOn(testReceiveSpanDisabled) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } tasks.withType().configureEach { diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/build.gradle.kts b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/build.gradle.kts index ef6a0332bef2..950fe9a4b852 100644 --- a/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/build.gradle.kts +++ b/instrumentation/quarkus-resteasy-reactive/quarkus2-testing/build.gradle.kts @@ -64,4 +64,10 @@ tasks { systemProperty("quarkus-internal-test.serialized-app-model.path", testModelPath.toString()) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/build.gradle.kts b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/build.gradle.kts index 6623f8f6b3df..8f2a90c298b5 100644 --- a/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/build.gradle.kts +++ b/instrumentation/quarkus-resteasy-reactive/quarkus3-testing/build.gradle.kts @@ -71,4 +71,10 @@ tasks { systemProperty("quarkus-internal-test.serialized-app-model.path", testModelPath.toString()) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/ratpack/ratpack-1.4/javaagent/build.gradle.kts b/instrumentation/ratpack/ratpack-1.4/javaagent/build.gradle.kts index 878a5f6f2685..2ed90736d679 100644 --- a/instrumentation/ratpack/ratpack-1.4/javaagent/build.gradle.kts +++ b/instrumentation/ratpack/ratpack-1.4/javaagent/build.gradle.kts @@ -49,9 +49,12 @@ if (!(findProperty("testLatestDeps") as Boolean)) { tasks { withType().configureEach { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") } -} -tasks.withType().configureEach { - jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts b/instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts index eaebce6016a6..972465ee9970 100644 --- a/instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts +++ b/instrumentation/ratpack/ratpack-1.7/javaagent/build.gradle.kts @@ -30,4 +30,10 @@ tasks { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/rediscala-1.8/javaagent/build.gradle.kts b/instrumentation/rediscala-1.8/javaagent/build.gradle.kts index 83b22de5e5cf..de028b480f8b 100644 --- a/instrumentation/rediscala-1.8/javaagent/build.gradle.kts +++ b/instrumentation/rediscala-1.8/javaagent/build.gradle.kts @@ -64,6 +64,12 @@ tasks { test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } if (findProperty("testLatestDeps") as Boolean) { diff --git a/instrumentation/redisson/redisson-3.17/javaagent/build.gradle.kts b/instrumentation/redisson/redisson-3.17/javaagent/build.gradle.kts index d9e4b881b60d..c4ebf2c18b18 100644 --- a/instrumentation/redisson/redisson-3.17/javaagent/build.gradle.kts +++ b/instrumentation/redisson/redisson-3.17/javaagent/build.gradle.kts @@ -39,4 +39,10 @@ tasks { check { dependsOn(testStableSemconv) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/build.gradle.kts b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/build.gradle.kts index 9948b881a7f7..52d2974daf5b 100644 --- a/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/build.gradle.kts +++ b/instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0/javaagent/build.gradle.kts @@ -38,4 +38,10 @@ tasks { check { dependsOn(testReceiveSpanDisabled) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } diff --git a/instrumentation/scala-fork-join-2.8/javaagent/build.gradle.kts b/instrumentation/scala-fork-join-2.8/javaagent/build.gradle.kts index 69d3fcbeb342..28a558cbb172 100644 --- a/instrumentation/scala-fork-join-2.8/javaagent/build.gradle.kts +++ b/instrumentation/scala-fork-join-2.8/javaagent/build.gradle.kts @@ -23,3 +23,9 @@ dependencies { testImplementation(project(":instrumentation:executors:testing")) } + +if (findProperty("denyUnsafe") as Boolean) { + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/spring/spring-cloud-aws-3.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-aws-3.0/javaagent/build.gradle.kts index 2e1aac948038..60fd14472e50 100644 --- a/instrumentation/spring/spring-cloud-aws-3.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-aws-3.0/javaagent/build.gradle.kts @@ -26,3 +26,10 @@ dependencies { otelJava { minJavaVersionSupported.set(JavaVersion.VERSION_17) } + +if (findProperty("denyUnsafe") as Boolean) { + // org.elasticmq:elasticmq-rest-sqs_2.13 uses unsafe. Future versions are likely to fix this. + tasks.withType().configureEach { + enabled = false + } +} diff --git a/instrumentation/spring/spring-pulsar-1.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-pulsar-1.0/javaagent/build.gradle.kts index fc140f946b4c..e392a9ff23b7 100644 --- a/instrumentation/spring/spring-pulsar-1.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-pulsar-1.0/javaagent/build.gradle.kts @@ -71,6 +71,12 @@ tasks { check { dependsOn(testing.suites) } + + if (findProperty("denyUnsafe") as Boolean) { + withType().configureEach { + enabled = false + } + } } // spring 6 requires java 17 diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldDetector.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldDetector.java index a08da8d9088c..329e4afa19ac 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldDetector.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldDetector.java @@ -6,7 +6,6 @@ package io.opentelemetry.javaagent.bootstrap.field; import io.opentelemetry.instrumentation.api.internal.cache.Cache; -import java.lang.invoke.MethodHandles; import java.util.Collection; /** Helper class for detecting whether given class has virtual fields. */ @@ -49,8 +48,4 @@ public static boolean hasVirtualField(Class clazz, String virtualFieldInterfa public static void markVirtualFields(Class clazz, Collection virtualFieldClassName) { classesWithVirtualFields.put(clazz, virtualFieldClassName); } - - public static MethodHandles.Lookup lookup() { - return MethodHandles.lookup(); - } } diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldLookupSupplier.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldLookupSupplier.java new file mode 100644 index 000000000000..e5eee3b66ccc --- /dev/null +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/field/VirtualFieldLookupSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.bootstrap.field; + +import java.lang.invoke.MethodHandles; +import java.util.function.Supplier; + +/** + * Helper class that provides a MethodHandles.Lookup that allows defining classes in this package. + */ +public final class VirtualFieldLookupSupplier implements Supplier { + + @Override + public MethodHandles.Lookup get() { + return MethodHandles.lookup(); + } +} diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/ExecutorLookupSupplier.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/ExecutorLookupSupplier.java new file mode 100644 index 000000000000..a4e09338cd1f --- /dev/null +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/ExecutorLookupSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.executors; + +import java.lang.invoke.MethodHandles; +import java.util.function.Supplier; + +/** + * Helper class that provides a MethodHandles.Lookup that allows defining classes in this package. + */ +public final class ExecutorLookupSupplier implements Supplier { + + @Override + public MethodHandles.Lookup get() { + return MethodHandles.lookup(); + } +} diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/lambda/LambdaLookupSupplier.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/lambda/LambdaLookupSupplier.java new file mode 100644 index 000000000000..5514195e0b6a --- /dev/null +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/lambda/LambdaLookupSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.internal.lambda; + +import java.lang.invoke.MethodHandles; +import java.util.function.Supplier; + +/** + * Helper class that provides a MethodHandles.Lookup that allows defining classes in this package. + */ +public final class LambdaLookupSupplier implements Supplier { + + @Override + public MethodHandles.Lookup get() { + return MethodHandles.lookup(); + } +} diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionLookupSupplier.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionLookupSupplier.java new file mode 100644 index 000000000000..7189599c0334 --- /dev/null +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionLookupSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.internal.reflection; + +import java.lang.invoke.MethodHandles; +import java.util.function.Supplier; + +/** + * Helper class that provides a MethodHandles.Lookup that allows defining classes in this package. + */ +public final class ReflectionLookupSupplier implements Supplier { + + @Override + public MethodHandles.Lookup get() { + return MethodHandles.lookup(); + } +} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java deleted file mode 100644 index 85a307ff249a..000000000000 --- a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/SunMiscUnsafeGenerator.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.tooling; - -import static org.objectweb.asm.Opcodes.ACC_FINAL; -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; -import static org.objectweb.asm.Opcodes.ACC_PUBLIC; -import static org.objectweb.asm.Opcodes.ACC_STATIC; -import static org.objectweb.asm.Opcodes.ACC_SUPER; -import static org.objectweb.asm.Opcodes.ALOAD; -import static org.objectweb.asm.Opcodes.DUP; -import static org.objectweb.asm.Opcodes.GETSTATIC; -import static org.objectweb.asm.Opcodes.ILOAD; -import static org.objectweb.asm.Opcodes.INVOKESPECIAL; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; -import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; -import static org.objectweb.asm.Opcodes.IRETURN; -import static org.objectweb.asm.Opcodes.L2I; -import static org.objectweb.asm.Opcodes.NEW; -import static org.objectweb.asm.Opcodes.PUTSTATIC; -import static org.objectweb.asm.Opcodes.RETURN; -import static org.objectweb.asm.Opcodes.V1_5; - -import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; - -/** - * Helper class for generating our own copy of sun.misc.Unsafe that delegates to - * jdk.internal.misc.Unsafe. Used when jdk provided sun.misc.Unsafe is not available which can - * happen when running modular application without jdk.unsupported module. - */ -class SunMiscUnsafeGenerator { - private static final String UNSAFE_NAME = "sun/misc/Unsafe"; - private static final String UNSAFE_DESC = "L" + UNSAFE_NAME + ";"; - - private final Class internalUnsafeClass; - private final List fields = new ArrayList<>(); - private final List methods = new ArrayList<>(); - - public SunMiscUnsafeGenerator(Class internalUnsafeClass) { - this.internalUnsafeClass = internalUnsafeClass; - - addFields(); - addMethods(); - } - - private void addFields() { - // fields that our unsafe class will contain - addField("INVALID_FIELD_OFFSET", int.class); - addField("ARRAY_BOOLEAN_BASE_OFFSET", int.class); - addField("ARRAY_BYTE_BASE_OFFSET", int.class); - addField("ARRAY_SHORT_BASE_OFFSET", int.class); - addField("ARRAY_CHAR_BASE_OFFSET", int.class); - addField("ARRAY_INT_BASE_OFFSET", int.class); - addField("ARRAY_LONG_BASE_OFFSET", int.class); - addField("ARRAY_FLOAT_BASE_OFFSET", int.class); - addField("ARRAY_DOUBLE_BASE_OFFSET", int.class); - addField("ARRAY_OBJECT_BASE_OFFSET", int.class); - addField("ARRAY_BOOLEAN_INDEX_SCALE", int.class); - addField("ARRAY_BYTE_INDEX_SCALE", int.class); - addField("ARRAY_SHORT_INDEX_SCALE", int.class); - addField("ARRAY_CHAR_INDEX_SCALE", int.class); - addField("ARRAY_INT_INDEX_SCALE", int.class); - addField("ARRAY_LONG_INDEX_SCALE", int.class); - addField("ARRAY_FLOAT_INDEX_SCALE", int.class); - addField("ARRAY_DOUBLE_INDEX_SCALE", int.class); - addField("ARRAY_OBJECT_INDEX_SCALE", int.class); - addField("ADDRESS_SIZE", int.class); - } - - private Field findSourceField(String name) { - try { - return internalUnsafeClass.getDeclaredField(name); - } catch (NoSuchFieldException exception) { - return null; - } - } - - private boolean isSuitableField(Field field, Class type) { - // jdk25 changes field type from int to long for offsets - return field != null - && Modifier.isPublic(field.getModifiers()) - && (field.getType() == type || (field.getType() == long.class && type == int.class)); - } - - private void addField(String name, Class type) { - Field field = findSourceField(name); - if (!isSuitableField(field, type)) { - throw new IllegalStateException( - "Could not find suitable field for " + name + " " + Type.getDescriptor(type)); - } - fields.add(new FieldDescriptor(name, type, field.getType())); - } - - private void addMethods() { - // methods that our unsafe class will contain - addMethod( - "compareAndSwapObject", - boolean.class, - Object.class, - long.class, - Object.class, - Object.class); - addMethod("compareAndSwapInt", boolean.class, Object.class, long.class, int.class, int.class); - addMethod( - "compareAndSwapLong", boolean.class, Object.class, long.class, long.class, long.class); - addMethod("putOrderedObject", void.class, Object.class, long.class, Object.class); - addMethod("putOrderedInt", void.class, Object.class, long.class, int.class); - addMethod("putOrderedLong", void.class, Object.class, long.class, long.class); - addMethod("allocateInstance", Object.class, Class.class); - addMethod("loadFence", void.class); - addMethod("storeFence", void.class); - addMethod("fullFence", void.class); - addMethod("getObject", Object.class, Object.class, long.class); - addMethod("putObject", void.class, Object.class, long.class, Object.class); - addMethod("getBoolean", boolean.class, Object.class, long.class); - addMethod("putBoolean", void.class, Object.class, long.class, boolean.class); - addMethod("getByte", byte.class, long.class); - addMethod("getByte", byte.class, Object.class, long.class); - addMethod("putByte", void.class, long.class, byte.class); - addMethod("putByte", void.class, Object.class, long.class, byte.class); - addMethod("getShort", short.class, long.class); - addMethod("getShort", short.class, Object.class, long.class); - addMethod("putShort", void.class, long.class, short.class); - addMethod("putShort", void.class, Object.class, long.class, short.class); - addMethod("getChar", char.class, long.class); - addMethod("getChar", char.class, Object.class, long.class); - addMethod("putChar", void.class, Object.class, long.class, char.class); - addMethod("putChar", void.class, long.class, char.class); - addMethod("getInt", int.class, Object.class, long.class); - addMethod("getInt", int.class, long.class); - addMethod("putInt", void.class, long.class, int.class); - addMethod("putInt", void.class, Object.class, long.class, int.class); - addMethod("getLong", long.class, long.class); - addMethod("getLong", long.class, Object.class, long.class); - addMethod("putLong", void.class, long.class, long.class); - addMethod("putLong", void.class, Object.class, long.class, long.class); - addMethod("getFloat", float.class, long.class); - addMethod("getFloat", float.class, Object.class, long.class); - addMethod("putFloat", void.class, Object.class, long.class, float.class); - addMethod("putFloat", void.class, long.class, float.class); - addMethod("getDouble", double.class, Object.class, long.class); - addMethod("getDouble", double.class, long.class); - addMethod("putDouble", void.class, Object.class, long.class, double.class); - addMethod("putDouble", void.class, long.class, double.class); - addMethod("getObjectVolatile", Object.class, Object.class, long.class); - addMethod("putObjectVolatile", void.class, Object.class, long.class, Object.class); - addMethod("getBooleanVolatile", boolean.class, Object.class, long.class); - addMethod("putBooleanVolatile", void.class, Object.class, long.class, boolean.class); - addMethod("getByteVolatile", byte.class, Object.class, long.class); - addMethod("putByteVolatile", void.class, Object.class, long.class, byte.class); - addMethod("getShortVolatile", short.class, Object.class, long.class); - addMethod("putShortVolatile", void.class, Object.class, long.class, short.class); - addMethod("getCharVolatile", char.class, Object.class, long.class); - addMethod("putCharVolatile", void.class, Object.class, long.class, char.class); - addMethod("getIntVolatile", int.class, Object.class, long.class); - addMethod("putIntVolatile", void.class, Object.class, long.class, int.class); - addMethod("getLongVolatile", long.class, Object.class, long.class); - addMethod("putLongVolatile", void.class, Object.class, long.class, long.class); - addMethod("getFloatVolatile", float.class, Object.class, long.class); - addMethod("putFloatVolatile", void.class, Object.class, long.class, float.class); - addMethod("getDoubleVolatile", double.class, Object.class, long.class); - addMethod("putDoubleVolatile", void.class, Object.class, long.class, double.class); - addMethod("getAndAddInt", int.class, Object.class, long.class, int.class); - addMethod("getAndAddLong", long.class, Object.class, long.class, long.class); - addMethod("getAndSetInt", int.class, Object.class, long.class, int.class); - addMethod("getAndSetLong", long.class, Object.class, long.class, long.class); - addMethod("getAndSetObject", Object.class, Object.class, long.class, Object.class); - addMethod("park", void.class, boolean.class, long.class); - addMethod("unpark", void.class, Object.class); - addMethod("throwException", void.class, Throwable.class); - addMethod("objectFieldOffset", long.class, Field.class); - addMethod("staticFieldBase", Object.class, Field.class); - addMethod("staticFieldOffset", long.class, Field.class); - addMethod("shouldBeInitialized", boolean.class, Class.class); - addMethod("ensureClassInitialized", void.class, Class.class); - addMethod("getAddress", long.class, long.class); - addMethod("putAddress", void.class, long.class, long.class); - addMethod("allocateMemory", long.class, long.class); - addMethod("reallocateMemory", long.class, long.class, long.class); - addMethod("setMemory", void.class, long.class, long.class, byte.class); - addMethod("setMemory", void.class, Object.class, long.class, long.class, byte.class); - addMethod("copyMemory", void.class, long.class, long.class, long.class); - addMethod( - "copyMemory", void.class, Object.class, long.class, Object.class, long.class, long.class); - addMethod("freeMemory", void.class, long.class); - addMethod("arrayBaseOffset", int.class, Class.class); - addMethod("arrayIndexScale", int.class, Class.class); - addMethod("addressSize", int.class); - addMethod("pageSize", int.class); - addMethod("getLoadAverage", int.class, double[].class, int.class); - // defineAnonymousClass was removed in jdk17 - addOptionalMethod( - "defineAnonymousClass", Class.class, Class.class, byte[].class, Object[].class); - // this method is missing from internal unsafe in some jdk11 versions - addOptionalMethod("invokeCleaner", void.class, ByteBuffer.class); - } - - private static List getNameCandidates(String name) { - if (name.startsWith("compareAndSwap")) { - name = name.replace("compareAndSwap", "compareAndSet"); - } else if (name.startsWith("putOrdered")) { - name = name.replace("putOrdered", "put") + "Release"; - } - if (name.contains("Object")) { - String alternativeName = name.replace("Object", "Reference"); - return Arrays.asList(name, alternativeName); - } - - return Collections.singletonList(name); - } - - private void addOptionalMethod(String name, Class returnType, Class... parameterTypes) { - addMethod(name, true, getNameCandidates(name), returnType, parameterTypes); - } - - private void addMethod(String name, Class returnType, Class... parameterTypes) { - addMethod(name, false, getNameCandidates(name), returnType, parameterTypes); - } - - private void addMethod( - String name, - boolean optional, - List targetNameCandidates, - Class returnType, - Class... parameterTypes) { - Method target = null; - for (String candidate : targetNameCandidates) { - Method method = findSourceMethod(candidate, parameterTypes); - if (isSuitableMethod(method, returnType)) { - target = method; - break; - } - } - if (target == null) { - if (optional) { - return; - } - throw new IllegalStateException( - "Could not find suitable method for " - + name - + " " - + Type.getMethodDescriptor(Type.getType(returnType), toTypes(parameterTypes))); - } - methods.add( - new MethodDescriptor( - name, target.getName(), returnType, target.getReturnType(), parameterTypes)); - } - - private Method findSourceMethod(String name, Class... parameterTypes) { - try { - return internalUnsafeClass.getDeclaredMethod(name, parameterTypes); - } catch (NoSuchMethodException exception) { - return null; - } - } - - private boolean isSuitableMethod(Method method, Class returnType) { - // jdk25 changes method return type from int to long for methods returning fields offsets - return method != null - && Modifier.isPublic(method.getModifiers()) - && (method.getReturnType() == returnType - || (method.getReturnType() == long.class && returnType == int.class)); - } - - private static Type[] toTypes(Class... classes) { - Type[] result = new Type[classes.length]; - for (int i = 0; i < classes.length; i++) { - result[i] = Type.getType(classes[i]); - } - return result; - } - - private static class FieldDescriptor { - final String name; - final Class targetType; - final Class sourceType; - - FieldDescriptor(String name, Class targetType, Class sourceType) { - this.name = name; - this.targetType = targetType; - this.sourceType = sourceType; - } - } - - private static class MethodDescriptor { - final String name; - final String targetName; - final Class targetReturnType; - final Class sourceReturnType; - final Class[] parameterTypes; - - MethodDescriptor( - String name, - String targetName, - Class targetReturnType, - Class sourceReturnType, - Class[] parameterTypes) { - this.name = name; - this.targetName = targetName; - this.targetReturnType = targetReturnType; - this.sourceReturnType = sourceReturnType; - this.parameterTypes = parameterTypes; - } - } - - private byte[] getBytes() { - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - ClassVisitor cv = cw; - cv.visit(V1_5, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, UNSAFE_NAME, null, "java/lang/Object", null); - - { - FieldVisitor fv = - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "theUnsafe", UNSAFE_DESC, null, null); - fv.visitEnd(); - } - { - FieldVisitor fv = - cv.visitField( - ACC_PRIVATE | ACC_STATIC | ACC_FINAL, - "theInternalUnsafe", - Type.getDescriptor(internalUnsafeClass), - null, - null); - fv.visitEnd(); - } - - { - MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "", "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - for (FieldDescriptor field : fields) { - FieldVisitor fv = - cv.visitField( - ACC_PUBLIC | ACC_STATIC | ACC_FINAL, - field.name, - Type.getDescriptor(field.targetType), - null, - null); - fv.visitEnd(); - } - - for (MethodDescriptor method : methods) { - Type[] parameters = toTypes(method.parameterTypes); - Type returnType = Type.getType(method.targetReturnType); - String descriptor = Type.getMethodDescriptor(returnType, parameters); - String sourceDescriptor = - Type.getMethodDescriptor(Type.getType(method.sourceReturnType), parameters); - MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, method.name, descriptor, null, null); - mv.visitCode(); - mv.visitFieldInsn( - GETSTATIC, UNSAFE_NAME, "theInternalUnsafe", Type.getDescriptor(internalUnsafeClass)); - int slot = 1; - for (Type parameter : parameters) { - mv.visitVarInsn(parameter.getOpcode(ILOAD), slot); - slot += parameter.getSize(); - } - mv.visitMethodInsn( - INVOKEVIRTUAL, - Type.getInternalName(internalUnsafeClass), - method.targetName, - sourceDescriptor, - false); - if (method.targetReturnType != method.sourceReturnType - && method.targetReturnType == int.class - && method.sourceReturnType == long.class) { - mv.visitInsn(L2I); - } - mv.visitInsn(returnType.getOpcode(IRETURN)); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - { - MethodVisitor mv = cv.visitMethod(ACC_STATIC, "", "()V", null, null); - mv.visitCode(); - // private static final sun.misc.Unsafe theUnsafe = new Unsafe(); - mv.visitTypeInsn(NEW, UNSAFE_NAME); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, UNSAFE_NAME, "", "()V", false); - mv.visitFieldInsn(PUTSTATIC, UNSAFE_NAME, "theUnsafe", UNSAFE_DESC); - - // private static final jdk.internal.misc.Unsafe theInternalUnsafe = - // jdk.internal.misc.Unsafe.getUnsafe(); - mv.visitMethodInsn( - INVOKESTATIC, - Type.getInternalName(internalUnsafeClass), - "getUnsafe", - "()" + Type.getDescriptor(internalUnsafeClass), - false); - mv.visitFieldInsn( - PUTSTATIC, UNSAFE_NAME, "theInternalUnsafe", Type.getDescriptor(internalUnsafeClass)); - - // initialize field value from corresponding field in internal unsafe class - for (FieldDescriptor field : fields) { - mv.visitFieldInsn( - GETSTATIC, - Type.getInternalName(internalUnsafeClass), - field.name, - Type.getDescriptor(field.sourceType)); - if (field.sourceType != field.targetType - && field.targetType == int.class - && field.sourceType == long.class) { - mv.visitInsn(L2I); - } - mv.visitFieldInsn(PUTSTATIC, UNSAFE_NAME, field.name, Type.getDescriptor(field.targetType)); - } - - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - cv.visitEnd(); - return cw.toByteArray(); - } - - public static void generateUnsafe( - Class internalUnsafeClass, AgentClassLoader agentClassLoader) { - SunMiscUnsafeGenerator generator = new SunMiscUnsafeGenerator(internalUnsafeClass); - agentClassLoader.defineClass("sun.misc.Unsafe", generator.getBytes()); - } -} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java b/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java deleted file mode 100644 index 7f81277f154d..000000000000 --- a/javaagent-tooling/javaagent-tooling-java9/src/main/java/io/opentelemetry/javaagent/tooling/UnsafeInitializer.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.tooling; - -import static java.util.logging.Level.WARNING; - -import io.opentelemetry.javaagent.bootstrap.AgentClassLoader; -import java.lang.instrument.Instrumentation; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -public class UnsafeInitializer { - private static final Logger logger = Logger.getLogger(UnsafeInitializer.class.getName()); - - static void initialize(Instrumentation instrumentation, ClassLoader classLoader) { - initialize(instrumentation, classLoader, true); - } - - private static void initialize( - Instrumentation instrumentation, ClassLoader classLoader, boolean testUnsafePresent) { - Class unsafeClass; - try { - unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); - } catch (ClassNotFoundException exception) { - return; - } - - Map> exports = new HashMap<>(); - // expose jdk.internal.misc.Unsafe to our agent - // this is used to generate our replacement sun.misc.Unsafe and also by grpc/netty to call - // jdk.internal.misc.Unsafe.allocateUninitializedArray - exports.put( - unsafeClass.getPackage().getName(), - Collections.singleton(UnsafeInitializer.class.getModule())); - instrumentation.redefineModule( - unsafeClass.getModule(), - Collections.emptySet(), - exports, - Collections.emptyMap(), - Collections.emptySet(), - Collections.emptyMap()); - - if (testUnsafePresent && hasSunMiscUnsafe()) { - return; - } - if (!(classLoader instanceof AgentClassLoader)) { - // some tests don't pass AgentClassLoader, ignore them - return; - } - - try { - SunMiscUnsafeGenerator.generateUnsafe(unsafeClass, (AgentClassLoader) classLoader); - } catch (Throwable throwable) { - logger.log(WARNING, "Unsafe generation failed", throwable); - } - } - - private static boolean hasSunMiscUnsafe() { - try { - Class.forName("sun.misc.Unsafe"); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } -} diff --git a/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy b/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy deleted file mode 100644 index 671be3c55d5c..000000000000 --- a/javaagent-tooling/javaagent-tooling-java9/src/test/groovy/UnsafeTest.groovy +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.javaagent.bootstrap.AgentClassLoader -import io.opentelemetry.javaagent.tooling.UnsafeInitializer -import net.bytebuddy.agent.ByteBuddyAgent -import spock.lang.Specification - -class UnsafeTest extends Specification { - - def "test generate sun.misc.Unsafe"() { - setup: - ByteBuddyAgent.install() - URL testJarLocation = AgentClassLoader.getProtectionDomain().getCodeSource().getLocation() - AgentClassLoader loader = new AgentClassLoader(new File(testJarLocation.toURI())) - UnsafeInitializer.initialize(ByteBuddyAgent.getInstrumentation(), loader, false) - - expect: - loader.loadClass("sun.misc.Unsafe").getClassLoader() == loader - } -} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index e45fae651526..dca07f97cd8d 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -107,7 +107,6 @@ public static void installBytebuddyAgent( logVersionInfo(); if (earlyConfig.getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) { - setupUnsafe(inst); List agentListeners = loadOrdered(AgentListener.class, extensionClassLoader); installBytebuddyAgent(inst, extensionClassLoader, agentListeners, earlyConfig); } else { @@ -290,14 +289,6 @@ private static void copyNecessaryConfigToSystemProperties(ConfigProperties confi } } - private static void setupUnsafe(Instrumentation inst) { - try { - UnsafeInitializer.initialize(inst, AgentInstaller.class.getClassLoader()); - } catch (UnsupportedClassVersionError exception) { - // ignore - } - } - private static void setBootstrapPackages( ConfigProperties config, ClassLoader extensionClassLoader) { BootstrapPackagesBuilderImpl builder = new BootstrapPackagesBuilderImpl(); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderMap.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderMap.java index aedadef84eac..2a6fe77f8d86 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderMap.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderMap.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.tooling.util; import io.opentelemetry.instrumentation.api.internal.cache.Cache; +import io.opentelemetry.javaagent.tooling.HelperInjector; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.Collections; @@ -15,27 +16,41 @@ import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.modifier.Ownership; import net.bytebuddy.description.modifier.Visibility; -import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; class ClassLoaderMap { private static final Cache>> data = Cache.weak(); + private static final Map bootLoaderData = new ConcurrentHashMap<>(); + static Injector defaultInjector = + (classLoader, className, bytes) -> { + HelperInjector.injectHelperClasses( + classLoader, Collections.singletonMap(className, () -> bytes)); + return Class.forName(className, false, classLoader); + }; - public static Object get(ClassLoader classLoader, Object key) { - return getClassLoaderData(classLoader, false).get(key); + public static Object get(ClassLoader classLoader, Injector classInjector, Object key) { + return getClassLoaderData(classLoader, classInjector, false).get(key); } - public static void put(ClassLoader classLoader, Object key, Object value) { - getClassLoaderData(classLoader, true).put(key, value); + public static void put( + ClassLoader classLoader, Injector classInjector, Object key, Object value) { + getClassLoaderData(classLoader, classInjector, true).put(key, value); } public static Object computeIfAbsent( - ClassLoader classLoader, Object key, Supplier value) { - return getClassLoaderData(classLoader, true).computeIfAbsent(key, unused -> value.get()); + ClassLoader classLoader, + Injector classInjector, + Object key, + Supplier value) { + return getClassLoaderData(classLoader, classInjector, true) + .computeIfAbsent(key, unused -> value.get()); } private static Map getClassLoaderData( - ClassLoader classLoader, boolean initialize) { - classLoader = maskNullClassLoader(classLoader); + ClassLoader classLoader, Injector classInjector, boolean initialize) { + if (classLoader == null) { + return bootLoaderData; + } + WeakReference> weakReference = data.get(classLoader); Map map = weakReference != null ? weakReference.get() : null; if (map == null) { @@ -43,28 +58,30 @@ private static Map getClassLoaderData( if (!initialize) { return Collections.emptyMap(); } - map = createMap(classLoader); + map = createMap(classLoader, classInjector); data.put(classLoader, new WeakReference<>(map)); } return map; } @SuppressWarnings("unchecked") - private static Map createMap(ClassLoader classLoader) { + private static Map createMap(ClassLoader classLoader, Injector classInjector) { + String className = + "io.opentelemetry.javaagent.ClassLoaderData$$" + + Integer.toHexString(System.identityHashCode(classLoader)); // generate a class with a single static field named "data" and define it in the given class // loader - Class clazz = + byte[] bytes = new ByteBuddy() .subclass(Object.class) - .name( - "io.opentelemetry.javaagent.ClassLoaderData$$" - + Integer.toHexString(System.identityHashCode(classLoader))) + .name(className) .defineField("data", Object.class, Ownership.STATIC, Visibility.PUBLIC) .make() - .load(classLoader, ClassLoadingStrategy.Default.INJECTION.allowExistingTypes()) - .getLoaded(); + .getBytes(); Map map; try { + Class clazz = classInjector.inject(classLoader, className, bytes); + Field field = clazz.getField("data"); synchronized (classLoader) { map = (Map) field.get(classLoader); @@ -79,10 +96,9 @@ private static Map createMap(ClassLoader classLoader) { return map; } - private static final ClassLoader BOOT_LOADER = new ClassLoader(null) {}; - - private static ClassLoader maskNullClassLoader(ClassLoader classLoader) { - return classLoader == null ? BOOT_LOADER : classLoader; + @FunctionalInterface + interface Injector { + Class inject(ClassLoader classLoader, String className, byte[] bytes) throws Exception; } private ClassLoaderMap() {} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValue.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValue.java index 6288d8f97217..d0aa48f5e9b2 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValue.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValue.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.tooling.util; +import io.opentelemetry.javaagent.tooling.util.ClassLoaderMap.Injector; import java.util.function.Supplier; /** @@ -15,17 +16,28 @@ */ public final class ClassLoaderValue { + private final Injector classInjector; + + public ClassLoaderValue() { + this(ClassLoaderMap.defaultInjector); + } + + // visible for testing + ClassLoaderValue(Injector classInjector) { + this.classInjector = classInjector; + } + @SuppressWarnings("unchecked") public T get(ClassLoader classLoader) { - return (T) ClassLoaderMap.get(classLoader, this); + return (T) ClassLoaderMap.get(classLoader, classInjector, this); } public void put(ClassLoader classLoader, T value) { - ClassLoaderMap.put(classLoader, this, value); + ClassLoaderMap.put(classLoader, classInjector, this, value); } @SuppressWarnings("unchecked") public T computeIfAbsent(ClassLoader classLoader, Supplier value) { - return (T) ClassLoaderMap.computeIfAbsent(classLoader, this, value); + return (T) ClassLoaderMap.computeIfAbsent(classLoader, classInjector, this, value); } } diff --git a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValueTest.java b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValueTest.java index db8ba91f92e6..8381d6a4c8ac 100644 --- a/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValueTest.java +++ b/javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/util/ClassLoaderValueTest.java @@ -17,16 +17,16 @@ class ClassLoaderValueTest { @Test void testValue() { - testClassLoader(this.getClass().getClassLoader()); + testClassLoader(new TestClassLoader()); testClassLoader(null); } void testClassLoader(ClassLoader classLoader) { - ClassLoaderValue value1 = new ClassLoaderValue<>(); + ClassLoaderValue value1 = new ClassLoaderValue<>(new TestClassInjector()); value1.put(classLoader, "value"); assertThat(value1.get(classLoader)).isEqualTo("value"); - ClassLoaderValue value2 = new ClassLoaderValue<>(); + ClassLoaderValue value2 = new ClassLoaderValue<>(new TestClassInjector()); String value = "value"; String ret1 = value2.computeIfAbsent(classLoader, () -> value); String ret2 = @@ -42,8 +42,8 @@ void testClassLoader(ClassLoader classLoader) { @Test void testGc() throws InterruptedException, TimeoutException { - ClassLoader testClassLoader = new ClassLoader() {}; - ClassLoaderValue classLoaderValue = new ClassLoaderValue<>(); + ClassLoader testClassLoader = new TestClassLoader(); + ClassLoaderValue classLoaderValue = new ClassLoaderValue<>(new TestClassInjector()); Value value = new Value(); classLoaderValue.put(testClassLoader, value); WeakReference valueWeakReference = new WeakReference<>(value); @@ -62,5 +62,22 @@ void testGc() throws InterruptedException, TimeoutException { assertThat(valueWeakReference.get()).isNull(); } + static class TestClassLoader extends ClassLoader { + + public Class defineClass(String name, byte[] bytes) { + return super.defineClass(name, bytes, 0, bytes.length); + } + } + + static class TestClassInjector implements ClassLoaderMap.Injector { + @Override + public Class inject(ClassLoader classLoader, String className, byte[] classBytes) { + if (classLoader instanceof TestClassLoader) { + return ((TestClassLoader) classLoader).defineClass(className, classBytes); + } + throw new IllegalArgumentException("Unsupported class loader: " + classLoader); + } + } + private static class Value {} } diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index 62575564c875..f382e2e691b9 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -13,19 +13,23 @@ import io.opentelemetry.javaagent.bootstrap.HelperResources; import io.opentelemetry.javaagent.bootstrap.InjectedClassHelper; import io.opentelemetry.javaagent.bootstrap.InjectedClassHelper.HelperClassInfo; -import io.opentelemetry.javaagent.bootstrap.field.VirtualFieldAccessorMarker; -import io.opentelemetry.javaagent.bootstrap.field.VirtualFieldDetector; +import io.opentelemetry.javaagent.bootstrap.field.VirtualFieldLookupSupplier; import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode; +import io.opentelemetry.javaagent.instrumentation.executors.ExecutorLookupSupplier; +import io.opentelemetry.javaagent.instrumentation.internal.lambda.LambdaLookupSupplier; +import io.opentelemetry.javaagent.instrumentation.internal.reflection.ReflectionLookupSupplier; import io.opentelemetry.javaagent.tooling.muzzle.HelperResource; import java.io.File; import java.io.IOException; import java.lang.instrument.Instrumentation; +import java.lang.invoke.MethodHandles; import java.net.URL; import java.nio.file.Files; import java.security.ProtectionDomain; import java.security.SecureClassLoader; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -297,6 +301,23 @@ private void injectHelperClasses( } } + public static void injectHelperClasses( + ClassLoader classLoader, Map> classNameToBytes) { + if (isBootClassLoader(classLoader)) { + throw new UnsupportedOperationException("boot loader not supported"); + } + if (classNameToBytes.isEmpty()) { + return; + } + + Map map = + helperClasses.computeIfAbsent(classLoader, (unused) -> new ConcurrentHashMap<>()); + for (Map.Entry> entry : classNameToBytes.entrySet()) { + HelperClass injector = new HelperClass(entry.getValue()); + map.put(entry.getKey(), injector); + } + } + private static Map resolve(Map> classes) { Map result = new LinkedHashMap<>(); for (Map.Entry> entry : classes.entrySet()) { @@ -305,31 +326,53 @@ private static Map resolve(Map> classes return result; } - private static final String virtualFieldPackage = - VirtualFieldAccessorMarker.class.getPackage().getName() + "."; + // disable using unsafe for jdk 23+ where it prints a warning + private static final boolean canUseUnsafe = + Double.parseDouble(System.getProperty("java.specification.version")) < 23; + private static final Map packageLookups = new HashMap<>(); - private void injectBootstrapClassLoader(Map> inject) throws IOException { + static { + // add lookups for instrumentations that define classes in boot loader + addPackageLookup(new ExecutorLookupSupplier()); + addPackageLookup(new LambdaLookupSupplier()); + addPackageLookup(new ReflectionLookupSupplier()); + // because all generated virtual field classes are in the same package we can use lookup to + // define them + addPackageLookup(new VirtualFieldLookupSupplier()); + } + private static void addPackageLookup(Supplier supplier) { + packageLookups.put(supplier.getClass().getPackage().getName(), supplier.get()); + } + + private static ClassInjector getClassInjector(String packageName) { + MethodHandles.Lookup lookup = packageLookups.get(packageName); + return lookup != null ? ClassInjector.UsingLookup.of(lookup) : null; + } + + private void injectBootstrapClassLoader(Map> inject) throws IOException { Map classnameToBytes = resolve(inject); if (helperInjectorListener != null) { helperInjectorListener.onInjection(classnameToBytes); } if (ClassInjector.UsingLookup.isAvailable()) { - // because all generated virtual field classes are in the same package we can use lookup to - // define them - ClassInjector virtualFieldInjector = - ClassInjector.UsingLookup.of(VirtualFieldDetector.lookup()); - for (Iterator> iterator = classnameToBytes.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); String className = entry.getKey(); - if (className.startsWith(virtualFieldPackage)) { + int dotIndex = className.lastIndexOf('.'); + if (dotIndex == -1) { + continue; + } + String packageName = className.substring(0, dotIndex); + // if we have a lookup for this package we can use it to define the class + ClassInjector classInjector = getClassInjector(packageName); + if (classInjector != null) { iterator.remove(); try { - virtualFieldInjector.injectRaw(Collections.singletonMap(className, entry.getValue())); + classInjector.injectRaw(Collections.singletonMap(className, entry.getValue())); } catch (LinkageError error) { // Unlike the ClassInjector.UsingUnsafe.ofBootLoader() ClassInjector.UsingLookup doesn't // check whether the class got loaded when there is an exception defining it. @@ -347,10 +390,11 @@ private void injectBootstrapClassLoader(Map> inject) th } } - // TODO by default, we use unsafe to define rest of the classes into boot loader - // can be disabled with -Dnet.bytebuddy.safe=true - // use -Dsun.misc.unsafe.memory.access=debug to check where unsafe is used - if (ClassInjector.UsingUnsafe.isAvailable() && !classnameToBytes.isEmpty()) { + if (classnameToBytes.isEmpty()) { + return; + } + + if (canUseUnsafe && ClassInjector.UsingUnsafe.isAvailable()) { ClassInjector.UsingUnsafe.ofBootLoader().injectRaw(classnameToBytes); return; } diff --git a/testing/agent-exporter/build.gradle.kts b/testing/agent-exporter/build.gradle.kts index 3534d1d8eeb9..ffbbaf959fe6 100644 --- a/testing/agent-exporter/build.gradle.kts +++ b/testing/agent-exporter/build.gradle.kts @@ -15,6 +15,8 @@ dependencies { compileOnly(project(":javaagent-extension-api")) compileOnly(project(":javaagent-bootstrap")) compileOnly(project(":javaagent-tooling")) + // Used by byte-buddy but not brought in as a transitive dependency. + compileOnly("com.google.code.findbugs:annotations") implementation("io.opentelemetry:opentelemetry-exporter-otlp-common") compileOnly("io.opentelemetry:opentelemetry-api-incubator") diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DefaultStreamMessageInstrumentation.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DefaultStreamMessageInstrumentation.java new file mode 100644 index 000000000000..ec6b988f65cf --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DefaultStreamMessageInstrumentation.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.instrumentation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.concurrent.ConcurrentLinkedQueue; +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldList; +import net.bytebuddy.description.method.MethodList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.pool.TypePool; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class DefaultStreamMessageInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.opentelemetry.testing.internal.armeria.common.stream.DefaultStreamMessage"); + } + + @Override + public void transform(TypeTransformer transformer) { + // in the constructor of DefaultStreamMessage replace new MpscChunkedArrayQueue with new + // ConcurrentLinkedQueue + transformer.applyTransformer( + (builder, typeDescription, classLoader, javaModule, protectionDomain) -> + builder.visit( + new AsmVisitorWrapper.AbstractBase() { + + @Override + public ClassVisitor wrap( + TypeDescription instrumentedType, + ClassVisitor classVisitor, + Implementation.Context implementationContext, + TypePool typePool, + FieldList fields, + MethodList methods, + int writerFlags, + int readerFlags) { + return new ClassVisitor(Opcodes.ASM9, classVisitor) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions) { + MethodVisitor mv = + super.visitMethod(access, name, descriptor, signature, exceptions); + if (!"".equals(name)) { + return mv; + } + + return new MethodVisitor(api, mv) { + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.NEW + && type.endsWith("jctools/queues/MpscChunkedArrayQueue")) { + type = Type.getInternalName(ConcurrentLinkedQueue.class); + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn( + int opcode, + String owner, + String name, + String descriptor, + boolean isInterface) { + if (opcode == Opcodes.INVOKESPECIAL + && owner.endsWith("jctools/queues/MpscChunkedArrayQueue") + && name.equals("") + && descriptor.equals("(II)V")) { + owner = Type.getInternalName(ConcurrentLinkedQueue.class); + // original constructor takes two arguments, remove them from the + // stack + super.visitInsn(Opcodes.POP); + super.visitInsn(Opcodes.POP); + super.visitMethodInsn( + opcode, + Type.getInternalName(ConcurrentLinkedQueue.class), + name, + "()V", + isInterface); + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + }; + } + }; + } + })); + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DenyUnsafeInstrumentationModule.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DenyUnsafeInstrumentationModule.java new file mode 100644 index 000000000000..aedaf9f164f5 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/DenyUnsafeInstrumentationModule.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.instrumentation; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.List; + +/** + * Instrument libraries used for testing to remove usages of unsafe so we could test the agent with + * --sun-misc-unsafe-memory-access=deny + */ +@AutoService(InstrumentationModule.class) +public class DenyUnsafeInstrumentationModule extends InstrumentationModule { + + public DenyUnsafeInstrumentationModule() { + super("deny-unsafe"); + } + + @Override + public List typeInstrumentations() { + return asList( + new ProtoUtf8UnsafeProcessorInstrumentation(), + new ExceptionSamplerInstrumentation(), + new ServerInstrumentation(), + new DefaultStreamMessageInstrumentation()); + } + + @Override + public boolean defaultEnabled(ConfigProperties config) { + // using a system property here will enable the instrumentation when declarative config is used + return Boolean.getBoolean("otel.instrumentation.deny-unsafe.enabled"); + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ExceptionSamplerInstrumentation.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ExceptionSamplerInstrumentation.java new file mode 100644 index 000000000000..eaff932a7c52 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ExceptionSamplerInstrumentation.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.instrumentation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.concurrent.ConcurrentHashMap; +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldList; +import net.bytebuddy.description.method.MethodList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.pool.TypePool; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class ExceptionSamplerInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.opentelemetry.testing.internal.armeria.common.ExceptionSampler"); + } + + @Override + public void transform(TypeTransformer transformer) { + // in the constructor of ExceptionSampler replace new NonBlockingHashMap with new + // ConcurrentHashMap + transformer.applyTransformer( + (builder, typeDescription, classLoader, javaModule, protectionDomain) -> + builder.visit( + new AsmVisitorWrapper.AbstractBase() { + + @Override + public ClassVisitor wrap( + TypeDescription instrumentedType, + ClassVisitor classVisitor, + Implementation.Context implementationContext, + TypePool typePool, + FieldList fields, + MethodList methods, + int writerFlags, + int readerFlags) { + return new ClassVisitor(Opcodes.ASM9, classVisitor) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions) { + MethodVisitor mv = + super.visitMethod(access, name, descriptor, signature, exceptions); + if (!"".equals(name)) { + return mv; + } + + return new MethodVisitor(api, mv) { + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.NEW + && type.endsWith("jctools/maps/NonBlockingHashMap")) { + type = Type.getInternalName(ConcurrentHashMap.class); + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn( + int opcode, + String owner, + String name, + String descriptor, + boolean isInterface) { + if (opcode == Opcodes.INVOKESPECIAL + && owner.endsWith("jctools/maps/NonBlockingHashMap") + && name.equals("")) { + owner = Type.getInternalName(ConcurrentHashMap.class); + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + }; + } + }; + } + })); + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ProtoUtf8UnsafeProcessorInstrumentation.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ProtoUtf8UnsafeProcessorInstrumentation.java new file mode 100644 index 000000000000..5bbefd38768d --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ProtoUtf8UnsafeProcessorInstrumentation.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.instrumentation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ProtoUtf8UnsafeProcessorInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("io.opentelemetry.testing.internal.protobuf.Utf8$UnsafeProcessor"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("isAvailable"), this.getClass().getName() + "$IsAvailableAdvice"); + } + + @SuppressWarnings("unused") + public static class IsAvailableAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, suppress = Throwable.class) + public static boolean onEnter() { + return true; + } + + @Advice.AssignReturned.ToReturned + @Advice.OnMethodExit(suppress = Throwable.class) + public static boolean onExit() { + // make isAvailable always return false + return false; + } + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ServerInstrumentation.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ServerInstrumentation.java new file mode 100644 index 000000000000..8de93a0ec740 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/instrumentation/ServerInstrumentation.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.instrumentation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldList; +import net.bytebuddy.description.method.MethodList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.pool.TypePool; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class ServerInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return named("io.opentelemetry.testing.internal.armeria.server.Server"); + } + + @Override + public void transform(TypeTransformer transformer) { + // in the constructor of Server replace new NonBlockingHashSet with + // Collections.newSetFromMap(new ConcurrentHashMap()) + transformer.applyTransformer( + (builder, typeDescription, classLoader, javaModule, protectionDomain) -> + builder.visit( + new AsmVisitorWrapper.AbstractBase() { + + @Override + public ClassVisitor wrap( + TypeDescription instrumentedType, + ClassVisitor classVisitor, + Implementation.Context implementationContext, + TypePool typePool, + FieldList fields, + MethodList methods, + int writerFlags, + int readerFlags) { + return new ClassVisitor(Opcodes.ASM9, classVisitor) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions) { + MethodVisitor mv = + super.visitMethod(access, name, descriptor, signature, exceptions); + if (!"".equals(name)) { + return mv; + } + + return new MethodVisitor(api, mv) { + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.NEW + && type.endsWith("jctools/maps/NonBlockingHashSet")) { + type = Type.getInternalName(ConcurrentHashMap.class); + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn( + int opcode, + String owner, + String name, + String descriptor, + boolean isInterface) { + if (opcode == Opcodes.INVOKESPECIAL + && owner.endsWith("jctools/maps/NonBlockingHashSet") + && name.equals("")) { + super.visitMethodInsn( + opcode, + Type.getInternalName(ConcurrentHashMap.class), + name, + descriptor, + isInterface); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName(Collections.class), + "newSetFromMap", + "(Ljava/util/Map;)Ljava/util/Set;", + false); + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + }; + } + }; + } + })); + } +} diff --git a/testing/dependencies-shaded-for-testing/build.gradle.kts b/testing/dependencies-shaded-for-testing/build.gradle.kts index 9d74ff0b1992..b69b8929cb2e 100644 --- a/testing/dependencies-shaded-for-testing/build.gradle.kts +++ b/testing/dependencies-shaded-for-testing/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("otel.java-conventions") } +val denyUnsafe = gradle.startParameter.projectProperties["denyUnsafe"] == "true" + dependencies { implementation("com.linecorp.armeria:armeria-junit5:1.33.4") implementation("com.google.errorprone:error_prone_annotations") @@ -10,6 +12,12 @@ dependencies { implementation("com.google.protobuf:protobuf-java-util:4.33.0") implementation("com.github.tomakehurst:wiremock-jre8:2.35.2") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.0") + // we'll replace caffeine shaded in armeria with a later version that doesn't use Unsafe. Caffeine + // 3+ doesn't work with Java 8, but that is fine since --sun-misc-unsafe-memory-access=deny + // requires Java 23. + if (denyUnsafe) { + implementation("com.github.ben-manes.caffeine:caffeine:3.2.2") + } } tasks { @@ -23,6 +31,12 @@ tasks { // Ensures tests are not affected by Armeria instrumentation relocate("com.linecorp.armeria", "io.opentelemetry.testing.internal.armeria") + if (denyUnsafe) { + relocate( + "com.github.benmanes.caffeine", + "io.opentelemetry.testing.internal.armeria.internal.shaded.caffeine" + ) + } relocate("com.fasterxml.jackson", "io.opentelemetry.testing.internal.jackson") relocate("net.bytebuddy", "io.opentelemetry.testing.internal.bytebuddy") relocate("reactor", "io.opentelemetry.testing.internal.reactor") @@ -62,6 +76,10 @@ tasks { filesMatching("META-INF/services/**") { duplicatesStrategy = DuplicatesStrategy.INCLUDE } + // exclude caffeine shaded in armeria + if (denyUnsafe) { + exclude("com/linecorp/armeria/internal/shaded/caffeine/**") + } // relocate everything else enableAutoRelocation = true