diff --git a/.circleci/config.continue.yml.j2 b/.circleci/config.continue.yml.j2 index 4a3d002d2b8..39b2f5aa91e 100644 --- a/.circleci/config.continue.yml.j2 +++ b/.circleci/config.continue.yml.j2 @@ -36,7 +36,7 @@ instrumentation_modules: &instrumentation_modules "dd-java-agent/instrumentation debugger_modules: &debugger_modules "dd-java-agent/agent-debugger|dd-java-agent/agent-bootstrap|dd-java-agent/agent-builder|internal-api|communication|dd-trace-core" profiling_modules: &profiling_modules "dd-java-agent/agent-profiling" -default_system_tests_commit: &default_system_tests_commit edfea31b7a9ceaed03b705de34a4e525853444c0 +default_system_tests_commit: &default_system_tests_commit ca7566bf7e66c0afdf29b0d3c07b2972e5869997 parameters: nightly: @@ -735,6 +735,7 @@ jobs: name: Generate muzzle dep report command: >- SKIP_BUILDSCAN="true" + GRADLE_OPTS="-Dorg.gradle.jvmargs='-Xmx2G -Xms2G -XX:ErrorFile=/tmp/hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp'" ./gradlew generateMuzzleReport muzzleInstrumentationReport - run: name: Collect Reports @@ -1193,6 +1194,15 @@ build_test_jobs: &build_test_jobs stage: smoke cacheType: smoke testJvm: "graalvm17" + + - tests: + requires: + - ok_to_test + name: test_graalvm21_smoke + gradleTarget: "stageMainDist :dd-smoke-test:spring-boot-3.0-native:test" + stage: smoke + cacheType: smoke + testJvm: "graalvm21" - tests: requires: diff --git a/.circleci/render_config.py b/.circleci/render_config.py index 51cd43d413d..9be71879b92 100755 --- a/.circleci/render_config.py +++ b/.circleci/render_config.py @@ -28,7 +28,7 @@ } # Version to use for all the base Docker images, see # https://github.com/DataDog/dd-trace-java-docker-build/pkgs/container/dd-trace-java-docker-build -DOCKER_IMAGE_VERSION="v23.09" +DOCKER_IMAGE_VERSION="v23.10" # Get labels from pull requests to override some defaults for jobs to run. # `run-tests: all` will run all tests. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 658580cf016..b0076b4541c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ dd-java-agent/instrumentation/cucumber/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/jacoco/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/junit-4.10/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/junit-5.3/ @DataDog/ci-app-libraries-java +dd-java-agent/instrumentation/karate/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/testng/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/gradle/ @DataDog/ci-app-libraries-java dd-java-agent/instrumentation/maven-3.2.1/ @DataDog/ci-app-libraries-java @@ -36,6 +37,11 @@ dd-smoke-tests/debugger-integration-tests/ @DataDog/debugger-java dd-java-agent/agent-iast/ @DataDog/asm-java dd-java-agent/instrumentation/*iast* @DataDog/asm-java dd-java-agent/instrumentation/*appsec* @DataDog/asm-java +dd-java-agent/instrumentation/json/ @DataDog/asm-java +dd-smoke-tests/iast-util/ @DataDog/asm-java +dd-smoke-tests/spring-security/ @DataDog/asm-java +dd-java-agent/instrumentation/commons-fileupload/ @DataDog/asm-java +dd-java-agent/instrumentation/spring-security-5/ @DataDog/asm-java **/appsec/ @DataDog/asm-java **/iast/ @DataDog/asm-java **/Iast*.java @DataDog/asm-java diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 69460820818..f125629f6e6 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -6,7 +6,7 @@ This lists and describes the repository GitHub actions. ### add-assets-to-release [🔗](add-assets-to-release.yaml) -_Trigger:_ When a release is published +_Trigger:_ When a release is published. _Actions:_ * Ensure the release name is properly formatted (using `x.y.z` format), @@ -16,7 +16,7 @@ _Recovery:_ Download artifacts and upload them manually to the release. ### add-milestone-to-pull-requests [🔗](add-milestone-to-pull-requests.yaml) -_Trigger:_ When a PR to `master` is closed +_Trigger:_ When a PR to `master` is closed. _Action:_ Get the last (by name) opened milestone and affect it to the closed pull request. @@ -24,7 +24,7 @@ _Recovery:_ Attach the milestone by hand to the PR. ### create-next-milestone [🔗](create-next-milestone.yaml) -_Trigger:_ When closing a milestone +_Trigger:_ When closing a milestone. _Action:_ Create a new milestone by incrementing minor version. @@ -44,7 +44,7 @@ _Recovery:_ Manually trigger the action again on the relevant tag. ## increment-milestones-on-tag [🔗](increment-milestones-on-tag.yaml) -_Trigger:_ When creating a tag +_Trigger:_ When creating a tag. _Actions:_ * Close the milestone related to the tag, @@ -57,7 +57,7 @@ As there is no milestone for _patch_ releases, it won't close and create _patch_ ## update-download-releases [🔗](update-download-releases.yaml) -_Trigger:_ When a release is published +_Trigger:_ When a release is published. _Action:_ Update the _download releases_ with the latest release artifact. @@ -67,7 +67,7 @@ _Notes:_ _Download releases_ are special GitHub releases with fixed URL and tags ## update-issues-on-release [🔗](update-issues-on-release.yaml) -_Trigger:_ When a release is published +_Trigger:_ When a release is published. _Action:_ * Find all issues related to the release by checking the related milestone, @@ -78,15 +78,21 @@ _Recovery:_ Check at the milestone for the related issues and update them manual ## Code Quality and Security +### comment-on-submodule-update [🔗](comment-on-submodule-update.yaml) + +_Trigger:_ When creating a PR commits to `master` or a `release/*` branch with a Git Submodule update. + +_Action:_ Notify the PR author through comments that about the Git Submodule update. + ### codeql-analysis [🔗](codeql-analysis.yml) -_Trigger:_ When pushing commits to `master` or any pull request to `master` +_Trigger:_ When pushing commits to `master` or any pull request to `master`. _Action:_ Run GitHub CodeQL action and upload result to GitHub security tab. ### trivy-analysis [🔗](trivy-analysis.yml) -_Trigger:_ When pushing commits to `master` or any pull request to `master` +_Trigger:_ When pushing commits to `master` or any pull request to `master`. _Action:_ Run Trivy security scanner on built artifacts and upload result to GitHub security tab. @@ -100,7 +106,7 @@ _Comment:_ To delete? ### lib-injection [🔗](lib-injection.yaml) -_Trigger:_ When pushing commits to `master`, release branches or any PR targetting `master`, and when creating tags +_Trigger:_ When pushing commits to `master`, release branches or any PR targetting `master`, and when creating tags. _Actions:_ * Build and publish to GHCR a Docker image with the Java tracer agent, @@ -108,13 +114,13 @@ _Actions:_ ### lib-injection-manual-release [🔗](lib-injection-manual-release.yaml) -_Trigger:_ When manually triggered +_Trigger:_ When manually triggered. _Action:_ Build and publish to GHCR a Docker image with the given Java tracer version. ### lib-injection-prune-registry [🔗](lib-injection-prune-registry.yaml) -_Trigger:_ Every week or manually +_Trigger:_ Every week or manually. _Action:_ Clean up old lib-injection Docker images from GHCR. diff --git a/.github/workflows/comment-on-submodule-update.yaml b/.github/workflows/comment-on-submodule-update.yaml new file mode 100644 index 00000000000..df60b2044b0 --- /dev/null +++ b/.github/workflows/comment-on-submodule-update.yaml @@ -0,0 +1,29 @@ +name: Comment on Submodule Update + +on: + pull_request: + branches: + - 'master' + - 'release/**' + paths: + - 'dd-java-agent/agent-jmxfetch/integrations-core' + +jobs: + comment_on_submodule_update: + runs-on: ubuntu-latest + + steps: + - name: Post comment on submodule update + uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 # 6.3.3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Hi! 👋 Looks like you updated a Git Submodule.\n' + + 'If this was not intentional please make sure to:\n\n' + + '* Update the submodule to the latest commit on the master branch using the `git submodule update` command,\n' + + '* Check you [properly set up your environment for contributing](https://github.com/DataDog/dd-trace-java/blob/master/CONTRIBUTING.md#git-submodule-setup).' + }) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3be67d8c675..4043b3131e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ cache: &default_cache .gradle_build: &gradle_build <<: *common - image: ghcr.io/datadog/dd-trace-java-docker-build:base + image: ghcr.io/datadog/dd-trace-java-docker-build:v23.10-base before_script: - export GRADLE_USER_HOME=`pwd`/.gradle @@ -82,6 +82,12 @@ package: script: - ../.gitlab/build_java_package.sh +package-arm: + extends: .package-arm + when: on_success # this can't use 'needs: [build]', since build is not available in the scheduled pipeline + script: + - ../.gitlab/build_java_package.sh + .release-package: stage: deploy variables: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c903bdffab..57b14e7aef2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -220,11 +220,11 @@ Suggested plugins and settings: To run tests on a different JVM than the one used for doing the build, you need two things: -1) An environment variable pointing to the JVM to use on the form `JAVA_[JDKNAME]_HOME`, i.e. `JAVA_ZULU15_HOME` +1) An environment variable pointing to the JVM to use on the form `JAVA_[JDKNAME]_HOME`, e.g. `JAVA_ZULU15_HOME`, `JAVA_GRAALVM17_HOME` -2) A command line option to the gradle task on the form `-PtestJvm=[JDKNAME]`, i.e. `-PtestJvm=ZULU15` +2) A command line option to the gradle task on the form `-PtestJvm=[JDKNAME]`, e.g. `-PtestJvm=ZULU15`, `-PtestJvm=GRAALVM17` -Please note that the JDK name needs to end with the JDK version, i.e. `11`, `ZULU15`, `ORACLE8`, et.c. +Please note that the JDK name needs to end with the JDK version, e.g. `11`, `ZULU15`, `ORACLE8`, `GRAALVM17`, etc. ## The APM Test Agent diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index d19514e6543..34e41e48faa 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -116,7 +116,9 @@ abstract class InstrumentTask extends DefaultTask { parameters.buildStartedTime.set(invocationDetails.buildStartedTime) parameters.pluginClassPath.setFrom(project.configurations.findByName('instrumentPluginClasspath') ?: []) parameters.plugins.set(extension.plugins) - parameters.instrumentingClassPath.setFrom(project.configurations.compileClasspath.findAll { + def matcher = instrumentTask.name =~ /instrument([A-Z].+)Java/ + def cfgName = matcher.matches() ? "${matcher.group(1).uncapitalize()}CompileClasspath" : 'compileClasspath' + parameters.instrumentingClassPath.setFrom(project.configurations[cfgName].findAll { it.name != 'previous-compilation-data.bin' && !it.name.endsWith(".gz") } + sourceDirectory + (extension.additionalClasspath[instrumentTask.name] ?: [])*.get()) parameters.sourceDirectory.set(sourceDirectory.asFile) diff --git a/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java b/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java index 1f1f73f1b7a..ae3c375875e 100644 --- a/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java +++ b/communication/src/main/java/datadog/communication/ddagent/DDAgentFeaturesDiscovery.java @@ -47,6 +47,8 @@ public class DDAgentFeaturesDiscovery implements DroppingPolicy { public static final String DEBUGGER_ENDPOINT = "debugger/v1/input"; + public static final String TELEMETRY_PROXY_ENDPOINT = "telemetry/proxy/"; + private static final long MIN_FEATURE_DISCOVERY_INTERVAL_MILLIS = 60 * 1000; private final OkHttpClient client; @@ -58,6 +60,7 @@ public class DDAgentFeaturesDiscovery implements DroppingPolicy { private final boolean metricsEnabled; private final String[] dataStreamsEndpoints = {V01_DATASTREAMS_ENDPOINT}; private final String[] evpProxyEndpoints = {V2_EVP_PROXY_ENDPOINT}; + private final String[] telemetryProxyEndpoints = {TELEMETRY_PROXY_ENDPOINT}; private volatile String traceEndpoint; private volatile String metricsEndpoint; @@ -69,6 +72,7 @@ public class DDAgentFeaturesDiscovery implements DroppingPolicy { private volatile String debuggerEndpoint; private volatile String evpProxyEndpoint; private volatile String version; + private volatile String telemetryProxyEndpoint; private long lastTimeDiscovered; @@ -100,6 +104,7 @@ private void reset() { evpProxyEndpoint = null; version = null; lastTimeDiscovered = 0; + telemetryProxyEndpoint = null; } /** Run feature discovery, unconditionally. */ @@ -162,14 +167,15 @@ private void doDiscovery() { if (log.isDebugEnabled()) { log.debug( - "discovered traceEndpoint={}, metricsEndpoint={}, supportsDropping={}, supportsLongRunning={}, dataStreamsEndpoint={}, configEndpoint={}, evpProxyEndpoint={}", + "discovered traceEndpoint={}, metricsEndpoint={}, supportsDropping={}, supportsLongRunning={}, dataStreamsEndpoint={}, configEndpoint={}, evpProxyEndpoint={}, telemetryProxyEndpoint={}", traceEndpoint, metricsEndpoint, supportsDropping, supportsLongRunning, dataStreamsEndpoint, configEndpoint, - evpProxyEndpoint); + evpProxyEndpoint, + telemetryProxyEndpoint); } } @@ -247,6 +253,13 @@ private boolean processInfoResponse(String response) { } } + for (String endpoint : telemetryProxyEndpoints) { + if (endpoints.contains(endpoint) || endpoints.contains("/" + endpoint)) { + telemetryProxyEndpoint = endpoint; + break; + } + } + supportsLongRunning = Boolean.TRUE.equals(map.getOrDefault("long_running_spans", false)); if (metricsEnabled) { @@ -352,4 +365,8 @@ public String state() { public boolean active() { return supportsMetrics() && supportsDropping; } + + public boolean supportsTelemetryProxy() { + return telemetryProxyEndpoint != null; + } } diff --git a/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java b/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java index e00b1260e04..5491f999da2 100644 --- a/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java +++ b/communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java @@ -39,10 +39,15 @@ public void createRemaining(Config config) { String namedPipe = config.getAgentNamedPipe(); okHttpClient = OkHttpUtils.buildHttpClient( - agentUrl, - unixDomainSocket, - namedPipe, - TimeUnit.SECONDS.toMillis(config.getAgentTimeout())); + agentUrl, unixDomainSocket, namedPipe, getHttpClientTimeout(config)); + } + } + + private static long getHttpClientTimeout(Config config) { + if (!config.isCiVisibilityEnabled() || !config.isCiVisibilityAgentlessEnabled()) { + return TimeUnit.SECONDS.toMillis(config.getAgentTimeout()); + } else { + return config.getCiVisibilityBackendApiTimeoutMillis(); } } diff --git a/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java b/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java index 0455e2ec3ad..e4dbe099ca2 100644 --- a/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java +++ b/communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java @@ -115,7 +115,6 @@ private void doConnect() { // when using UDS, set "entity-id" to "none" to avoid having the DogStatsD // server add origin tags (see https://github.com/DataDog/jmxfetch/pull/264) if (this.port == 0) { - clientBuilder.constantTags("dd.internal.card:none"); clientBuilder.entityID("none"); } else { clientBuilder.entityID(null); diff --git a/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy b/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy index af9b865d1b4..70b5519d355 100644 --- a/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy +++ b/communication/src/test/groovy/datadog/communication/ddagent/DDAgentFeaturesDiscoveryTest.groovy @@ -15,7 +15,6 @@ import spock.lang.Shared import java.nio.file.Files import java.nio.file.Paths -import java.util.concurrent.CountDownLatch import static datadog.communication.ddagent.DDAgentFeaturesDiscovery.V01_DATASTREAMS_ENDPOINT import static datadog.communication.ddagent.DDAgentFeaturesDiscovery.V6_METRICS_ENDPOINT @@ -38,6 +37,7 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification { static final String INFO_WITHOUT_DATA_STREAMS_RESPONSE = loadJsonFile("agent-info-without-data-streams.json") static final String INFO_WITHOUT_DATA_STREAMS_STATE = Strings.sha256(INFO_WITHOUT_DATA_STREAMS_RESPONSE) static final String INFO_WITH_LONG_RUNNING_SPANS = loadJsonFile("agent-info-with-long-running-spans.json") + static final String INFO_WITH_TELEMETRY_PROXY_RESPONSE = loadJsonFile("agent-info-with-telemetry-proxy.json") static final String PROBE_STATE = "probestate" def "test parse /info response"() { @@ -62,6 +62,7 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification { features.supportsEvpProxy() features.getVersion() == "0.99.0" !features.supportsLongRunning() + !features.supportsTelemetryProxy() 0 * _ } @@ -89,6 +90,7 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification { features.supportsEvpProxy() features.getVersion() == "0.99.0" !features.supportsLongRunning() + !features.supportsTelemetryProxy() 0 * _ } @@ -384,17 +386,22 @@ class DDAgentFeaturesDiscoveryTest extends DDSpecification { // but we don't permit dropping anyway !(features as DroppingPolicy).active() features.state() == INFO_WITHOUT_METRICS_STATE + !features.supportsTelemetryProxy() 0 * _ } - def countingNotFound(Request request, CountDownLatch latch) { - latch.countDown() - return notFound(request) - } + def "test parse /info response with telemetry proxy"() { + setup: + OkHttpClient client = Mock(OkHttpClient) + DDAgentFeaturesDiscovery features = new DDAgentFeaturesDiscovery(client, monitoring, agentUrl, true, true) - def countingInfoResponse(Request request, String json, CountDownLatch latch) { - latch.countDown() - return infoResponse(request, json) + when: "/info available" + features.discover() + + then: + 1 * client.newCall(_) >> { Request request -> infoResponse(request, INFO_WITH_TELEMETRY_PROXY_RESPONSE) } + features.supportsTelemetryProxy() + 0 * _ } def infoResponse(Request request, String json) { diff --git a/communication/src/test/resources/agent-features/agent-info-with-telemetry-proxy.json b/communication/src/test/resources/agent-features/agent-info-with-telemetry-proxy.json new file mode 100644 index 00000000000..55e493e8316 --- /dev/null +++ b/communication/src/test/resources/agent-features/agent-info-with-telemetry-proxy.json @@ -0,0 +1,62 @@ +{ + "version": "0.99.0", + "git_commit": "fab047e10", + "build_date": "2020-12-04 15:57:06.74187 +0200 EET m=+0.029001792", + "endpoints": [ + "/v0.3/traces", + "/v0.3/services", + "/v0.4/traces", + "/v0.4/services", + "/v0.5/traces", + "/v0.6/stats", + "/profiling/v1/input", + "/telemetry/proxy/", + "/v0.1/pipeline_stats", + "/evp_proxy/v1/", + "/evp_proxy/v2/", + "/debugger/v1/input", + "/v0.7/config" + ], + "feature_flags": [ + "feature_flag" + ], + "config": { + "default_env": "prod", + "bucket_interval": 1000000000, + "extra_aggregators": [ + "agg:val" + ], + "extra_sample_rate": 2.4, + "target_tps": 11, + "max_eps": 12, + "receiver_port": 8111, + "receiver_socket": "/sock/path", + "connection_limit": 12, + "receiver_timeout": 100, + "max_request_bytes": 123, + "statsd_port": 123, + "max_memory": 1000000, + "max_cpu": 12345, + "analyzed_rate_by_service_legacy": { + "X": 1.2 + }, + "analyzed_spans_by_service": { + "X": { + "Y": 2.4 + } + }, + "obfuscation": { + "elastic_search": true, + "mongo": true, + "sql_exec_plan": true, + "sql_exec_plan_normalize": true, + "http": { + "remove_query_string": true, + "remove_path_digits": true + }, + "remove_stack_traces": false, + "redis": true, + "memcached": false + } + } +} diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index 90f1336a04c..9f27142f049 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -150,7 +150,13 @@ public static void start(final Instrumentation inst, final URL agentJarURL, Stri createAgentClassloader(agentJarURL); if (Platform.isNativeImageBuilder()) { + // these default services are not used during native-image builds + jmxFetchEnabled = false; + remoteConfigEnabled = false; + telemetryEnabled = false; + // apply trace instrumentation, but skip starting other services startDatadogAgent(inst); + StaticEventLogger.end("Agent.start"); return; } @@ -847,15 +853,29 @@ public void withTracer(TracerAPI tracer) { * on JFR. */ private static ProfilingContextIntegration createProfilingContextIntegration() { - if (Config.get().isProfilingEnabled() && !Platform.isWindows()) { - try { - return (ProfilingContextIntegration) - AGENT_CLASSLOADER - .loadClass("com.datadog.profiling.ddprof.DatadogProfilingIntegration") - .getDeclaredConstructor() - .newInstance(); - } catch (Throwable t) { - log.debug("Profiling context labeling not available. {}", t.getMessage()); + if (Config.get().isProfilingEnabled()) { + if (Config.get().isDatadogProfilerEnabled() && !Platform.isWindows()) { + try { + return (ProfilingContextIntegration) + AGENT_CLASSLOADER + .loadClass("com.datadog.profiling.ddprof.DatadogProfilingIntegration") + .getDeclaredConstructor() + .newInstance(); + } catch (Throwable t) { + log.debug("ddprof-based profiling context labeling not available. {}", t.getMessage()); + } + } + if (Config.get().isProfilingTimelineEventsEnabled()) { + // important: note that this will not initialise JFR until onStart is called + try { + return (ProfilingContextIntegration) + AGENT_CLASSLOADER + .loadClass("com.datadog.profiling.controller.openjdk.JFREventContextIntegration") + .getDeclaredConstructor() + .newInstance(); + } catch (Throwable t) { + log.debug("JFR event-based profiling context labeling not available. {}", t.getMessage()); + } } } return ProfilingContextIntegration.NoOp.INSTANCE; @@ -916,6 +936,7 @@ public void withTracer(TracerAPI tracer) { .getDeclaredConstructor() .newInstance()); } + tracer.getProfilingContext().onStart(); } catch (Throwable e) { if (e instanceof InvocationTargetException) { e = e.getCause(); diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecorator.java index 26f074a1548..08a04402e26 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecorator.java @@ -6,12 +6,35 @@ import datadog.trace.api.DDTags; import datadog.trace.api.UserEventTrackingMode; import datadog.trace.api.internal.TraceSegment; +import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import java.util.Map; import javax.annotation.Nonnull; public class AppSecUserEventDecorator { + public boolean isEnabled() { + if (!ActiveSubsystems.APPSEC_ACTIVE) { + return false; + } + UserEventTrackingMode mode = Config.get().getAppSecUserEventsTrackingMode(); + if (mode == DISABLED) { + return false; + } + return true; + } + + public void onUserNotFound() { + if (!isEnabled()) { + return; + } + TraceSegment segment = getSegment(); + if (segment == null) { + return; + } + segment.setTagTop("appsec.events.users.login.failure.usr.exists", false); + } + public void onLoginSuccess(String userId, Map metadata) { TraceSegment segment = getSegment(); if (segment == null) { @@ -24,7 +47,7 @@ public void onLoginSuccess(String userId, Map metadata) { onEvent(segment, "users.login.success", metadata); } - public void onLoginFailure(String userId, Map metadata, boolean userExists) { + public void onLoginFailure(String userId, Map metadata) { TraceSegment segment = getSegment(); if (segment == null) { return; @@ -33,7 +56,7 @@ public void onLoginFailure(String userId, Map metadata, boolean if (userId != null) { segment.setTagTop("appsec.events.users.login.failure.usr.id", userId); } - segment.setTagTop("appsec.events.users.login.failure.usr.exists", userExists); + onEvent(segment, "users.login.failure", metadata); } diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 777dfcacb65..334b2d665ba 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -143,7 +143,7 @@ public AgentSpan startSpan( } AgentPropagation.ContextVisitor getter = getter(); if (null != carrier && null != getter) { - tracer().getDataStreamsMonitoring().setCheckpoint(span, SERVER_PATHWAY_EDGE_TAGS, 0); + tracer().getDataStreamsMonitoring().setCheckpoint(span, SERVER_PATHWAY_EDGE_TAGS, 0, 0); } return span; } @@ -250,7 +250,7 @@ public AgentSpan onRequest( } String inferredAddressStr = null; - if (clientIpResolverEnabled) { + if (clientIpResolverEnabled && context != null) { InetAddress inferredAddress = ClientIpAddressResolver.resolve(context, span); // the peer address should be used if: // 1. the headers yield nothing, regardless of whether it is public or not @@ -269,6 +269,17 @@ public AgentSpan onRequest( inferredAddressStr = inferredAddress.getHostAddress(); span.setTag(Tags.HTTP_CLIENT_IP, inferredAddressStr); } + } else if (clientIpResolverEnabled && span.getLocalRootSpan() != span) { + // in this case context == null + // If there is no context we can't do anything but use the peer addr. + // Additionally, context == null arises on subspans for which the resolution + // likely already happened on the top span, so we don't need to do the resolution + // again. Instead, copy from the top span, should it exist + AgentSpan localRootSpan = span.getLocalRootSpan(); + Object clientIp = localRootSpan.getTag(Tags.HTTP_CLIENT_IP); + if (clientIp != null) { + span.setTag(Tags.HTTP_CLIENT_IP, clientIp); + } } if (peerIp != null) { diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java index 5fdbe0d6053..2bfd2de0f8d 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/DBInfo.java @@ -6,6 +6,7 @@ public class DBInfo { public static DBInfo DEFAULT = new Builder().type("database").build(); private final String type; private final String subtype; + private final boolean fullPropagationSupport; private final String url; private final String user; private final String instance; @@ -16,6 +17,7 @@ public class DBInfo { DBInfo( String type, String subtype, + boolean fullPropagationSupport, String url, String user, String instance, @@ -24,6 +26,7 @@ public class DBInfo { Integer port) { this.type = type; this.subtype = subtype; + this.fullPropagationSupport = fullPropagationSupport; this.url = url; this.user = user; this.instance = instance; @@ -35,6 +38,9 @@ public class DBInfo { public static class Builder { private String type; private String subtype; + // most DBs do support full propagation (inserting trace ID in query comments), so we default to + // true. See https://docs.datadoghq.com/database_monitoring/connect_dbm_and_apm + private boolean fullPropagationSupport = true; private String url; private String user; private String instance; @@ -47,6 +53,7 @@ public static class Builder { Builder( String type, String subtype, + boolean fullPropagationSupport, String url, String user, String instance, @@ -55,6 +62,7 @@ public static class Builder { Integer port) { this.type = type; this.subtype = subtype; + this.fullPropagationSupport = fullPropagationSupport; this.url = url; this.user = user; this.instance = instance; @@ -65,6 +73,9 @@ public static class Builder { public Builder type(String type) { this.type = type; + // Those DBs use the full text of the query including the comments as a cache key, + // so we disable full propagation support for them to avoid destroying the cache. + if (type.equals("oracle") || type.equals("sqlserver")) this.fullPropagationSupport = false; return this; } @@ -73,6 +84,11 @@ public Builder subtype(String subtype) { return this; } + public Builder fullPropagationSupport(boolean fullPropagationSupport) { + this.fullPropagationSupport = fullPropagationSupport; + return this; + } + public Builder url(String url) { this.url = url; return this; @@ -104,7 +120,7 @@ public Builder port(Integer port) { } public DBInfo build() { - return new DBInfo(type, subtype, url, user, instance, db, host, port); + return new DBInfo(type, subtype, fullPropagationSupport, url, user, instance, db, host, port); } } @@ -116,6 +132,10 @@ public String getSubtype() { return subtype; } + public boolean getFullPropagationSupport() { + return fullPropagationSupport; + } + public String getUrl() { return url; } @@ -141,7 +161,7 @@ public Integer getPort() { } public Builder toBuilder() { - return new Builder(type, subtype, url, user, instance, db, host, port); + return new Builder(type, subtype, fullPropagationSupport, url, user, instance, db, host, port); } @Override @@ -151,6 +171,7 @@ public boolean equals(Object o) { DBInfo dbInfo = (DBInfo) o; return Objects.equals(type, dbInfo.type) && Objects.equals(subtype, dbInfo.subtype) + && fullPropagationSupport == dbInfo.fullPropagationSupport && Objects.equals(url, dbInfo.url) && Objects.equals(user, dbInfo.user) && Objects.equals(instance, dbInfo.instance) @@ -161,6 +182,6 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(type, subtype, url, user, instance, db, host, port); + return Objects.hash(type, subtype, fullPropagationSupport, url, user, instance, db, host, port); } } diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jfr/InstrumentationBasedProfiling.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jfr/InstrumentationBasedProfiling.java index c27b73e85bf..846d8e381c3 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jfr/InstrumentationBasedProfiling.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jfr/InstrumentationBasedProfiling.java @@ -1,10 +1,15 @@ package datadog.trace.bootstrap.instrumentation.jfr; +import datadog.trace.api.Platform; + public final class InstrumentationBasedProfiling { private static volatile boolean isJFRReady; public static void enableInstrumentationBasedProfiling() { - isJFRReady = true; + // ignore the enablement when running in native image builder + if (!Platform.isNativeImageBuilder()) { + isJFRReady = true; + } } public static boolean isJFRReady() { diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/WindowSampler.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/WindowSampler.java index f48afc17856..f6489a29c2f 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/WindowSampler.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/WindowSampler.java @@ -13,10 +13,14 @@ public class WindowSampler { protected WindowSampler( Duration windowDuration, int samplesPerWindow, int lookback, Class eventType) { - sampler = new AdaptiveSampler(windowDuration, samplesPerWindow, lookback, 16); + sampler = new AdaptiveSampler(windowDuration, samplesPerWindow, lookback, 16, false); sampleType = EventType.getEventType(eventType); } + public void start() { + sampler.start(); + } + public boolean sample() { return sampleType.isEnabled() && sampler.sample(); } diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionProfiling.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionProfiling.java index 0cabe247278..d147017aedd 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionProfiling.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionProfiling.java @@ -42,13 +42,17 @@ private ExceptionProfiling(final Config config) { this.recordExceptionMessage = recordExceptionMessage; } - public ExceptionSampleEvent process(final Throwable t, final int stackDepth) { + public void start() { + sampler.start(); + } + + public ExceptionSampleEvent process(final Throwable t) { // always record the exception in histogram final boolean firstHit = histogram.record(t); final boolean sampled = sampler.sample(); if (firstHit || sampled) { - return new ExceptionSampleEvent(t, stackDepth, sampled, firstHit); + return new ExceptionSampleEvent(t, sampled, firstHit); } return null; } diff --git a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionSampleEvent.java b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionSampleEvent.java index 57de3176207..7dfbeaedb01 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionSampleEvent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java11/datadog/trace/bootstrap/instrumentation/jfr/exceptions/ExceptionSampleEvent.java @@ -18,10 +18,6 @@ public class ExceptionSampleEvent extends Event implements ContextualEvent { @Label("Exception message") private final String message; - /** JFR may truncate the stack trace - so store original length as well. */ - @Label("Exception stackdepth") - private final int stackDepth; - @Label("Sampled") private final boolean sampled; @@ -34,8 +30,7 @@ public class ExceptionSampleEvent extends Event implements ContextualEvent { @Label("Span Id") private long spanId; - public ExceptionSampleEvent( - Throwable e, final int stackDepth, boolean sampled, boolean firstOccurrence) { + public ExceptionSampleEvent(Throwable e, boolean sampled, boolean firstOccurrence) { /* * TODO: we should have some tests for this class. * Unfortunately at the moment this is not easily possible because we cannot build tests with groovy that @@ -44,7 +39,6 @@ public ExceptionSampleEvent( */ this.type = e.getClass().getName(); this.message = getMessage(e); - this.stackDepth = stackDepth; this.sampled = sampled; this.firstOccurrence = firstOccurrence; captureContext(); diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecoratorTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecoratorTest.groovy index da0f5887491..a97f9008b48 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecoratorTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/AppSecUserEventDecoratorTest.groovy @@ -1,6 +1,7 @@ package datadog.trace.bootstrap.instrumentation.decorator import datadog.trace.api.internal.TraceSegment +import datadog.trace.bootstrap.ActiveSubsystems import datadog.trace.test.util.DDSpecification import static datadog.trace.api.config.AppSecConfig.APPSEC_AUTOMATED_USER_EVENTS_TRACKING @@ -9,6 +10,17 @@ class AppSecUserEventDecoratorTest extends DDSpecification { def traceSegment = Mock(TraceSegment) + Boolean appsecActiveOriginal = null + + def setup() { + appsecActiveOriginal = ActiveSubsystems.APPSEC_ACTIVE + ActiveSubsystems.APPSEC_ACTIVE = true + } + + def cleanup() { + ActiveSubsystems.APPSEC_ACTIVE = appsecActiveOriginal + } + def "test onSignup [#mode]"() { setup: injectSysConfig(APPSEC_AUTOMATED_USER_EVENTS_TRACKING, mode) @@ -59,23 +71,62 @@ class AppSecUserEventDecoratorTest extends DDSpecification { def decorator = newDecorator() when: - decorator.onLoginFailure('user', ['key1': 'value1', 'key2': 'value2'], (boolean)userExists) + decorator.onLoginFailure('user', ['key1': 'value1', 'key2': 'value2']) then: 1 * traceSegment.setTagTop('_dd.appsec.events.users.login.failure.auto.mode', modeTag) 1 * traceSegment.setTagTop('appsec.events.users.login.failure.track', true, true) 1 * traceSegment.setTagTop('manual.keep', true) 1 * traceSegment.setTagTop('appsec.events.users.login.failure.usr.id', 'user') - 1 * traceSegment.setTagTop("appsec.events.users.login.failure.usr.exists", userExists) 1 * traceSegment.setTagTop('appsec.events.users.login.failure', ['key1':'value1', 'key2':'value2']) 0 * _ where: - mode | modeTag | userExists | description - 'safe' | 'SAFE' | true | 'with existing user' - 'safe' | 'SAFE' | false | 'user doesn\'t exist' - 'extended' | 'EXTENDED' | true | 'with existing user' - 'extended' | 'EXTENDED' | false | 'user doesn\'t exist' + mode | modeTag | description + 'safe' | 'SAFE' | 'with existing user' + 'safe' | 'SAFE' | 'user doesn\'t exist' + 'extended' | 'EXTENDED' | 'with existing user' + 'extended' | 'EXTENDED' | 'user doesn\'t exist' + } + + def "test onUserNotFound [#mode]"() { + setup: + injectSysConfig(APPSEC_AUTOMATED_USER_EVENTS_TRACKING, mode) + def decorator = newDecorator() + + when: + decorator.onUserNotFound() + + then: + 1 * traceSegment.setTagTop("appsec.events.users.login.failure.usr.exists", false) + 0 * _ + + where: + mode | modeTag + 'safe' | 'SAFE' + 'extended' | 'EXTENDED' + } + + def "test isEnabled (appsec = #appsec, mode = #mode)"() { + setup: + ActiveSubsystems.APPSEC_ACTIVE = appsec + injectSysConfig(APPSEC_AUTOMATED_USER_EVENTS_TRACKING, mode) + def decorator = newDecorator() + + when: + def enabled = decorator.isEnabled() + + then: + enabled == result + + where: + appsec | mode | result + false | "disabled" | false + false | "safe" | false + false | "extended" | false + true | "disabled" | false + true | "safe" | true + true | "extended" | true } def newDecorator() { diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy index 28379f84cb1..fcbba105a5a 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTest.groovy @@ -23,6 +23,7 @@ import datadog.trace.core.datastreams.DataStreamsMonitoring import java.util.function.Function import java.util.function.Supplier +import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_DECODED_RESOURCE_PRESERVE_SPACES import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_QUERY_STRING import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_RAW_RESOURCE import static datadog.trace.api.config.TraceInstrumentationConfig.HTTP_SERVER_TAG_QUERY_STRING @@ -63,6 +64,7 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { } else { 1 * this.span.getRequestContext() } + _ * this.span.getLocalRootSpan() >> this.span 0 * _ where: @@ -102,6 +104,7 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { 1 * this.span.setResourceName({ it as String == expectedPath }) } 1 * this.span.setTag(Tags.HTTP_METHOD, null) + _ * this.span.getLocalRootSpan() >> this.span 0 * _ where: @@ -141,18 +144,34 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { 2 * this.span.getRequestContext() 1 * this.span.setResourceName({ it as String == expectedResource }, ResourceNamePriorities.HTTP_PATH_NORMALIZER) 1 * this.span.setTag(Tags.HTTP_METHOD, null) + _ * this.span.getLocalRootSpan() >> this.span 0 * _ where: rawQuery | rawResource | url | expectedUrl | expectedQuery | expectedResource - false | false | "http://host/p%20ath?query%3F?" | "http://host/p ath" | "query??" | "/path" + false | false | "http://host/p%20ath?query%3F?" | "http://host/p ath" | "query??" | "/p ath" false | true | "http://host/p%20ath?query%3F?" | "http://host/p%20ath" | "query??" | "/p%20ath" - true | false | "http://host/p%20ath?query%3F?" | "http://host/p ath" | "query%3F?" | "/path" + true | false | "http://host/p%20ath?query%3F?" | "http://host/p ath" | "query%3F?" | "/p ath" true | true | "http://host/p%20ath?query%3F?" | "http://host/p%20ath" | "query%3F?" | "/p%20ath" req = [url: url == null ? null : new URI(url)] } + void 'url handling without space preservation'() { + setup: + injectSysConfig(HTTP_SERVER_RAW_RESOURCE, 'false') + injectSysConfig(HTTP_SERVER_DECODED_RESOURCE_PRESERVE_SPACES, 'false') + def decorator = newDecorator() + + when: + decorator.onRequest(this.span, null, [url: new URI('http://host/p%20ath')], null) + + then: + 1 * this.span.setResourceName({ it as String == '/path' }, ResourceNamePriorities.HTTP_PATH_NORMALIZER) + _ * this.span.getLocalRootSpan() >> this.span + _ * _ + } + def "test onConnection"() { setup: def ctx = Mock(AgentSpan.Context.Extracted) @@ -269,6 +288,7 @@ class HttpServerDecoratorTest extends ServerDecoratorTest { when: decorator.onRequest(this.span, [peerIp: '4.4.4.4'], null, ctx) + _ * this.span.getLocalRootSpan() >> this.span then: 2 * ctx.getXForwardedFor() >> '2.3.4.5' 1 * this.span.setTag(Tags.HTTP_CLIENT_IP, '2.3.4.5') diff --git a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index fe4673541e3..22bd6116cbd 100644 --- a/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -11,8 +11,8 @@ import datadog.trace.agent.tooling.usm.UsmExtractorImpl; import datadog.trace.agent.tooling.usm.UsmMessageFactoryImpl; import datadog.trace.api.InstrumenterConfig; -import datadog.trace.api.IntegrationsCollector; import datadog.trace.api.ProductActivation; +import datadog.trace.api.telemetry.IntegrationsCollector; import datadog.trace.bootstrap.FieldBackedContextAccessor; import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; import datadog.trace.util.AgentTaskScheduler; diff --git a/dd-java-agent/agent-ci-visibility/build.gradle b/dd-java-agent/agent-ci-visibility/build.gradle index 9917f17cbdb..a22f1a052f2 100644 --- a/dd-java-agent/agent-ci-visibility/build.gradle +++ b/dd-java-agent/agent-ci-visibility/build.gradle @@ -27,11 +27,12 @@ excludedClassesCoverage += [ "datadog.trace.civisibility.ci.CIInfo", "datadog.trace.civisibility.ci.CIInfo.Builder", "datadog.trace.civisibility.communication.*", - "datadog.trace.civisibility.config.JvmInfoFactory", + "datadog.trace.civisibility.config.CachingJvmInfoFactory", + "datadog.trace.civisibility.config.JvmInfoFactoryImpl", + "datadog.trace.civisibility.config.JvmInfoFactoryImpl.JvmVersionOutputParser", "datadog.trace.civisibility.config.ConfigurationApi", "datadog.trace.civisibility.config.ModuleExecutionSettingsFactory", "datadog.trace.civisibility.config.JvmInfo", - "datadog.trace.civisibility.config.JvmInfoFactory.JvmVersionOutputParser", "datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory", "datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory.Key", "datadog.trace.civisibility.config.CiVisibilitySettings", @@ -67,12 +68,16 @@ excludedClassesCoverage += [ "datadog.trace.civisibility.ipc.SignalClient.Factory", "datadog.trace.civisibility.ipc.SkippableTestsResponse", "datadog.trace.civisibility.ipc.TestFramework", - "datadog.trace.civisibility.source.MethodLinesResolver.MethodLines", "datadog.trace.civisibility.source.index.PackageTree.Node", "datadog.trace.civisibility.source.index.RepoIndex", "datadog.trace.civisibility.source.index.RepoIndexBuilder.RepoIndexingFileVisitor", "datadog.trace.civisibility.source.index.RepoIndexFetcher", "datadog.trace.civisibility.source.index.RepoIndexSourcePathResolver", + "datadog.trace.civisibility.source.BestEffortSourcePathResolver", + "datadog.trace.civisibility.source.MethodLinesResolver.MethodLines", + "datadog.trace.civisibility.source.NoOpSourcePathResolver", + "datadog.trace.civisibility.source.SourcePathResolver.1", + "datadog.trace.civisibility.source.Utils", "datadog.trace.civisibility.utils.ShellCommandExecutor", "datadog.trace.civisibility.utils.ShellCommandExecutor.OutputParser", "datadog.trace.civisibility.utils.SpanUtils" @@ -95,6 +100,12 @@ dependencies { testFixturesApi project(':dd-java-agent:testing') testFixturesApi project(':utils:test-utils') + + testFixturesApi group: 'org.skyscreamer', name: 'jsonassert', version: '1.5.1' + testFixturesApi group: 'org.freemarker', name: 'freemarker', version: '2.3.30' + testFixturesApi group: 'com.jayway.jsonpath', name: 'json-path', version: '2.8.0' + testFixturesApi group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.0' + testFixturesApi group: 'org.msgpack', name: 'jackson-dataformat-msgpack', version: '0.9.6' } shadowJar { diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java index 7f0c82c9304..dfcc42e0006 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java @@ -1,6 +1,7 @@ package datadog.trace.civisibility; import datadog.communication.ddagent.SharedCommunicationObjects; +import datadog.communication.ddagent.TracerVersion; import datadog.trace.api.Config; import datadog.trace.api.civisibility.CIVisibility; import datadog.trace.api.civisibility.InstrumentationBridge; @@ -17,10 +18,12 @@ import datadog.trace.civisibility.codeowners.CodeownersProvider; import datadog.trace.civisibility.communication.BackendApi; import datadog.trace.civisibility.communication.BackendApiFactory; +import datadog.trace.civisibility.config.CachingJvmInfoFactory; import datadog.trace.civisibility.config.CachingModuleExecutionSettingsFactory; import datadog.trace.civisibility.config.ConfigurationApi; import datadog.trace.civisibility.config.ConfigurationApiImpl; import datadog.trace.civisibility.config.JvmInfoFactory; +import datadog.trace.civisibility.config.JvmInfoFactoryImpl; import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory; import datadog.trace.civisibility.config.ModuleExecutionSettingsFactoryImpl; import datadog.trace.civisibility.coverage.CoverageProbeStoreFactory; @@ -46,13 +49,20 @@ import datadog.trace.civisibility.source.CompilerAidedMethodLinesResolver; import datadog.trace.civisibility.source.CompilerAidedSourcePathResolver; import datadog.trace.civisibility.source.MethodLinesResolver; +import datadog.trace.civisibility.source.NoOpSourcePathResolver; import datadog.trace.civisibility.source.SourcePathResolver; +import datadog.trace.civisibility.source.index.ConventionBasedResourceResolver; +import datadog.trace.civisibility.source.index.PackageResolver; +import datadog.trace.civisibility.source.index.PackageResolverImpl; import datadog.trace.civisibility.source.index.RepoIndexBuilder; import datadog.trace.civisibility.source.index.RepoIndexFetcher; import datadog.trace.civisibility.source.index.RepoIndexProvider; import datadog.trace.civisibility.source.index.RepoIndexSourcePathResolver; +import datadog.trace.civisibility.source.index.ResourceResolver; import datadog.trace.util.Strings; +import datadog.trace.util.throwable.FatalAgentMisconfigurationError; import java.net.InetSocketAddress; +import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; @@ -74,6 +84,19 @@ public static void start(SharedCommunicationObjects sco) { return; } + String injectedTracerVersion = config.getCiVisibilityInjectedTracerVersion(); + if (injectedTracerVersion != null + && !injectedTracerVersion.equals(TracerVersion.TRACER_VERSION)) { + throw new FatalAgentMisconfigurationError( + "Running JVM with tracer version " + + TracerVersion.TRACER_VERSION + + " however parent process attempted to inject " + + injectedTracerVersion + + ". Do not inject the tracer into the forked JVMs manually, or ensure the manually injected version is the same as the one injected automatically"); + } + + sco.createRemaining(config); + GitClient.Factory gitClientFactory = buildGitClientFactory(config); CoverageProbeStoreFactory coverageProbeStoreFactory = buildTestProbesFactory(config); @@ -117,10 +140,11 @@ private static BuildEventsHandler.Factory buildEventsHandlerFactory( DDBuildSystemSession.Factory sessionFactory = buildSystemSessionFactory( config, sco, gitInfoProvider, coverageProbeStoreFactory, gitClientFactory); + JvmInfoFactory jvmInfoFactory = new CachingJvmInfoFactory(config, new JvmInfoFactoryImpl()); return new BuildEventsHandler.Factory() { @Override public BuildEventsHandler create() { - return new BuildEventsHandlerImpl<>(sessionFactory, new JvmInfoFactory()); + return new BuildEventsHandlerImpl<>(sessionFactory, jvmInfoFactory); } }; } @@ -132,8 +156,6 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( CoverageProbeStoreFactory coverageProbeStoreFactory, GitClient.Factory gitClientFactory) { BackendApiFactory backendApiFactory = new BackendApiFactory(config, sco); - BackendApi backendApi = backendApiFactory.createBackendApi(); - return (String projectName, Path projectRoot, String startCommand, @@ -150,9 +172,15 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( CIInfo ciInfo = ciProviderInfo.buildCIInfo(); String repoRoot = ciInfo.getCiWorkspace(); - RepoIndexProvider indexProvider = - new RepoIndexBuilder(projectRoot.toString(), FileSystems.getDefault()); - SourcePathResolver sourcePathResolver = getSourcePathResolver(repoRoot, indexProvider); + FileSystem fileSystem = FileSystems.getDefault(); + PackageResolver packageResolver = new PackageResolverImpl(fileSystem); + ResourceResolver resourceResolver = + new ConventionBasedResourceResolver( + fileSystem, config.getCiVisibilityResourceFolderNames()); + RepoIndexBuilder indexBuilder = + new RepoIndexBuilder(repoRoot, packageResolver, resourceResolver, fileSystem); + + SourcePathResolver sourcePathResolver = getSourcePathResolver(repoRoot, indexBuilder); Codeowners codeowners = getCodeowners(repoRoot); MethodLinesResolver methodLinesResolver = @@ -163,11 +191,12 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( TestDecorator testDecorator = new TestDecoratorImpl(buildSystemName, ciTags); TestModuleRegistry testModuleRegistry = new TestModuleRegistry(); + BackendApi backendApi = backendApiFactory.createBackendApi(); GitDataUploader gitDataUploader = buildGitDataUploader(config, gitInfoProvider, gitClientFactory, backendApi, repoRoot); ModuleExecutionSettingsFactory moduleExecutionSettingsFactory = buildModuleExecutionSettingsFactory( - config, backendApi, gitDataUploader, indexProvider, repoRoot); + config, backendApi, gitDataUploader, indexBuilder, repoRoot); String signalServerHost = config.getCiVisibilitySignalServerHost(); int signalServerPort = config.getCiVisibilitySignalServerPort(); @@ -176,7 +205,6 @@ private static DDBuildSystemSession.Factory buildSystemSessionFactory( // only start Git data upload in parent process gitDataUploader.startOrObserveGitDataUpload(); - RepoIndexBuilder indexBuilder = new RepoIndexBuilder(repoRoot, FileSystems.getDefault()); return new DDBuildSystemSessionImpl( projectName, repoRoot, @@ -210,7 +238,9 @@ private static TestEventsHandler.Factory testEventsHandlerFactory( CIInfo ciInfo = ciProviderInfo.buildCIInfo(); String repoRoot = ciInfo.getCiWorkspace(); String moduleName = - (repoRoot != null) ? Paths.get(repoRoot).relativize(path).toString() : path.toString(); + repoRoot != null && path.startsWith(repoRoot) + ? Paths.get(repoRoot).relativize(path).toString() + : config.getServiceName(); DDTestFrameworkSession testSession = sessionFactory.startSession(moduleName, path, component, null); @@ -226,8 +256,6 @@ private static DDTestFrameworkSession.Factory testFrameworkSessionFactory( CoverageProbeStoreFactory coverageProbeStoreFactory, GitClient.Factory gitClientFactory) { BackendApiFactory backendApiFactory = new BackendApiFactory(config, sco); - BackendApi backendApi = backendApiFactory.createBackendApi(); - return (String projectName, Path projectRoot, String component, Long startTime) -> { CIProviderInfoFactory ciProviderInfoFactory = new CIProviderInfoFactory(config); CIProviderInfo ciProviderInfo = ciProviderInfoFactory.createCIProviderInfo(projectRoot); @@ -273,9 +301,17 @@ private static DDTestFrameworkSession.Factory testFrameworkSessionFactory( // either we are in the build system // or we are in the tests JVM and the build system is not instrumented if (parentProcessSessionId == null || parentProcessModuleId == null) { + FileSystem fileSystem = FileSystems.getDefault(); + PackageResolver packageResolver = new PackageResolverImpl(fileSystem); + ResourceResolver resourceResolver = + new ConventionBasedResourceResolver( + fileSystem, config.getCiVisibilityResourceFolderNames()); + RepoIndexProvider indexProvider = + new RepoIndexBuilder(repoRoot, packageResolver, resourceResolver, fileSystem); + + BackendApi backendApi = backendApiFactory.createBackendApi(); GitDataUploader gitDataUploader = buildGitDataUploader(config, gitInfoProvider, gitClientFactory, backendApi, repoRoot); - RepoIndexProvider indexProvider = new RepoIndexBuilder(repoRoot, FileSystems.getDefault()); ModuleExecutionSettingsFactory moduleExecutionSettingsFactory = buildModuleExecutionSettingsFactory( config, backendApi, gitDataUploader, indexProvider, repoRoot); @@ -346,7 +382,14 @@ private static CIVisibility.SessionFactory apiSessionFactory( Map ciTags = new CITagsProvider().getCiTags(ciInfo); TestDecorator testDecorator = new TestDecoratorImpl(component, ciTags); - RepoIndexProvider indexProvider = new RepoIndexBuilder(repoRoot, FileSystems.getDefault()); + + FileSystem fileSystem = FileSystems.getDefault(); + PackageResolver packageResolver = new PackageResolverImpl(fileSystem); + ResourceResolver resourceResolver = + new ConventionBasedResourceResolver( + fileSystem, config.getCiVisibilityResourceFolderNames()); + RepoIndexProvider indexProvider = + new RepoIndexBuilder(repoRoot, packageResolver, resourceResolver, fileSystem); SourcePathResolver sourcePathResolver = getSourcePathResolver(repoRoot, indexProvider); return new DDTestSessionImpl( @@ -418,7 +461,7 @@ private static SourcePathResolver getSourcePathResolver( return new BestEffortSourcePathResolver( new CompilerAidedSourcePathResolver(repoRoot), indexSourcePathResolver); } else { - return clazz -> null; + return NoOpSourcePathResolver.INSTANCE; } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDBuildSystemModuleImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDBuildSystemModuleImpl.java index e1c2a0714a5..88e19ea7d1c 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDBuildSystemModuleImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDBuildSystemModuleImpl.java @@ -10,7 +10,6 @@ import datadog.trace.civisibility.coverage.CoverageUtils; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.ipc.ModuleExecutionResult; -import datadog.trace.civisibility.ipc.TestFramework; import datadog.trace.civisibility.source.MethodLinesResolver; import datadog.trace.civisibility.source.SourcePathResolver; import datadog.trace.civisibility.source.index.RepoIndexProvider; @@ -141,10 +140,7 @@ public void onModuleExecutionResultReceived(ModuleExecutionResult result) { } } - for (TestFramework testFramework : result.getTestFrameworks()) { - SpanUtils.mergeTag(span, Tags.TEST_FRAMEWORK, testFramework.getName()); - SpanUtils.mergeTag(span, Tags.TEST_FRAMEWORK_VERSION, testFramework.getVersion()); - } + SpanUtils.mergeTestFrameworks(span, result.getTestFrameworks()); } @Override diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkModuleProxy.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkModuleProxy.java index f6cf2224685..c7ab6cd7a11 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkModuleProxy.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkModuleProxy.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; import javax.annotation.Nullable; @@ -132,7 +133,7 @@ private void sendModuleExecutionResult() { coverageEnabled, itrEnabled, testsSkippedTotal, - testFrameworks, + new TreeSet<>(testFrameworks), coverageData); try (SignalClient signalClient = new SignalClient(signalServerAddress)) { diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkSessionImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkSessionImpl.java index 237c6ea2fb3..354a1ce688b 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkSessionImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestFrameworkSessionImpl.java @@ -1,6 +1,9 @@ package datadog.trace.civisibility; import datadog.trace.api.Config; +import datadog.trace.api.DDTags; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.civisibility.codeowners.Codeowners; import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory; import datadog.trace.civisibility.coverage.CoverageProbeStoreFactory; @@ -56,6 +59,18 @@ public DDTestFrameworkModuleImpl testModuleStart(String moduleName, @Nullable Lo methodLinesResolver, coverageProbeStoreFactory, moduleExecutionSettingsFactory, - SpanUtils.propagateCiVisibilityTagsTo(span)); + this::propagateModuleTags); + } + + private void propagateModuleTags(AgentSpan moduleSpan) { + SpanUtils.propagateCiVisibilityTags(span, moduleSpan); + SpanUtils.propagateTags( + span, + moduleSpan, + Tags.TEST_CODE_COVERAGE_ENABLED, + Tags.TEST_ITR_TESTS_SKIPPING_ENABLED, + Tags.TEST_ITR_TESTS_SKIPPING_TYPE, + Tags.TEST_ITR_TESTS_SKIPPING_COUNT, + DDTags.CI_ITR_TESTS_SKIPPED); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/CIInfo.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/CIInfo.java index 1220f2b6995..e66fae7c8cf 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/CIInfo.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/CIInfo.java @@ -1,5 +1,7 @@ package datadog.trace.civisibility.ci; +import static datadog.trace.api.git.GitUtils.filterSensitiveInfo; + import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -87,7 +89,7 @@ public Builder ciEnvVars(String... ciEnvVarKeysArray) { ciEnvVars = new HashMap<>(); for (String ciEnvVarKey : ciEnvVarKeysArray) { - final String envVarVal = System.getenv(ciEnvVarKey); + final String envVarVal = filterSensitiveInfo(System.getenv(ciEnvVarKey)); if (envVarVal != null && !envVarVal.isEmpty()) { ciEnvVars.put(ciEnvVarKey, envVarVal); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/GithubActionsInfo.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/GithubActionsInfo.java index 2dabe00a5e4..3779ac9930d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/GithubActionsInfo.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/GithubActionsInfo.java @@ -1,5 +1,6 @@ package datadog.trace.civisibility.ci; +import static datadog.trace.api.git.GitUtils.filterSensitiveInfo; import static datadog.trace.api.git.GitUtils.isTagReference; import static datadog.trace.api.git.GitUtils.normalizeBranch; import static datadog.trace.api.git.GitUtils.normalizeTag; @@ -28,7 +29,8 @@ class GithubActionsInfo implements CIProviderInfo { @Override public GitInfo buildCIGitInfo() { return new GitInfo( - buildGitRepositoryUrl(System.getenv(GHACTIONS_URL), System.getenv(GHACTIONS_REPOSITORY)), + buildGitRepositoryUrl( + filterSensitiveInfo(System.getenv(GHACTIONS_URL)), System.getenv(GHACTIONS_REPOSITORY)), buildGitBranch(), buildGitTag(), new CommitInfo(System.getenv(GHACTIONS_SHA))); @@ -38,13 +40,13 @@ public GitInfo buildCIGitInfo() { public CIInfo buildCIInfo() { final String pipelineUrl = buildPipelineUrl( - System.getenv(GHACTIONS_URL), + filterSensitiveInfo(System.getenv(GHACTIONS_URL)), System.getenv(GHACTIONS_REPOSITORY), System.getenv(GHACTIONS_PIPELINE_ID), System.getenv(GHACTIONS_PIPELINE_RETRY)); final String jobUrl = buildJobUrl( - System.getenv(GHACTIONS_URL), + filterSensitiveInfo(System.getenv(GHACTIONS_URL)), System.getenv(GHACTIONS_REPOSITORY), System.getenv(GHACTIONS_SHA)); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/UnknownCIInfo.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/UnknownCIInfo.java index 1e16adcad00..c5a1c2e4274 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/UnknownCIInfo.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ci/UnknownCIInfo.java @@ -4,6 +4,8 @@ import datadog.trace.api.git.GitInfo; import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class is the strategy to use when the CI provider used to execute the tests cannot be @@ -21,6 +23,8 @@ */ class UnknownCIInfo implements CIProviderInfo { + private static final Logger LOGGER = LoggerFactory.getLogger(UnknownCIInfo.class); + public static final String UNKNOWN_PROVIDER_NAME = "unknown"; private final String targetFolder; @@ -42,11 +46,17 @@ public GitInfo buildCIGitInfo() { @Override public CIInfo buildCIInfo() { - final Path workspace = findParentPathBackwards(getCurrentPath(), getTargetFolder(), true); + Path workspace = findParentPathBackwards(getCurrentPath(), getTargetFolder(), true); if (workspace == null) { return CIInfo.NOOP; } + try { + workspace = workspace.toRealPath(); + } catch (Exception e) { + LOGGER.debug("Could not get real path for workspace folder {}", workspace, e); + } + return CIInfo.builder().ciWorkspace(workspace.toAbsolutePath().toString()).build(); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java index c79fb8d65ab..934e68868ee 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/BackendApiFactory.java @@ -23,7 +23,6 @@ public BackendApiFactory(Config config, SharedCommunicationObjects sharedCommuni } public @Nullable BackendApi createBackendApi() { - long timeoutMillis = config.getCiVisibilityBackendApiTimeoutMillis(); HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0); if (config.isCiVisibilityAgentlessEnabled()) { @@ -33,15 +32,20 @@ public BackendApiFactory(Config config, SharedCommunicationObjects sharedCommuni throw new FatalAgentMisconfigurationError( "Agentless mode is enabled and api key is not set. Please set application key"); } - return new IntakeApi(site, apiKey, timeoutMillis, retryPolicyFactory); + long timeoutMillis = config.getCiVisibilityBackendApiTimeoutMillis(); + String traceId = config.getIdGenerationStrategy().generateTraceId().toString(); + return new IntakeApi(site, apiKey, traceId, timeoutMillis, retryPolicyFactory); } DDAgentFeaturesDiscovery featuresDiscovery = sharedCommunicationObjects.featuresDiscovery(config); + featuresDiscovery.discoverIfOutdated(); if (featuresDiscovery.supportsEvpProxy()) { + String traceId = config.getIdGenerationStrategy().generateTraceId().toString(); String evpProxyEndpoint = featuresDiscovery.getEvpProxyEndpoint(); HttpUrl evpProxyUrl = sharedCommunicationObjects.agentUrl.resolve(evpProxyEndpoint); - return new EvpProxyApi(evpProxyUrl, timeoutMillis, retryPolicyFactory); + return new EvpProxyApi( + traceId, evpProxyUrl, retryPolicyFactory, sharedCommunicationObjects.okHttpClient); } log.warn( diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java index 589c0811f22..2329b86b061 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/EvpProxyApi.java @@ -19,17 +19,24 @@ public class EvpProxyApi implements BackendApi { private static final String API_VERSION = "v2"; private static final String X_DATADOG_EVP_SUBDOMAIN_HEADER = "X-Datadog-EVP-Subdomain"; + private static final String X_DATADOG_TRACE_ID_HEADER = "x-datadog-trace-id"; + private static final String X_DATADOG_PARENT_ID_HEADER = "x-datadog-parent-id"; private static final String API_SUBDOMAIN = "api"; + private final String traceId; private final HttpRetryPolicy.Factory retryPolicyFactory; private final HttpUrl evpProxyUrl; private final OkHttpClient httpClient; public EvpProxyApi( - HttpUrl evpProxyUrl, long timeoutMillis, HttpRetryPolicy.Factory retryPolicyFactory) { + String traceId, + HttpUrl evpProxyUrl, + HttpRetryPolicy.Factory retryPolicyFactory, + OkHttpClient httpClient) { + this.traceId = traceId; this.evpProxyUrl = evpProxyUrl.resolve(String.format("api/%s/", API_VERSION)); this.retryPolicyFactory = retryPolicyFactory; - httpClient = OkHttpUtils.buildHttpClient(evpProxyUrl, timeoutMillis); + this.httpClient = httpClient; } @Override @@ -41,6 +48,8 @@ public T post( new Request.Builder() .url(url) .addHeader(X_DATADOG_EVP_SUBDOMAIN_HEADER, API_SUBDOMAIN) + .addHeader(X_DATADOG_TRACE_ID_HEADER, traceId) + .addHeader(X_DATADOG_PARENT_ID_HEADER, traceId) .post(requestBody) .build(); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java index d8617376b9c..55728a277d2 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/communication/IntakeApi.java @@ -20,15 +20,23 @@ public class IntakeApi implements BackendApi { private static final String API_VERSION = "v2"; private static final String DD_API_KEY_HEADER = "dd-api-key"; + private static final String X_DATADOG_TRACE_ID_HEADER = "x-datadog-trace-id"; + private static final String X_DATADOG_PARENT_ID_HEADER = "x-datadog-parent-id"; private final String apiKey; + private final String traceId; private final HttpRetryPolicy.Factory retryPolicyFactory; private final HttpUrl hostUrl; private final OkHttpClient httpClient; public IntakeApi( - String site, String apiKey, long timeoutMillis, HttpRetryPolicy.Factory retryPolicyFactory) { + String site, + String apiKey, + String traceId, + long timeoutMillis, + HttpRetryPolicy.Factory retryPolicyFactory) { this.apiKey = apiKey; + this.traceId = traceId; this.retryPolicyFactory = retryPolicyFactory; final String ciVisibilityAgentlessUrlStr = Config.get().getCiVisibilityAgentlessUrl(); @@ -47,7 +55,12 @@ public T post( throws IOException { HttpUrl url = hostUrl.resolve(uri); Request.Builder requestBuilder = - new Request.Builder().url(url).post(requestBody).addHeader(DD_API_KEY_HEADER, apiKey); + new Request.Builder() + .url(url) + .post(requestBody) + .addHeader(DD_API_KEY_HEADER, apiKey) + .addHeader(X_DATADOG_TRACE_ID_HEADER, traceId) + .addHeader(X_DATADOG_PARENT_ID_HEADER, traceId); Request request = requestBuilder.build(); HttpRetryPolicy retryPolicy = retryPolicyFactory.create(); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java new file mode 100644 index 00000000000..62204a42aec --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CachingJvmInfoFactory.java @@ -0,0 +1,23 @@ +package datadog.trace.civisibility.config; + +import datadog.trace.api.Config; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; +import java.nio.file.Path; + +public class CachingJvmInfoFactory implements JvmInfoFactory { + + private final DDCache cache; + private final JvmInfoFactoryImpl delegate; + + public CachingJvmInfoFactory(Config config, JvmInfoFactoryImpl delegate) { + this.delegate = delegate; + this.cache = + DDCaches.newFixedSizeCache(config.getCiVisibilityModuleExecutionSettingsCacheSize()); + } + + @Override + public JvmInfo getJvmInfo(Path jvmExecutablePath) { + return cache.computeIfAbsent(jvmExecutablePath, delegate::getJvmInfo); + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java index 5d3566a1bb3..1f4c3b512ef 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java @@ -6,10 +6,12 @@ public class CiVisibilitySettings { private final boolean code_coverage; private final boolean tests_skipping; + private final boolean require_git; - public CiVisibilitySettings(boolean code_coverage, boolean tests_skipping) { + public CiVisibilitySettings(boolean code_coverage, boolean tests_skipping, boolean require_git) { this.code_coverage = code_coverage; this.tests_skipping = tests_skipping; + this.require_git = require_git; } public boolean isCodeCoverageEnabled() { @@ -20,6 +22,10 @@ public boolean isTestsSkippingEnabled() { return tests_skipping; } + public boolean isGitUploadRequired() { + return require_git; + } + public interface Factory { CiVisibilitySettings create(Path path); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java index ead6ce0a6ca..80fda0022d0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java @@ -11,7 +11,7 @@ public interface ConfigurationApi { new ConfigurationApi() { @Override public CiVisibilitySettings getSettings(TracerEnvironment tracerEnvironment) { - return new CiVisibilitySettings(false, false); + return new CiVisibilitySettings(false, false, false); } @Override diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java index b1cfd999ef4..49c861a466a 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationsJson.java @@ -4,6 +4,7 @@ import com.squareup.moshi.Json; import com.squareup.moshi.ToJson; import datadog.trace.api.civisibility.config.Configurations; +import java.util.Map; public final class ConfigurationsJson { @Json(name = "os.platform") @@ -33,6 +34,9 @@ public final class ConfigurationsJson { @Json(name = "test.bundle") private final String testBundle; + @Json(name = "custom") + private final Map custom; + public ConfigurationsJson( String osPlatform, String osArchitecture, @@ -41,7 +45,8 @@ public ConfigurationsJson( String runtimeVersion, String runtimeVendor, String runtimeArchitecture, - String testBundle) { + String testBundle, + Map custom) { this.osPlatform = osPlatform; osArch = osArchitecture; this.osArchitecture = osArchitecture; @@ -51,6 +56,7 @@ public ConfigurationsJson( this.runtimeVendor = runtimeVendor; this.runtimeArchitecture = runtimeArchitecture; this.testBundle = testBundle; + this.custom = custom; } public static final class ConfigurationsJsonAdapter { @@ -64,7 +70,8 @@ public Configurations fromJson(ConfigurationsJson configurationsJson) { configurationsJson.runtimeVersion, configurationsJson.runtimeVendor, configurationsJson.runtimeArchitecture, - configurationsJson.testBundle); + configurationsJson.testBundle, + configurationsJson.custom); } @ToJson @@ -77,7 +84,8 @@ public ConfigurationsJson toJson(Configurations configurations) { configurations.getRuntimeVersion(), configurations.getRuntimeVendor(), configurations.getRuntimeArchitecture(), - configurations.getTestBundle()); + configurations.getTestBundle(), + configurations.getCustom()); } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java index 6a62ce3927d..99a26b3a885 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactory.java @@ -1,89 +1,7 @@ package datadog.trace.civisibility.config; -import datadog.trace.civisibility.utils.ShellCommandExecutor; -import datadog.trace.util.ProcessUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; import java.nio.file.Path; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -public class JvmInfoFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(JvmInfoFactory.class); - - private static final int JVM_VERSION_LAUNCH_TIMEOUT = 5_000; - - // provide some confidence - public JvmInfo getJvmInfo(Path jvmExecutablePath) { - String currentJvm = - ProcessUtils.getCurrentJvmPath(); // might be home dir or full executable path - // if we cannot determine forked JVM, - // we assume it is the same as current one, - // which is the most common case - if (jvmExecutablePath == null - || currentJvm != null && jvmExecutablePath.startsWith(currentJvm)) { - return JvmInfo.CURRENT_JVM; - } else { - return doGetJvmInfo(jvmExecutablePath); - } - } - - static JvmInfo doGetJvmInfo(Path jvmExecutablePath) { - Path jvmExecutableFolder = jvmExecutablePath.getParent(); - ShellCommandExecutor commandExecutor = - new ShellCommandExecutor(jvmExecutableFolder.toFile(), JVM_VERSION_LAUNCH_TIMEOUT); - try { - return commandExecutor.executeCommandReadingError( - new JvmVersionOutputParser(), "./java", "-XshowSettings:properties", "-version"); - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.warn( - "Interrupted while waiting for JVM runtime info for {}, assuming {}", - jvmExecutablePath, - JvmInfo.CURRENT_JVM); - return JvmInfo.CURRENT_JVM; - - } catch (Exception e) { - LOGGER.warn( - "Could not determine JVM runtime info for {}, assuming {}", - jvmExecutablePath, - JvmInfo.CURRENT_JVM, - e); - return JvmInfo.CURRENT_JVM; - } - } - - private static final class JvmVersionOutputParser - implements ShellCommandExecutor.OutputParser { - @Override - public JvmInfo parse(InputStream inputStream) throws IOException { - String name = null; - String version = null; - String vendor = null; - - BufferedReader bis = - new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); - String line; - while ((line = bis.readLine()) != null) { - if (line.contains("java.runtime.name ")) { - name = getPropertyValue(line); - } else if (line.contains("java.version ")) { - version = getPropertyValue(line); - } else if (line.contains("java.vendor ")) { - vendor = getPropertyValue(line); - } - } - return new JvmInfo(name, version, vendor); - } - - private String getPropertyValue(String line) { - // format of the input is: " property.name = propertyValue" - return line.substring(line.indexOf('=') + 2); - } - } +public interface JvmInfoFactory { + JvmInfo getJvmInfo(Path jvmExecutablePath); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java new file mode 100644 index 00000000000..0a3a4a28519 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/JvmInfoFactoryImpl.java @@ -0,0 +1,89 @@ +package datadog.trace.civisibility.config; + +import datadog.trace.civisibility.utils.ShellCommandExecutor; +import datadog.trace.util.ProcessUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JvmInfoFactoryImpl implements JvmInfoFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(JvmInfoFactoryImpl.class); + + private static final int JVM_VERSION_LAUNCH_TIMEOUT = 5_000; + + @Override + public JvmInfo getJvmInfo(Path jvmExecutablePath) { + String currentJvm = + ProcessUtils.getCurrentJvmPath(); // might be home dir or full executable path + // if we cannot determine forked JVM, + // we assume it is the same as current one, + // which is the most common case + if (jvmExecutablePath == null + || currentJvm != null && jvmExecutablePath.startsWith(currentJvm)) { + return JvmInfo.CURRENT_JVM; + } else { + return doGetJvmInfo(jvmExecutablePath); + } + } + + static JvmInfo doGetJvmInfo(Path jvmExecutablePath) { + Path jvmExecutableFolder = jvmExecutablePath.getParent(); + ShellCommandExecutor commandExecutor = + new ShellCommandExecutor(jvmExecutableFolder.toFile(), JVM_VERSION_LAUNCH_TIMEOUT); + try { + return commandExecutor.executeCommandReadingError( + new JvmVersionOutputParser(), "./java", "-XshowSettings:properties", "-version"); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.warn( + "Interrupted while waiting for JVM runtime info for {}, assuming {}", + jvmExecutablePath, + JvmInfo.CURRENT_JVM); + return JvmInfo.CURRENT_JVM; + + } catch (Exception e) { + LOGGER.warn( + "Could not determine JVM runtime info for {}, assuming {}", + jvmExecutablePath, + JvmInfo.CURRENT_JVM, + e); + return JvmInfo.CURRENT_JVM; + } + } + + private static final class JvmVersionOutputParser + implements ShellCommandExecutor.OutputParser { + @Override + public JvmInfo parse(InputStream inputStream) throws IOException { + String name = null; + String version = null; + String vendor = null; + + BufferedReader bis = + new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + String line; + while ((line = bis.readLine()) != null) { + if (line.contains("java.runtime.name ")) { + name = getPropertyValue(line); + } else if (line.contains("java.version ")) { + version = getPropertyValue(line); + } else if (line.contains("java.vendor ")) { + vendor = getPropertyValue(line); + } + } + return new JvmInfo(name, version, vendor); + } + + private String getPropertyValue(String line) { + // format of the input is: " property.name = propertyValue" + return line.substring(line.indexOf('=') + 2); + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java index 6122ac1aabe..c4ead81887d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ModuleExecutionSettingsFactoryImpl.java @@ -1,5 +1,6 @@ package datadog.trace.civisibility.config; +import datadog.communication.ddagent.TracerVersion; import datadog.trace.api.Config; import datadog.trace.api.civisibility.config.Configurations; import datadog.trace.api.civisibility.config.ModuleExecutionSettings; @@ -31,6 +32,7 @@ public class ModuleExecutionSettingsFactoryImpl implements ModuleExecutionSettin private static final Logger LOGGER = LoggerFactory.getLogger(ModuleExecutionSettingsFactoryImpl.class); + private static final String TEST_CONFIGURATION_TAG_PREFIX = "test.configuration."; private final Config config; private final ConfigurationApi configurationApi; @@ -87,11 +89,21 @@ private TracerEnvironment buildTracerEnvironment( String repositoryRoot, JvmInfo jvmInfo, @Nullable String moduleName) { GitInfo gitInfo = GitInfoProvider.INSTANCE.getGitInfo(repositoryRoot); + TracerEnvironment.Builder builder = TracerEnvironment.builder(); + for (Map.Entry e : config.getGlobalTags().entrySet()) { + String key = e.getKey(); + if (key.startsWith(TEST_CONFIGURATION_TAG_PREFIX)) { + String configurationKey = key.substring(TEST_CONFIGURATION_TAG_PREFIX.length()); + String configurationValue = e.getValue(); + builder.customTag(configurationKey, configurationValue); + } + } + /* * IMPORTANT: JVM and OS properties should match tags * set in datadog.trace.civisibility.decorator.TestDecorator */ - return TracerEnvironment.builder() + return builder .service(config.getServiceName()) .env(config.getEnv()) .repositoryUrl(gitInfo.getRepositoryURL()) @@ -109,12 +121,23 @@ private TracerEnvironment buildTracerEnvironment( private CiVisibilitySettings getCiVisibilitySettings(TracerEnvironment tracerEnvironment) { try { - return configurationApi.getSettings(tracerEnvironment); + CiVisibilitySettings settings = configurationApi.getSettings(tracerEnvironment); + if (settings.isGitUploadRequired()) { + LOGGER.info("Git data upload needs to finish before remote settings can be retrieved"); + gitDataUploader + .startOrObserveGitDataUpload() + .get(config.getCiVisibilityGitUploadTimeoutMillis(), TimeUnit.MILLISECONDS); + + return configurationApi.getSettings(tracerEnvironment); + } else { + return settings; + } + } catch (Exception e) { LOGGER.warn( "Could not obtain CI Visibility settings, will default to disabled code coverage and tests skipping"); LOGGER.debug("Error while obtaining CI Visibility settings", e); - return new CiVisibilitySettings(false, false); + return new CiVisibilitySettings(false, false, false); } } @@ -123,8 +146,11 @@ private boolean isItrEnabled(CiVisibilitySettings ciVisibilitySettings) { } private boolean isCodeCoverageEnabled(CiVisibilitySettings ciVisibilitySettings) { - return ciVisibilitySettings.isCodeCoverageEnabled() - && config.isCiVisibilityCodeCoverageEnabled(); + return config.isCiVisibilityCodeCoverageEnabled() + && (ciVisibilitySettings.isCodeCoverageEnabled() // coverage enabled via backend settings + || config + .isCiVisibilityJacocoPluginVersionProvided() // coverage enabled via tracer settings + ); } private Map getPropertiesPropagatedToChildProcess( @@ -156,6 +182,11 @@ private Map getPropertiesPropagatedToChildProcess( CiVisibilityConfig.CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED), Boolean.toString(false)); + propagatedSystemProperties.put( + Strings.propertyNameToSystemPropertyName( + CiVisibilityConfig.CIVISIBILITY_INJECTED_TRACER_VERSION), + TracerVersion.TRACER_VERSION); + return propagatedSystemProperties; } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java index 3250f3a6389..e01e56785c9 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializer.java @@ -26,10 +26,10 @@ public static ByteBuffer serialize(Collection skippableTests) { String name = test.getName(); String parameters = test.getParameters(); - length += suite.length(); - length += name.length(); + length += suite.getBytes(CHARSET).length; + length += name.getBytes(CHARSET).length; if (parameters != null) { - length += parameters.length(); + length += parameters.getBytes(CHARSET).length; } } @@ -41,14 +41,18 @@ public static ByteBuffer serialize(Collection skippableTests) { String name = test.getName(); String parameters = test.getParameters(); - buffer.putInt(suite.length()); - buffer.put(suite.getBytes(CHARSET)); - buffer.putInt(name.length()); - buffer.put(name.getBytes(CHARSET)); + byte[] suiteBytes = suite.getBytes(CHARSET); + buffer.putInt(suiteBytes.length); + buffer.put(suiteBytes); + + byte[] nameBytes = name.getBytes(CHARSET); + buffer.putInt(nameBytes.length); + buffer.put(nameBytes); if (parameters != null) { - buffer.putInt(parameters.length()); - buffer.put(parameters.getBytes(CHARSET)); + byte[] parametersBytes = parameters.getBytes(CHARSET); + buffer.putInt(parametersBytes.length); + buffer.put(parametersBytes); } else { buffer.putInt(-1); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java index 6240aea6264..319c95a454e 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java @@ -2,6 +2,8 @@ import com.squareup.moshi.Json; import datadog.trace.api.civisibility.config.Configurations; +import java.util.HashMap; +import java.util.Map; public class TracerEnvironment { @@ -13,6 +15,10 @@ public class TracerEnvironment { private final String branch; private final String sha; + + @Json(name = "test_level") + private final String testLevel = "test"; + private final Configurations configurations; private TracerEnvironment( @@ -52,6 +58,7 @@ public static final class Builder { private String runtimeVendor; private String runtimeArchitecture; private String testBundle; + private final Map customTags = new HashMap<>(); public Builder service(String service) { this.service = service; @@ -118,6 +125,11 @@ public Builder testBundle(String testBundle) { return this; } + public Builder customTag(String key, String value) { + this.customTags.put(key, value); + return this; + } + public TracerEnvironment build() { return new TracerEnvironment( service, @@ -133,7 +145,8 @@ public TracerEnvironment build() { runtimeVersion, runtimeVendor, runtimeArchitecture, - testBundle)); + testBundle, + customTags)); } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/NoopCoverageProbeStore.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/NoopCoverageProbeStore.java index 111cf8eff35..dc68fe42234 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/NoopCoverageProbeStore.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/NoopCoverageProbeStore.java @@ -11,6 +11,9 @@ public class NoopCoverageProbeStore implements CoverageProbeStore { @Override public void record(Class clazz, long classId, String className, int probeId) {} + @Override + public void recordNonCodeResource(String absolutePath) {} + @Override public void report(Long testSessionId, Long testSuiteId, long spanId) {} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/SegmentlessTestProbes.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/SegmentlessTestProbes.java index cb133bd1e55..5407b786742 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/SegmentlessTestProbes.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/SegmentlessTestProbes.java @@ -5,10 +5,12 @@ import datadog.trace.api.civisibility.coverage.TestReportFileEntry; import datadog.trace.civisibility.source.SourcePathResolver; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,12 +21,14 @@ public class SegmentlessTestProbes implements CoverageProbeStore { // Unbounded data structure that only exists within a single test span private final Set> coveredClasses; + private final Collection nonCodeResources; private final SourcePathResolver sourcePathResolver; private volatile TestReport testReport; SegmentlessTestProbes(SourcePathResolver sourcePathResolver) { this.sourcePathResolver = sourcePathResolver; coveredClasses = ConcurrentHashMap.newKeySet(); + nonCodeResources = new ConcurrentLinkedQueue<>(); } @Override @@ -32,6 +36,11 @@ public void record(Class clazz, long classId, String className, int probeId) coveredClasses.add(clazz); } + @Override + public void recordNonCodeResource(String absolutePath) { + nonCodeResources.add(absolutePath); + } + @Override public void report(Long testSessionId, Long testSuiteId, long spanId) { List fileEntries = new ArrayList<>(coveredClasses.size()); @@ -47,6 +56,20 @@ public void report(Long testSessionId, Long testSuiteId, long spanId) { TestReportFileEntry fileEntry = new TestReportFileEntry(sourcePath, Collections.emptyList()); fileEntries.add(fileEntry); } + + for (String nonCodeResource : nonCodeResources) { + String resourcePath = sourcePathResolver.getResourcePath(nonCodeResource); + if (resourcePath == null) { + log.debug( + "Skipping coverage reporting for {} because resource path could not be determined", + nonCodeResource); + continue; + } + TestReportFileEntry fileEntry = + new TestReportFileEntry(resourcePath, Collections.emptyList()); + fileEntries.add(fileEntry); + } + testReport = new TestReport(testSessionId, testSuiteId, spanId, fileEntries); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/TestProbes.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/TestProbes.java index d0fa5a731f4..55a4a68e871 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/TestProbes.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/TestProbes.java @@ -7,11 +7,14 @@ import datadog.trace.civisibility.source.Utils; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nullable; import org.jacoco.core.analysis.Analyzer; import org.jacoco.core.data.ExecutionDataStore; @@ -26,12 +29,14 @@ public class TestProbes implements CoverageProbeStore { // Unbounded data structure that only exists within a single test span private final Map, ExecutionDataAdapter> probeActivations; + private final Collection nonCodeResources; private final SourcePathResolver sourcePathResolver; private volatile TestReport testReport; TestProbes(SourcePathResolver sourcePathResolver) { this.sourcePathResolver = sourcePathResolver; probeActivations = new ConcurrentHashMap<>(); + nonCodeResources = new ConcurrentLinkedQueue<>(); } @Override @@ -41,6 +46,11 @@ public void record(Class clazz, long classId, String className, int probeId) .record(probeId); } + @Override + public void recordNonCodeResource(String absolutePath) { + nonCodeResources.add(absolutePath); + } + @Override public void report(Long testSessionId, Long testSuiteId, long spanId) { Map> segmentsBySourcePath = new HashMap<>(); @@ -96,6 +106,19 @@ public void report(Long testSessionId, Long testSuiteId, long spanId) { fileEntries.add(new TestReportFileEntry(sourcePath, compressedSegments)); } + for (String nonCodeResource : nonCodeResources) { + String resourcePath = sourcePathResolver.getResourcePath(nonCodeResource); + if (resourcePath == null) { + log.debug( + "Skipping coverage reporting for {} because resource path could not be determined", + nonCodeResource); + continue; + } + TestReportFileEntry fileEntry = + new TestReportFileEntry(resourcePath, Collections.emptyList()); + fileEntries.add(fileEntry); + } + testReport = new TestReport(testSessionId, testSuiteId, spanId, fileEntries); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java index 8cd2afcd20d..2220d831d6c 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/events/BuildEventsHandlerImpl.java @@ -40,9 +40,19 @@ public void onTestSessionStart( final Path projectRoot, final String startCommand, final String buildSystemName, - final String buildSystemVersion) { + final String buildSystemVersion, + Map additionalTags) { DDBuildSystemSession testSession = sessionFactory.startSession(projectName, projectRoot, startCommand, buildSystemName, null); + + if (additionalTags != null) { + for (Map.Entry e : additionalTags.entrySet()) { + String tag = e.getKey(); + Object value = e.getValue(); + testSession.setTag(tag, value); + } + } + testSession.setTag(Tags.TEST_TOOLCHAIN, buildSystemName + ":" + buildSystemVersion); inProgressTestSessions.put(sessionKey, testSession); } @@ -139,4 +149,9 @@ public ModuleExecutionSettings getModuleExecutionSettings(T sessionKey, Path jvm JvmInfo jvmInfo = jvmInfoFactory.getJvmInfo(jvmExecutablePath); return testSession.getModuleExecutionSettings(jvmInfo); } + + @Override + public ModuleInfo getModuleInfo(T sessionKey, String moduleName) { + return getTestModule(sessionKey, moduleName).getModuleInfo(); + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/git/tree/GitDataUploaderImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/git/tree/GitDataUploaderImpl.java index ca142328102..e8da31861f5 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/git/tree/GitDataUploaderImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/git/tree/GitDataUploaderImpl.java @@ -81,7 +81,11 @@ public Future startOrObserveGitDataUpload() { private void uploadGitData() { try { - if (config.isCiVisibilityGitUnshallowEnabled() && gitClient.isShallow()) { + LOGGER.info("Starting git data upload, {}", gitClient); + + if (config.isCiVisibilityGitUnshallowEnabled() + && !config.isCiVisibilityGitUnshallowDefer() + && gitClient.isShallow()) { unshallowRepository(); } @@ -89,12 +93,28 @@ private void uploadGitData() { String remoteUrl = gitInfo.getRepositoryURL(); List latestCommits = gitClient.getLatestCommits(); if (latestCommits.isEmpty()) { - LOGGER.debug("No commits in the last month"); + LOGGER.info("No commits in the last month, skipping git data upload"); callback.complete(null); return; } Collection commitsToSkip = gitDataApi.searchCommits(remoteUrl, latestCommits); + if (commitsToSkip.size() == latestCommits.size()) { + LOGGER.info( + "Backend already knows of the {} local commits, skipping git data upload", + latestCommits.size()); + callback.complete(null); + return; + } + + if (config.isCiVisibilityGitUnshallowEnabled() + && config.isCiVisibilityGitUnshallowDefer() + && gitClient.isShallow()) { + unshallowRepository(); + latestCommits = gitClient.getLatestCommits(); + commitsToSkip = gitDataApi.searchCommits(remoteUrl, latestCommits); + } + Collection commitsToInclude = new ArrayList<>(latestCommits.size() - commitsToSkip.size()); for (String commit : latestCommits) { @@ -105,7 +125,7 @@ private void uploadGitData() { List objectHashes = gitClient.getObjects(commitsToSkip, commitsToInclude); if (objectHashes.isEmpty()) { - LOGGER.debug("No git objects to upload"); + LOGGER.info("No git objects to upload"); callback.complete(null); return; } @@ -128,7 +148,7 @@ private void uploadGitData() { FileUtils.delete(packFilesDirectory); } - LOGGER.info("Git data upload finished, {}", gitClient); + LOGGER.info("Git data upload finished"); callback.complete(null); } catch (Exception e) { @@ -140,6 +160,7 @@ private void uploadGitData() { } private void unshallowRepository() throws IOException, TimeoutException, InterruptedException { + long unshallowStart = System.currentTimeMillis(); try { gitClient.unshallow(GitClient.HEAD); return; @@ -158,6 +179,7 @@ private void unshallowRepository() throws IOException, TimeoutException, Interru e); gitClient.unshallow(null); } + LOGGER.info("Repository unshallowing took {} ms", System.currentTimeMillis() - unshallowStart); } private void waitForUploadToFinish() { diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/TestFramework.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/TestFramework.java index 7130c3cdac5..5efe3a2313e 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/TestFramework.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/ipc/TestFramework.java @@ -2,7 +2,7 @@ import java.util.Objects; -public final class TestFramework { +public final class TestFramework implements Comparable { private final String name; private final String version; @@ -35,4 +35,10 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, version); } + + @Override + public int compareTo(TestFramework o) { + int nameComparison = name.compareTo(o.name); + return nameComparison != 0 ? nameComparison : version.compareTo(o.version); + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/BestEffortSourcePathResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/BestEffortSourcePathResolver.java index 9d83ddc2cbf..fba9c191596 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/BestEffortSourcePathResolver.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/BestEffortSourcePathResolver.java @@ -22,4 +22,16 @@ public String getSourcePath(@Nonnull Class c) { } return null; } + + @Nullable + @Override + public String getResourcePath(@Nullable String relativePath) { + for (SourcePathResolver delegate : delegates) { + String resourcePath = delegate.getResourcePath(relativePath); + if (resourcePath != null) { + return resourcePath; + } + } + return null; + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/CompilerAidedSourcePathResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/CompilerAidedSourcePathResolver.java index c8000a10ce8..a675db70b93 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/CompilerAidedSourcePathResolver.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/CompilerAidedSourcePathResolver.java @@ -23,4 +23,10 @@ public String getSourcePath(@Nonnull Class c) { return null; } } + + @Nullable + @Override + public String getResourcePath(String relativePath) { + return null; + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/NoOpSourcePathResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/NoOpSourcePathResolver.java new file mode 100644 index 00000000000..a7b9e18f7bf --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/NoOpSourcePathResolver.java @@ -0,0 +1,21 @@ +package datadog.trace.civisibility.source; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class NoOpSourcePathResolver implements SourcePathResolver { + + public static final SourcePathResolver INSTANCE = new NoOpSourcePathResolver(); + + @Nullable + @Override + public String getSourcePath(@Nonnull Class c) { + return null; + } + + @Nullable + @Override + public String getResourcePath(@Nullable String relativePath) { + return null; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/SourcePathResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/SourcePathResolver.java index 23c0f2682c8..60e5d6f81d0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/SourcePathResolver.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/SourcePathResolver.java @@ -4,11 +4,32 @@ import javax.annotation.Nullable; public interface SourcePathResolver { - /** * @return path to the source file corresponding to the provided class, relative to repository * root. {@code null} is returned if the path could not be resolved */ @Nullable String getSourcePath(@Nonnull Class c); + + /** + * @param relativePath Path to a resource in current run's repository, relative to a resource root + * @return Path relative to repository root + */ + @Nullable + String getResourcePath(@Nullable String relativePath); + + SourcePathResolver NO_OP = + new SourcePathResolver() { + @Nullable + @Override + public String getSourcePath(@Nonnull Class c) { + return null; + } + + @Nullable + @Override + public String getResourcePath(@Nullable String relativePath) { + return null; + } + }; } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java index f9023ab5b60..8c826776656 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/Utils.java @@ -7,7 +7,26 @@ public abstract class Utils { public static InputStream getClassStream(Class clazz) throws IOException { String className = clazz.getName(); - String classPath = "/" + className.replace('.', '/') + ".class"; - return clazz.getResourceAsStream(classPath); + InputStream classStream = clazz.getResourceAsStream(toResourceName(className)); + if (classStream != null) { + return classStream; + } else { + // might be auto-generated inner class (e.g. Mockito mock) + String topLevelClassName = stripNestedClassNames(clazz.getName()); + return clazz.getResourceAsStream(toResourceName(topLevelClassName)); + } + } + + private static String toResourceName(String className) { + return "/" + className.replace('.', '/') + ".class"; + } + + public static String stripNestedClassNames(String className) { + int innerClassNameIdx = className.indexOf('$'); + if (innerClassNameIdx >= 0) { + return className.substring(0, innerClassNameIdx); + } else { + return className; + } } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ConventionBasedResourceResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ConventionBasedResourceResolver.java new file mode 100644 index 00000000000..2d70fa7962c --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ConventionBasedResourceResolver.java @@ -0,0 +1,41 @@ +package datadog.trace.civisibility.source.index; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.List; + +public class ConventionBasedResourceResolver implements ResourceResolver { + + private final FileSystem fileSystem; + private final List resourceFolderNames; + + public ConventionBasedResourceResolver(FileSystem fileSystem, List resourceFolderNames) { + this.fileSystem = fileSystem; + this.resourceFolderNames = resourceFolderNames; + } + + /** + * Given absolute path to a resource file, returns resource root - the enclosing folder that is + * considered as a resource folder by the project's build system. Resource folder is a folder + * containing non-code resources that are copied to a target/build folder during the project's + * build. + * + *

The implementation of this method is very naive: it examines the resource's path looking for + * segments that match conventional resource folder names ("resources/", "java/", etc.). + * + * @param resourceFile Absolute path to a resource file + * @return Resource root + */ + @Override + public Path getResourceRoot(Path resourceFile) throws IOException { + String pathAsString = resourceFile.toString(); + for (String resourceFolderName : resourceFolderNames) { + int idx = pathAsString.indexOf(resourceFolderName); + if (idx >= 0) { + return fileSystem.getPath(pathAsString.substring(0, idx + resourceFolderName.length())); + } + } + return null; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/PackageResolverImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/PackageResolverImpl.java index 6aedf77370a..1da3595d38a 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/PackageResolverImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/PackageResolverImpl.java @@ -8,12 +8,12 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; -class PackageResolverImpl implements PackageResolver { +public class PackageResolverImpl implements PackageResolver { private static final String PACKAGE_KEYWORD = "package"; private final FileSystem fileSystem; - PackageResolverImpl(FileSystem fileSystem) { + public PackageResolverImpl(FileSystem fileSystem) { this.fileSystem = fileSystem; } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java index d8010aeb072..6144c39b605 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndex.java @@ -50,7 +50,7 @@ public List getRootPackages() { @Nullable public String getSourcePath(@Nonnull Class c) { - String topLevelClassName = stripNestedClassNames(c.getName()); + String topLevelClassName = Utils.stripNestedClassNames(c.getName()); SourceType sourceType = detectSourceType(c); String extension = sourceType.getExtension(); String classNameWithExtension = topLevelClassName + extension; @@ -107,15 +107,6 @@ private SourceType detectSourceType(Class c) { return SourceType.JAVA; } - private String stripNestedClassNames(String className) { - int innerClassNameIdx = className.indexOf('$'); - if (innerClassNameIdx >= 0) { - return className.substring(0, innerClassNameIdx); - } else { - return className; - } - } - /** * Names of package-private classes do not have to correspond to the names of their source code * files. For such classes filename is extracted from SourceFile attribute that is available in @@ -126,6 +117,10 @@ private String getSourcePathForPackagePrivateOrNonJavaClass(Class c) { SourceFileAttributeVisitor sourceFileAttributeVisitor = new SourceFileAttributeVisitor(); try (InputStream classStream = Utils.getClassStream(c)) { + if (classStream == null) { + log.debug("Could not get input stream for class {}", c.getName()); + return null; + } ClassReader classReader = new ClassReader(classStream); classReader.accept( sourceFileAttributeVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); @@ -157,6 +152,17 @@ private String getSourcePathForPackagePrivateOrNonJavaClass(Class c) { } } + public String getResourcePath(String relativePath) { + int sourceRootIdx = trie.apply(relativePath); + if (sourceRootIdx < 0) { + log.debug("Could not find source root for resource {}", relativePath); + return null; + } + + String sourceRoot = sourceRoots.get(sourceRootIdx); + return sourceRoot + File.separator + relativePath; + } + private static final class SourceFileAttributeVisitor extends ClassVisitor { private String source; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexBuilder.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexBuilder.java index d0d0675e001..6c6aef603cb 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexBuilder.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexBuilder.java @@ -22,20 +22,20 @@ public class RepoIndexBuilder implements RepoIndexProvider { private final String repoRoot; private final PackageResolver packageResolver; + private final ResourceResolver resourceResolver; private final FileSystem fileSystem; private final Object indexInitializationLock = new Object(); private volatile RepoIndex index; - public RepoIndexBuilder(String repoRoot, FileSystem fileSystem) { - this.repoRoot = repoRoot; - this.packageResolver = new PackageResolverImpl(fileSystem); - this.fileSystem = fileSystem; - } - - RepoIndexBuilder(String repoRoot, PackageResolver packageResolver, FileSystem fileSystem) { + public RepoIndexBuilder( + String repoRoot, + PackageResolver packageResolver, + ResourceResolver resourceResolver, + FileSystem fileSystem) { this.repoRoot = repoRoot; this.packageResolver = packageResolver; + this.resourceResolver = resourceResolver; this.fileSystem = fileSystem; } @@ -60,7 +60,7 @@ private RepoIndex doGetIndex() { Path repoRootPath = fileSystem.getPath(repoRoot); RepoIndexingFileVisitor repoIndexingFileVisitor = - new RepoIndexingFileVisitor(packageResolver, repoRootPath); + new RepoIndexingFileVisitor(packageResolver, resourceResolver, repoRootPath); long startTime = System.currentTimeMillis(); try { @@ -77,10 +77,11 @@ private RepoIndex doGetIndex() { RepoIndexingStats stats = repoIndexingFileVisitor.indexingStats; RepoIndex index = repoIndexingFileVisitor.getIndex(); log.info( - "Indexing took {} ms. Files visited: {}, source files visited: {}, source roots found: {}, root packages found: {}", + "Indexing took {} ms. Files visited: {}, source files visited: {}, resource files visited: {}, source roots found: {}, root packages found: {}", duration, stats.filesVisited, stats.sourceFilesVisited, + stats.resourceFilesVisited, repoIndexingFileVisitor.sourceRoots.size(), index.getRootPackages()); return index; @@ -91,14 +92,17 @@ private static final class RepoIndexingFileVisitor implements FileVisitor private static final Logger log = LoggerFactory.getLogger(RepoIndexingFileVisitor.class); private final PackageResolver packageResolver; + private final ResourceResolver resourceResolver; private final ClassNameTrie.Builder trieBuilder; private final LinkedHashSet sourceRoots; private final PackageTree packageTree; private final RepoIndexingStats indexingStats; private final Path repoRoot; - private RepoIndexingFileVisitor(PackageResolver packageResolver, Path repoRoot) { + private RepoIndexingFileVisitor( + PackageResolver packageResolver, ResourceResolver resourceResolver, Path repoRoot) { this.packageResolver = packageResolver; + this.resourceResolver = resourceResolver; this.repoRoot = repoRoot; trieBuilder = new ClassNameTrie.Builder(); sourceRoots = new LinkedHashSet<>(); @@ -116,18 +120,11 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { indexingStats.filesVisited++; try { - String fileName = file.getFileName().toString(); - SourceType sourceType = SourceType.getByFileName(fileName); - if (sourceType != null) { - indexingStats.sourceFilesVisited++; - - Path packagePath = packageResolver.getPackage(file); - packageTree.add(packagePath); + Path sourceRoot = getSourceRoot(file); + if (sourceRoot != null) { + sourceRoots.add(repoRoot.relativize(sourceRoot).toString()); - Path currentSourceRoot = getSourceRoot(file, packagePath); - sourceRoots.add(repoRoot.relativize(currentSourceRoot).toString()); - - Path relativePath = currentSourceRoot.relativize(file); + Path relativePath = sourceRoot.relativize(file); String classNameWithExtension = relativePath.toString().replace(File.separatorChar, '.'); if (!classNameWithExtension.isEmpty()) { trieBuilder.put(classNameWithExtension, sourceRoots.size() - 1); @@ -139,6 +136,24 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { return FileVisitResult.CONTINUE; } + private Path getSourceRoot(Path file) throws IOException { + String fileName = file.getFileName().toString(); + SourceType sourceType = SourceType.getByFileName(fileName); + if (sourceType == null) { + return null; + + } else if (!sourceType.isResource()) { + indexingStats.sourceFilesVisited++; + Path packagePath = packageResolver.getPackage(file); + packageTree.add(packagePath); + return getSourceRoot(file, packagePath); + + } else { + indexingStats.resourceFilesVisited++; + return resourceResolver.getResourceRoot(file); + } + } + private Path getSourceRoot(Path file, Path packagePath) { Path folder = file.getParent(); // remove package path suffix from folder path to get source root @@ -172,5 +187,6 @@ public RepoIndex getIndex() { private static final class RepoIndexingStats { int filesVisited; int sourceFilesVisited; + int resourceFilesVisited; } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolver.java index 5ccea333d79..65ca637c17b 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolver.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolver.java @@ -20,9 +20,13 @@ public RepoIndexSourcePathResolver(String repoRoot, RepoIndexProvider indexProvi } RepoIndexSourcePathResolver( - String repoRoot, PackageResolver packageResolver, FileSystem fileSystem) { + String repoRoot, + PackageResolver packageResolver, + ResourceResolver resourceResolver, + FileSystem fileSystem) { this.repoRoot = repoRoot; - this.indexProvider = new RepoIndexBuilder(repoRoot, packageResolver, fileSystem); + this.indexProvider = + new RepoIndexBuilder(repoRoot, packageResolver, resourceResolver, fileSystem); } @Nullable @@ -35,6 +39,12 @@ public String getSourcePath(@Nonnull Class c) { return indexProvider.getIndex().getSourcePath(c); } + @Nullable + @Override + public String getResourcePath(@Nullable String relativePath) { + return indexProvider.getIndex().getResourcePath(relativePath); + } + private boolean isLocatedInsideRepository(Class c) { ProtectionDomain protectionDomain = c.getProtectionDomain(); if (protectionDomain == null) { diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ResourceResolver.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ResourceResolver.java new file mode 100644 index 00000000000..1fbc8635a9e --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/ResourceResolver.java @@ -0,0 +1,8 @@ +package datadog.trace.civisibility.source.index; + +import java.io.IOException; +import java.nio.file.Path; + +public interface ResourceResolver { + Path getResourceRoot(Path resourceFile) throws IOException; +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/SourceType.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/SourceType.java index 792dba71118..a1debe75041 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/SourceType.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/source/index/SourceType.java @@ -1,23 +1,30 @@ package datadog.trace.civisibility.source.index; enum SourceType { - JAVA(".java"), - GROOVY(".groovy"), - KOTLIN(".kt"), - SCALA(".scala"); + JAVA(".java", false), + GROOVY(".groovy", false), + KOTLIN(".kt", false), + SCALA(".scala", false), + GHERKIN(".feature", true); private static final SourceType[] UNIVERSE = SourceType.values(); private final String extension; + private final boolean resource; - SourceType(String extension) { + SourceType(String extension, boolean resource) { this.extension = extension; + this.resource = resource; } public String getExtension() { return extension; } + public boolean isResource() { + return resource; + } + static SourceType getByFileName(String fileName) { for (SourceType sourceType : UNIVERSE) { if (fileName.endsWith(sourceType.extension)) { diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/utils/SpanUtils.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/utils/SpanUtils.java index 479ac19a1cb..32cdcb5dbbe 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/utils/SpanUtils.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/utils/SpanUtils.java @@ -3,8 +3,12 @@ import datadog.trace.api.civisibility.CIConstants; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.Tags; +import datadog.trace.civisibility.ipc.TestFramework; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeSet; import java.util.function.Consumer; public class SpanUtils { @@ -15,49 +19,81 @@ public static Consumer propagateCiVisibilityTagsTo(AgentSpan parentSp } public static void propagateCiVisibilityTags(AgentSpan parentSpan, AgentSpan childSpan) { - mergeTag(parentSpan, childSpan, Tags.TEST_FRAMEWORK); - mergeTag(parentSpan, childSpan, Tags.TEST_FRAMEWORK_VERSION); + mergeTestFrameworks(parentSpan, getFrameworks(childSpan)); propagateStatus(parentSpan, childSpan); } - public static void mergeTag(AgentSpan parentSpan, AgentSpan childSpan, String tagName) { - mergeTag(parentSpan, tagName, childSpan.getTag(tagName)); + public static void mergeTestFrameworks(AgentSpan span, Collection testFrameworks) { + Collection spanFrameworks = getFrameworks(span); + Collection merged = merge(spanFrameworks, testFrameworks); + setFrameworks(span, merged); } - public static void mergeTag(AgentSpan span, String tagName, Object tagValue) { - if (tagValue == null) { - return; + private static Collection getFrameworks(AgentSpan span) { + Object nameTag = span.getTag(Tags.TEST_FRAMEWORK); + Object versionTag = span.getTag(Tags.TEST_FRAMEWORK_VERSION); + if (nameTag == null && versionTag == null) { + return Collections.emptyList(); } - Object existingValue = span.getTag(tagName); - if (existingValue == null) { - span.setTag(tagName, tagValue); - return; - } + Collection frameworks = new ArrayList<>(); + if (nameTag instanceof String) { + frameworks.add(new TestFramework((String) nameTag, (String) versionTag)); - if (existingValue.equals(tagValue)) { - return; - } + } else if (nameTag instanceof Collection) { + Iterator names = ((Collection) nameTag).iterator(); + Iterator versions = ((Collection) versionTag).iterator(); + while (names.hasNext()) { + frameworks.add(new TestFramework(names.next(), versions.next())); + } - Collection updatedValue = new ArrayList<>(); - if (existingValue instanceof Collection) { - updatedValue.addAll((Collection) existingValue); } else { - updatedValue.add(existingValue); + throw new IllegalArgumentException( + "Unexpected tag type(s): " + + Tags.TEST_FRAMEWORK + + " (" + + nameTag + + ") " + + Tags.TEST_FRAMEWORK_VERSION + + " (" + + versionTag + + ")"); } + return frameworks; + } - if (tagValue instanceof Collection) { - for (Object value : (Collection) tagValue) { - if (!updatedValue.contains(value)) { - updatedValue.add(value); - } - } - } else { - if (!updatedValue.contains(tagValue)) { - updatedValue.add(tagValue); - } + private static Collection merge( + Collection parentFrameworks, Collection childFrameworks) { + if (parentFrameworks.isEmpty()) { + return childFrameworks; } - span.setTag(tagName, updatedValue); + if (childFrameworks.isEmpty()) { + return parentFrameworks; + } + Collection merged = new TreeSet<>(); + merged.addAll(parentFrameworks); + merged.addAll(childFrameworks); + return merged; + } + + private static void setFrameworks(AgentSpan span, Collection frameworks) { + if (frameworks.isEmpty()) { + return; + } + if (frameworks.size() == 1) { + TestFramework framework = frameworks.iterator().next(); + span.setTag(Tags.TEST_FRAMEWORK, framework.getName()); + span.setTag(Tags.TEST_FRAMEWORK_VERSION, framework.getVersion()); + return; + } + Collection names = new ArrayList<>(frameworks.size()); + Collection versions = new ArrayList<>(frameworks.size()); + for (TestFramework framework : frameworks) { + names.add(framework.getName()); + versions.add(framework.getVersion()); + } + span.setTag(Tags.TEST_FRAMEWORK, names); + span.setTag(Tags.TEST_FRAMEWORK_VERSION, versions); } private static void propagateStatus(AgentSpan parentSpan, AgentSpan childSpan) { @@ -85,4 +121,10 @@ private static void propagateStatus(AgentSpan parentSpan, AgentSpan childSpan) { break; } } + + public static void propagateTags(AgentSpan parentSpan, AgentSpan childSpan, String... tagNames) { + for (String tagName : tagNames) { + parentSpan.setTag(tagName, childSpan.getTag(tagName)); + } + } } diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/DDTestImplTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/DDTestImplTest.groovy index a372f5c42df..0d81f97a786 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/DDTestImplTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/DDTestImplTest.groovy @@ -10,6 +10,7 @@ import datadog.trace.civisibility.codeowners.CodeownersImpl import datadog.trace.civisibility.coverage.NoopCoverageProbeStore import datadog.trace.civisibility.decorator.TestDecoratorImpl import datadog.trace.civisibility.source.MethodLinesResolver +import datadog.trace.civisibility.source.SourcePathResolver import datadog.trace.civisibility.utils.SpanUtils import datadog.trace.common.writer.ListWriter import datadog.trace.core.CoreTracer @@ -107,7 +108,6 @@ class DDTestImplTest extends DDSpecification { def config = Config.get() def testDecorator = new TestDecoratorImpl("component", [:]) - def sourcePathResolver = { it -> null } def methodLinesResolver = { it -> MethodLinesResolver.MethodLines.EMPTY } def codeowners = CodeownersImpl.EMPTY def coverageProbeStoreFactory = new NoopCoverageProbeStore.NoopCoverageProbeStoreFactory() @@ -123,7 +123,7 @@ class DDTestImplTest extends DDSpecification { null, config, testDecorator, - sourcePathResolver, + SourcePathResolver.NO_OP, methodLinesResolver, codeowners, coverageProbeStoreFactory, diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy index 72539841faa..ed089c234a9 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy @@ -2,12 +2,14 @@ package datadog.trace.civisibility.config import com.squareup.moshi.Moshi import datadog.communication.http.HttpRetryPolicy +import datadog.communication.http.OkHttpUtils import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.api.civisibility.config.Configurations import datadog.trace.api.civisibility.config.SkippableTest import datadog.trace.civisibility.communication.BackendApi import datadog.trace.civisibility.communication.EvpProxyApi import okhttp3.HttpUrl +import okhttp3.OkHttpClient import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification @@ -37,6 +39,7 @@ class ConfigurationApiImplTest extends Specification { "repository_url": "https://github.com/DataDog/foo", "branch" : "prod", "sha" : "d64185e45d1722ab3a53c45be47accae", + "test_level" : "test", "configurations": [ "os.platform" : "linux", "os.architecture" : "amd64", @@ -45,14 +48,17 @@ class ConfigurationApiImplTest extends Specification { "runtime.name" : "runtimeName", "runtime.version" : "runtimeVersion", "runtime.vendor" : "vendor", - "runtime.architecture": "amd64" + "runtime.architecture": "amd64", + "custom": [ + "customTag": "customValue" + ] ] ] ] ] if (expectedRequest) { - response.status(200).send('{ "data": { "type": "ci_app_tracers_test_service_settings", "id": "uuid", "attributes": { "code_coverage": true, "tests_skipping": true } } }') + response.status(200).send('{ "data": { "type": "ci_app_tracers_test_service_settings", "id": "uuid", "attributes": { "code_coverage": true, "tests_skipping": true, "require_git": true } } }') } else { response.status(400).send() } @@ -70,6 +76,7 @@ class ConfigurationApiImplTest extends Specification { "repository_url": "https://github.com/DataDog/foo", "branch" : "prod", "sha" : "d64185e45d1722ab3a53c45be47accae", + "test_level" : "test", "configurations": [ "os.platform" : "linux", "os.architecture" : "amd64", @@ -78,7 +85,10 @@ class ConfigurationApiImplTest extends Specification { "runtime.name" : "runtimeName", "runtime.version" : "runtimeVersion", "runtime.vendor" : "vendor", - "runtime.architecture": "amd64" + "runtime.architecture": "amd64", + "custom": [ + "customTag": "customValue" + ] ] ] ] @@ -87,9 +97,9 @@ class ConfigurationApiImplTest extends Specification { if (expectedRequest) { response.status(200).send('{ "data": [' + '{ "id": "49968354e2091cdb", "type": "test", "attributes": ' + - '{ "configurations": { "test.bundle": "testBundle-a" }, "suite": "suite-a", "name": "name-a", "parameters": "parameters-a" } },' + + '{ "configurations": { "test.bundle": "testBundle-a", "custom": { "customTag": "customValue" } }, "suite": "suite-a", "name": "name-a", "parameters": "parameters-a" } },' + '{ "id": "49968354e2091cdc", "type": "test", "attributes": ' + - ' { "configurations": { "test.bundle": "testBundle-b" }, "suite": "suite-b", "name": "name-b", "parameters": "parameters-b" } }' + + ' { "configurations": { "test.bundle": "testBundle-b", "custom": { "customTag": "customValue" } }, "suite": "suite-b", "name": "name-b", "parameters": "parameters-b" } }' + '] }') } else { response.status(400).send() @@ -110,6 +120,7 @@ class ConfigurationApiImplTest extends Specification { then: settings.codeCoverageEnabled settings.testsSkippingEnabled + settings.gitUploadRequired } def "test skippable tests request"() { @@ -125,17 +136,19 @@ class ConfigurationApiImplTest extends Specification { skippableTests == [ new SkippableTest("suite-a", "name-a", "parameters-a", new Configurations(null, null, null, null, null, - null, null, "testBundle-a")), + null, null, "testBundle-a", Collections.singletonMap("customTag", "customValue"))), new SkippableTest("suite-b", "name-b", "parameters-b", new Configurations(null, null, null, null, null, - null, null, "testBundle-b")) + null, null, "testBundle-b", Collections.singletonMap("customTag", "customValue"))) ] } private BackendApi givenEvpProxy() { + String traceId = "a-trace-id" HttpUrl proxyUrl = HttpUrl.get(intakeServer.address) HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0) - return new EvpProxyApi(proxyUrl, REQUEST_TIMEOUT_MILLIS, retryPolicyFactory) + OkHttpClient client = OkHttpUtils.buildHttpClient(proxyUrl, REQUEST_TIMEOUT_MILLIS) + return new EvpProxyApi(traceId, proxyUrl, retryPolicyFactory, client) } private static TracerEnvironment givenTracerEnvironment() { @@ -152,6 +165,7 @@ class ConfigurationApiImplTest extends Specification { .runtimeVersion("runtimeVersion") .runtimeVendor("vendor") .runtimeArchitecture("amd64") + .customTag("customTag", "customValue") .build() } } diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy index db6a991d2df..1d438fc2780 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/JvmInfoFactoryTest.groovy @@ -15,7 +15,7 @@ class JvmInfoFactoryTest extends Specification { def currentJvmExecutable = getCurrentJvmExecutable() when: - def jvmInfo = JvmInfoFactory.doGetJvmInfo(currentJvmExecutable) + def jvmInfo = JvmInfoFactoryImpl.doGetJvmInfo(currentJvmExecutable) then: jvmInfo == JvmInfo.CURRENT_JVM diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy similarity index 90% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy rename to dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy index db382fc92a1..76494f2c43b 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/SkippableTestsSerializerTest.groovy @@ -22,6 +22,8 @@ class SkippableTestsSerializerTest extends Specification { tests << [ // single test [["suite", "name", null]], + [["suite", "𝕄 add user properties 𝕎 addUserProperties()", null]], + // non-ASCII characters [["suite", "name", "parameters"]], [["suite", "name", "{\"metadata\":{\"test_name\":\"test display name with #a #b #c\"}}"]], // multiple tests diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy index aba6ce092fa..c280fe51f76 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/git/tree/GitDataApiTest.groovy @@ -2,11 +2,13 @@ package datadog.trace.civisibility.git.tree import com.squareup.moshi.Moshi import datadog.communication.http.HttpRetryPolicy +import datadog.communication.http.OkHttpUtils import datadog.trace.agent.test.server.http.TestHttpServer import datadog.trace.civisibility.communication.BackendApi import datadog.trace.civisibility.communication.EvpProxyApi import datadog.trace.test.util.MultipartRequestParser import okhttp3.HttpUrl +import okhttp3.OkHttpClient import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification @@ -112,9 +114,11 @@ class GitDataApiTest extends Specification { } private BackendApi givenEvpProxy() { + String traceId = "a-trace-id" HttpUrl proxyUrl = HttpUrl.get(intakeServer.address) HttpRetryPolicy.Factory retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0) - return new EvpProxyApi(proxyUrl, REQUEST_TIMEOUT_MILLIS, retryPolicyFactory) + OkHttpClient client = OkHttpUtils.buildHttpClient(proxyUrl, REQUEST_TIMEOUT_MILLIS) + return new EvpProxyApi(traceId, proxyUrl, retryPolicyFactory, client) } private Path givenPackFile() { diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/ConventionBasedResourceResolverTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/ConventionBasedResourceResolverTest.groovy new file mode 100644 index 00000000000..cba076096a1 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/ConventionBasedResourceResolverTest.groovy @@ -0,0 +1,28 @@ +package datadog.trace.civisibility.source.index + +import com.google.common.jimfs.Configuration +import com.google.common.jimfs.Jimfs +import datadog.trace.api.Config +import spock.lang.Specification + +class ConventionBasedResourceResolverTest extends Specification { + + def "test resource root resolution: #path"() { + setup: + def fileSystem = Jimfs.newFileSystem(Configuration.unix()) + def resourcePath = fileSystem.getPath(path) + + when: + def resourceResolver = new ConventionBasedResourceResolver(fileSystem, Config.get().ciVisibilityResourceFolderNames) + def resourceRoot = resourceResolver.getResourceRoot(resourcePath) + + then: + resourceRoot == fileSystem.getPath(expectedResourceRoot) + + where: + path | expectedResourceRoot + "/root/src/test/groovy/features/MyFeature.feature" | "/root/src/test/groovy" + "/root/src/main/java/junit.properties" | "/root/src/main/java" + "/root/src/main/resources/my/package/fixture.json" | "/root/src/main/resources" + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy index 59a29662254..0dde87251a2 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/source/index/RepoIndexSourcePathResolverTest.groovy @@ -11,6 +11,7 @@ import java.nio.file.Path class RepoIndexSourcePathResolverTest extends Specification { def packageResolver = Stub(PackageResolver) + def resourceResolver = Stub(ResourceResolver) def fileSystem = Jimfs.newFileSystem(Configuration.unix()) def repoRoot = getRepoRoot() @@ -19,7 +20,7 @@ class RepoIndexSourcePathResolverTest extends Specification { def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(RepoIndexSourcePathResolverTest) == expectedSourcePath @@ -30,7 +31,7 @@ class RepoIndexSourcePathResolverTest extends Specification { def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(InnerClass) == expectedSourcePath @@ -41,7 +42,7 @@ class RepoIndexSourcePathResolverTest extends Specification { def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(InnerClass.NestedInnerClass) == expectedSourcePath @@ -52,7 +53,7 @@ class RepoIndexSourcePathResolverTest extends Specification { def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) def r = new Runnable() { void run() {} } @@ -66,18 +67,29 @@ class RepoIndexSourcePathResolverTest extends Specification { def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(PackagePrivateClass) == expectedSourcePath } + def "test source path resolution for class nested into package-private class"() { + setup: + def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") + + when: + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) + + then: + sourcePathResolver.getSourcePath(PackagePrivateClass.NestedIntoPackagePrivateClass) == expectedSourcePath + } + def "test source path resolution for non-java class whose file name is different from class name"() { setup: def expectedSourcePath = givenSourceFile(RepoIndexSourcePathResolverTest, repoRoot + "/src") when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(PublicClassWhoseNameDoesNotCorrespondToFileName) == expectedSourcePath @@ -87,7 +99,7 @@ class RepoIndexSourcePathResolverTest extends Specification { setup: when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(RepoIndexSourcePathResolver) == null @@ -97,7 +109,7 @@ class RepoIndexSourcePathResolverTest extends Specification { setup: when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(PackagePrivateClass) == null @@ -112,7 +124,7 @@ class RepoIndexSourcePathResolverTest extends Specification { Files.write(classPath, "STUB CLASS BODY".getBytes()) when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(RepoIndexSourcePathResolverTest) == null @@ -126,7 +138,7 @@ class RepoIndexSourcePathResolverTest extends Specification { givenRepoFile(fileSystem.getPath(repoRoot, "README.md")) when: - def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, fileSystem) + def sourcePathResolver = new RepoIndexSourcePathResolver(repoRoot, packageResolver, resourceResolver, fileSystem) then: sourcePathResolver.getSourcePath(RepoIndexSourcePathResolverTest) == expectedSourcePathOne @@ -173,6 +185,7 @@ class RepoIndexSourcePathResolverTest extends Specification { @PackageScope class PackagePrivateClass { + class NestedIntoPackagePrivateClass {} } class PublicClassWhoseNameDoesNotCorrespondToFileName { diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/azurepipelines.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/azurepipelines.json index 9262c329866..594da6d147b 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/azurepipelines.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/azurepipelines.json @@ -677,5 +677,201 @@ "git.commit.message": "azure-pipelines-commit-message", "git.repository_url": "https://dev.azure.com/fabrikamfiber/repo.git" } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@dev.azure.com:1234/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com:1234/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@1.1.1.1:1234/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://1.1.1.1:1234/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@1.1.1.1:1234/fabrikamfiber/repo_with_@_yeah.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://1.1.1.1:1234/fabrikamfiber/repo_with_@_yeah.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user@dev.azure.com/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user@host.xz:port/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user:password@host.xz:port/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user:password@1.1.1.1:port/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://1.1.1.1:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitbucket.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitbucket.json index 4b3c7e52c93..72d47cdff00 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitbucket.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitbucket.json @@ -418,5 +418,138 @@ "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", "git.repository_url": "https://bitbucket.org/DataDog/dogweb.git" } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user@bitbucket.org/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@bitbucket.org:1234/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "ssh://user@host.xz:port/path/to/repo.git/", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "ssh://user:password@host.xz:port/path/to/repo.git/", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitrise.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitrise.json index 5563094dc01..6f5b52cdf90 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitrise.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/bitrise.json @@ -499,5 +499,152 @@ "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", "git.repository_url": "https://github.com/DataDog/dogweb.git" } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "ssh://user@host.xz:port/path/to/repo.git/" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "ssh://user:password@host.xz:port/path/to/repo.git/" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buddy.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buddy.json index 26bf616455b..007cc196652 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buddy.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buddy.json @@ -177,5 +177,245 @@ "git.repository_url": "git@github.com:DataDog/userrepo.git", "git.tag": "v1.0" } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user@github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@github.com:1234/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1:1234/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1:1234/buddyworks/my-project_with_@_yeah.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/buddyworks/my-project_with_@_yeah.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "ssh://user@host.xz:port/path/to/repo.git/" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "ssh://user:password@host.xz:port/path/to/repo.git/" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/", + "git.tag": "v1.0" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buildkite.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buildkite.json index c332fd740d7..421904b20e6 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buildkite.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/buildkite.json @@ -704,6 +704,223 @@ "git.repository_url": "https://github.com/DataDog/dogweb.git" } ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user@github.com/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@github.com:1234/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "ssh://user@host.xz:port/path/to/repo.git/", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "ssh://user:password@host.xz:port/path/to/repo.git/", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], [ { "BUILDKITE": "true", diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/circleci.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/circleci.json index 8efa8c353f0..b9065be3bd6 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/circleci.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/circleci.json @@ -541,5 +541,166 @@ "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", "git.repository_url": "https://github.com/DataDog/dogweb.git" } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "ssh://user@host.xz:port/path/to/repo.git/", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "ssh://user:password@host.xz:port/path/to/repo.git/", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/codefresh.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/codefresh.json index d719df10592..7b1367b4f09 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/codefresh.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/codefresh.json @@ -158,5 +158,167 @@ "git.repository_url": "git@github.com:DataDog/userrepo.git", "git.tag": "0.0.2" } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "ssh://user@host.xz:port/path/to/repo.git/" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_REPOSITORY_URL": "ssh://user:password@host.xz:port/path/to/repo.git/" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/github.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/github.json index e5df52c58ba..3dd5ac62d54 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/github.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/github.json @@ -5,6 +5,7 @@ "GITHUB_JOB": "github-job-name", "GITHUB_REF": "master", "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", "GITHUB_RUN_ID": "ghactions-pipeline-id", "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", "GITHUB_SERVER_URL": "https://ghenterprise.com", @@ -13,13 +14,13 @@ "GITHUB_WORKSPACE": "/foo/bar" }, { - "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://ghenterprise.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\"}", + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://ghenterprise.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", "ci.job.name": "github-job-name", "ci.job.url": "https://ghenterprise.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", "ci.pipeline.id": "ghactions-pipeline-id", "ci.pipeline.name": "ghactions-pipeline-name", "ci.pipeline.number": "ghactions-pipeline-number", - "ci.pipeline.url": "https://ghenterprise.com/ghactions-repo/actions/runs/ghactions-pipeline-id", + "ci.pipeline.url": "https://ghenterprise.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", "ci.provider.name": "github", "ci.workspace_path": "/foo/bar", "git.branch": "master", @@ -560,5 +561,154 @@ "git.repository_url": "git@github.com:DataDog/userrepo.git", "git.tag": "0.0.2" } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user@github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@github.com:1234", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com:1234\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com:1234/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com:1234/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@1.1.1.1", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://1.1.1.1\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://1.1.1.1/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://1.1.1.1/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@1.1.1.1:1234", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://1.1.1.1:1234\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://1.1.1.1:1234/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://1.1.1.1:1234/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/ghactions-repo.git" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/gitlab.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/gitlab.json index c1879ed80bd..400d99c977d 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/gitlab.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/gitlab.json @@ -668,17 +668,7 @@ "CI_PROJECT_DIR": "/foo/bar", "CI_PROJECT_PATH": "gitlab-pipeline-name", "CI_PROJECT_URL": "https://gitlab.com/repo", - "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", - "DD_GIT_BRANCH": "user-supplied-branch", - "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", - "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", - "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", - "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", - "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", - "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", - "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", - "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", - "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "CI_REPOSITORY_URL": "http://user:pwd@hostname.com:1234/repo.git", "GITLAB_CI": "gitlab" }, { @@ -692,16 +682,133 @@ "ci.provider.name": "gitlab", "ci.stage.name": "gitlab-stage-name", "ci.workspace_path": "/foo/bar", - "git.branch": "user-supplied-branch", - "git.commit.author.date": "usersupplied-authordate", - "git.commit.author.email": "usersupplied-authoremail", - "git.commit.author.name": "usersupplied-authorname", - "git.commit.committer.date": "usersupplied-comitterdate", - "git.commit.committer.email": "usersupplied-comitteremail", - "git.commit.committer.name": "usersupplied-comittername", - "git.commit.message": "usersupplied-message", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", - "git.repository_url": "git@github.com:DataDog/userrepo.git" + "git.repository_url": "http://hostname.com:1234/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1:1234/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1:1234/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1:1234/repo_with_@_yeah.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1:1234/repo_with_@_yeah.git" } ], [ @@ -722,6 +829,7 @@ "CI_PROJECT_PATH": "gitlab-pipeline-name", "CI_PROJECT_URL": "https://gitlab.com/repo", "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "DD_GIT_BRANCH": "user-supplied-branch", "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", @@ -731,7 +839,6 @@ "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", - "DD_GIT_TAG": "0.0.2", "GITLAB_CI": "gitlab" }, { @@ -745,7 +852,7 @@ "ci.provider.name": "gitlab", "ci.stage.name": "gitlab-stage-name", "ci.workspace_path": "/foo/bar", - "git.branch": "master", + "git.branch": "user-supplied-branch", "git.commit.author.date": "usersupplied-authordate", "git.commit.author.email": "usersupplied-authoremail", "git.commit.author.name": "usersupplied-authorname", @@ -754,8 +861,7 @@ "git.commit.committer.name": "usersupplied-comittername", "git.commit.message": "usersupplied-message", "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", - "git.repository_url": "git@github.com:DataDog/userrepo.git", - "git.tag": "0.0.2" + "git.repository_url": "git@github.com:DataDog/userrepo.git" } ], [ @@ -775,7 +881,17 @@ "CI_PROJECT_DIR": "/foo/bar", "CI_PROJECT_PATH": "gitlab-pipeline-name", "CI_PROJECT_URL": "https://gitlab.com/repo", - "CI_REPOSITORY_URL": "https://user:password@gitlab.com/DataDog/dogweb.git", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", "GITLAB_CI": "gitlab" }, { @@ -790,12 +906,16 @@ "ci.stage.name": "gitlab-stage-name", "ci.workspace_path": "/foo/bar", "git.branch": "master", - "git.commit.author.date": "2021-07-21T11:43:07-04:00", - "git.commit.author.email": "john@doe.com", - "git.commit.author.name": "John Doe", - "git.commit.message": "gitlab-git-commit-message", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", - "git.repository_url": "https://gitlab.com/DataDog/dogweb.git" + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" } ], [ diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/jenkins.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/jenkins.json index 3e791b5f7ff..f87cdbd2a36 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/jenkins.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/jenkins.json @@ -687,6 +687,153 @@ "git.repository_url": "https://github.com/DataDog/dogweb.git" } ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user@github.com/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@github.com:1234/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "ssh://user@host.xz:port/path/to/repo.git/", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "ssh://user:password@host.xz:port/path/to/repo.git/", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], [ { "BUILD_NUMBER": "jenkins-pipeline-number", diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/teamcity.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/teamcity.json index 086c1c16de1..037887c4ae0 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/teamcity.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/teamcity.json @@ -74,5 +74,117 @@ "git.repository_url": "git@github.com:DataDog/userrepo.git", "git.tag": "0.0.2" } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com/DataDog/dogweb.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "ssh://user@host.xz:port/path/to/repo.git/", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_REPOSITORY_URL": "ssh://user:password@host.xz:port/path/to/repo.git/", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/usersupplied.json b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/usersupplied.json index 3e770927ebf..464c4158558 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/ci/usersupplied.json +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/ci/usersupplied.json @@ -178,5 +178,173 @@ "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", "git.repository_url": "https://github.com/DataDog/dogweb.git" } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "ssh://user@host.xz:port/path/to/repo.git/" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "ssh://user:password@host.xz:port/path/to/repo.git/" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:port/path/to/repo.git/" + } ] ] diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy new file mode 100644 index 00000000000..cff25d8f037 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy @@ -0,0 +1,222 @@ +package datadog.trace.civisibility + +import com.fasterxml.jackson.databind.ObjectMapper +import datadog.communication.serialization.GrowableBuffer +import datadog.communication.serialization.msgpack.MsgPackWriter +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.Config +import datadog.trace.api.civisibility.InstrumentationBridge +import datadog.trace.api.civisibility.config.ModuleExecutionSettings +import datadog.trace.api.civisibility.config.SkippableTest +import datadog.trace.api.config.CiVisibilityConfig +import datadog.trace.api.config.GeneralConfig +import datadog.trace.civisibility.codeowners.Codeowners +import datadog.trace.civisibility.config.JvmInfo +import datadog.trace.civisibility.config.JvmInfoFactoryImpl +import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory +import datadog.trace.civisibility.coverage.SegmentlessTestProbes +import datadog.trace.civisibility.decorator.TestDecorator +import datadog.trace.civisibility.decorator.TestDecoratorImpl +import datadog.trace.civisibility.events.BuildEventsHandlerImpl +import datadog.trace.civisibility.events.TestEventsHandlerImpl +import datadog.trace.civisibility.ipc.SignalServer +import datadog.trace.civisibility.source.MethodLinesResolver +import datadog.trace.civisibility.source.SourcePathResolver +import datadog.trace.civisibility.source.index.RepoIndexBuilder +import datadog.trace.civisibility.writer.ddintake.CiTestCovMapperV2 +import datadog.trace.civisibility.writer.ddintake.CiTestCycleMapperV1 +import datadog.trace.common.writer.RemoteMapper +import datadog.trace.core.DDSpan +import org.msgpack.jackson.dataformat.MessagePackFactory +import spock.lang.Unroll + +import java.lang.reflect.Method +import java.nio.ByteBuffer +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +@Unroll +abstract class CiVisibilityInstrumentationTest extends AgentTestRunner { + + static String dummyModule + static final String DUMMY_CI_TAG = "dummy_ci_tag" + static final String DUMMY_CI_TAG_VALUE = "dummy_ci_tag_value" + static final String DUMMY_SOURCE_PATH = "dummy_source_path" + static final int DUMMY_TEST_METHOD_START = 12 + static final int DUMMY_TEST_METHOD_END = 18 + static final Collection DUMMY_CODE_OWNERS = ["owner1", "owner2"] + + private static Path agentKeyFile + + private static final List skippableTests = new ArrayList<>() + private static volatile boolean itrEnabled = false + + def setupSpec() { + def currentPath = Paths.get("").toAbsolutePath() + def rootPath = currentPath.parent + dummyModule = rootPath.relativize(currentPath) + + def sourcePathResolver = Stub(SourcePathResolver) + sourcePathResolver.getSourcePath(_ as Class) >> DUMMY_SOURCE_PATH + sourcePathResolver.getResourcePath(_ as String) >> { + String path -> path + } + + def codeowners = Stub(Codeowners) + codeowners.getOwners(DUMMY_SOURCE_PATH) >> DUMMY_CODE_OWNERS + + def methodLinesResolver = Stub(MethodLinesResolver) + methodLinesResolver.getLines(_ as Method) >> new MethodLinesResolver.MethodLines(DUMMY_TEST_METHOD_START, DUMMY_TEST_METHOD_END) + + def moduleExecutionSettingsFactory = Stub(ModuleExecutionSettingsFactory) + moduleExecutionSettingsFactory.create(_ as JvmInfo, _ as String) >> { + Map properties = [ + (CiVisibilityConfig.CIVISIBILITY_ITR_ENABLED): String.valueOf(itrEnabled) + ] + return new ModuleExecutionSettings(false, itrEnabled, properties, Collections.singletonMap(dummyModule, skippableTests), Collections.emptyList()) + } + + def coverageProbeStoreFactory = new SegmentlessTestProbes.SegmentlessTestProbesFactory() + DDTestFrameworkSession.Factory testFrameworkSessionFactory = (String projectName, Path projectRoot, String component, Long startTime) -> { + def ciTags = [(DUMMY_CI_TAG): DUMMY_CI_TAG_VALUE] + TestDecorator testDecorator = new TestDecoratorImpl(component, ciTags) + return new DDTestFrameworkSessionImpl( + projectName, + startTime, + Config.get(), + testDecorator, + sourcePathResolver, + codeowners, + methodLinesResolver, + coverageProbeStoreFactory, + moduleExecutionSettingsFactory, + ) + } + + InstrumentationBridge.registerTestEventsHandlerFactory { + component, path -> + DDTestFrameworkSession testSession = testFrameworkSessionFactory.startSession(dummyModule, path, component, null) + DDTestFrameworkModule testModule = testSession.testModuleStart(dummyModule, null) + new TestEventsHandlerImpl(testSession, testModule) + } + + DDBuildSystemSession.Factory buildSystemSessionFactory = (String projectName, Path projectRoot, String startCommand, String component, Long startTime) -> { + def ciTags = [(DUMMY_CI_TAG): DUMMY_CI_TAG_VALUE] + TestDecorator testDecorator = new TestDecoratorImpl(component, ciTags) + TestModuleRegistry testModuleRegistry = new TestModuleRegistry() + SignalServer signalServer = new SignalServer() + RepoIndexBuilder repoIndexBuilder = Stub(RepoIndexBuilder) + return new DDBuildSystemSessionImpl( + projectName, + rootPath.toString(), + startCommand, + startTime, + Config.get(), + testModuleRegistry, + testDecorator, + sourcePathResolver, + codeowners, + methodLinesResolver, + moduleExecutionSettingsFactory, + coverageProbeStoreFactory, + signalServer, + repoIndexBuilder + ) + } + + InstrumentationBridge.registerBuildEventsHandlerFactory { + decorator -> new BuildEventsHandlerImpl<>(buildSystemSessionFactory, new JvmInfoFactoryImpl()) + } + + InstrumentationBridge.registerCoverageProbeStoreRegistry(coverageProbeStoreFactory) + } + + @Override + void setup() { + skippableTests.clear() + itrEnabled = false + } + + def givenSkippableTests(List tests) { + skippableTests.addAll(tests) + itrEnabled = true + } + + @Override + void configurePreAgent() { + super.configurePreAgent() + + agentKeyFile = Files.createTempFile("TestFrameworkTest", "dummy_agent_key") + Files.write(agentKeyFile, "dummy".getBytes()) + + injectSysConfig(GeneralConfig.API_KEY_FILE, agentKeyFile.toString()) + injectSysConfig(CiVisibilityConfig.CIVISIBILITY_ENABLED, "true") + injectSysConfig(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED, "true") + } + + def cleanupSpec() { + Files.deleteIfExists(agentKeyFile) + } + + def assertSpansData(String testcaseName, int expectedTracesCount) { + TEST_WRITER.waitForTraces(expectedTracesCount) + def traces = TEST_WRITER.toList() + + def events = getEventsAsJson(traces) + def coverages = getCoveragesAsJson(traces) + def additionalReplacements = [ + "content.meta.['test.framework_version']": instrumentedLibraryVersion(), + "content.meta.['test.toolchain']": "${instrumentedLibraryName()}:${instrumentedLibraryVersion()}" + ] + + // uncomment to generate expected data templates + // def baseTemplatesPath = CiVisibilityInstrumentationTest.classLoader + // .getResource("test-succeed") + // .toURI() + // .schemeSpecificPart + // .replace('build/resources/test', 'src/test/resources') + // .replace('build/resources/latestDepTest', 'src/test/resources') + // .replace("test-succeed", testcaseName) + // CiVisibilityTestUtils.generateTemplates(baseTemplatesPath, events, coverages, additionalReplacements) + + CiVisibilityTestUtils.assertData(testcaseName, events, coverages, additionalReplacements) + return true + } + + def getEventsAsJson(List> traces) { + return getSpansAsJson(new CiTestCycleMapperV1(Config.get().getWellKnownTags()), traces) + } + + def getCoveragesAsJson(List> traces) { + return getSpansAsJson(new CiTestCovMapperV2(), traces) + } + + def getSpansAsJson(RemoteMapper mapper, List> traces) { + def buffer = new GrowableBuffer(8192) + def writer = new MsgPackWriter(buffer) + def msgPackMapper = new ObjectMapper(new MessagePackFactory()) + def jsonSpans = [] + for (List trace : traces) { + for (DDSpan span : trace) { + writer.format([span], mapper) + + ByteBuffer slicedBuffer = buffer.slice() + buffer.reset() + + if (slicedBuffer.remaining() == 0) { + continue + } + + byte[] bytes = new byte[slicedBuffer.remaining()] + slicedBuffer.get(bytes) + jsonSpans += msgPackMapper.readValue(bytes, Map) + } + } + return jsonSpans + } + + abstract String instrumentedLibraryName() + + abstract String instrumentedLibraryVersion() +} diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy new file mode 100644 index 00000000000..b9e92ab2c0b --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy @@ -0,0 +1,122 @@ +package datadog.trace.civisibility + +import com.fasterxml.jackson.databind.ObjectMapper +import datadog.trace.agent.test.server.http.TestHttpServer +import datadog.trace.test.util.MultipartRequestParser +import org.msgpack.jackson.dataformat.MessagePackFactory +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification +import spock.util.concurrent.PollingConditions + +import java.util.concurrent.ConcurrentLinkedQueue + +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer + +abstract class CiVisibilitySmokeTest extends Specification { + + @Shared + ObjectMapper msgPackMapper = new ObjectMapper(new MessagePackFactory()) + + @Shared + Queue> receivedTraces = new ConcurrentLinkedQueue<>() + + @Shared + Queue> receivedCoverages = new ConcurrentLinkedQueue<>() + + def setup() { + receivedTraces.clear() + receivedCoverages.clear() + } + + def cleanup() { + receivedTraces.clear() + receivedCoverages.clear() + } + + @Shared + @AutoCleanup + protected TestHttpServer intakeServer = httpServer { + handlers { + prefix("/api/v2/citestcycle") { + def decodedEvent = msgPackMapper.readValue(request.body, Map) + receivedTraces.add(decodedEvent) + + response.status(200).send() + } + + prefix("/api/v2/citestcov") { + def parsed = MultipartRequestParser.parseRequest(request.body, request.headers.get("Content-Type")) + def coverages = parsed.get("coverage1") + for (def coverage : coverages) { + def decodedCoverage = msgPackMapper.readValue(coverage.get(), Map) + receivedCoverages.add(decodedCoverage) + } + + response.status(202).send() + } + + prefix("/api/v2/libraries/tests/services/setting") { + response.status(200).send('{ "data": { "type": "ci_app_tracers_test_service_settings", "id": "uuid", "attributes": { "code_coverage": true, "tests_skipping": true } } }') + } + + prefix("/api/v2/ci/tests/skippable") { + response.status(200).send('{ "data": [{' + + ' "id": "d230520a0561ee2f",' + + ' "type": "test",' + + ' "attributes": {' + + ' "configurations": {},' + + ' "name": "test_to_skip_with_itr",' + + ' "suite": "datadog.smoke.TestSucceed"' + + ' }' + + '}] }') + } + } + } + + protected verifyEventsAndCoverages(String projectName, String toolchain, String toolchainVersion, int expectedEventsCount, int expectedCoveragesCount) { + def events = waitForEvents(expectedEventsCount) + def coverages = waitForCoverages(expectedCoveragesCount) + + def additionalReplacements = ["content.meta.['test.toolchain']": "$toolchain:$toolchainVersion"] + + // uncomment to generate expected data templates + // def baseTemplatesPath = AbstractCiVisibilitySmokeTest.classLoader.getResource(projectName).toURI().schemeSpecificPart.replace('build/resources/test', 'src/test/resources') + // CiVisibilityTestUtils.generateTemplates(baseTemplatesPath, events, coverages, additionalReplacements) + + CiVisibilityTestUtils.assertData(projectName, events, coverages, additionalReplacements) + return true + } + + protected List> waitForEvents(int expectedEventsSize) { + def traceReceiveConditions = new PollingConditions(timeout: 15, initialDelay: 1, delay: 0.5, factor: 1) + traceReceiveConditions.eventually { + int eventsSize = 0 + for (Map trace : receivedTraces) { + eventsSize += trace["events"].size() + } + assert eventsSize == expectedEventsSize + } + + List> events = new ArrayList<>() + while (!receivedTraces.isEmpty()) { + def trace = receivedTraces.poll() + events.addAll((List>) trace["events"]) + } + return events + } + + protected List> waitForCoverages(int traceSize) { + def traceReceiveConditions = new PollingConditions(timeout: 15, initialDelay: 1, delay: 0.5, factor: 1) + traceReceiveConditions.eventually { + assert receivedCoverages.size() == traceSize + } + + List> coverages = new ArrayList<>() + while (!receivedCoverages.isEmpty()) { + def trace = receivedCoverages.poll() + coverages.addAll((List>) trace["coverages"]) + } + return coverages + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy deleted file mode 100644 index cd818861261..00000000000 --- a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTest.groovy +++ /dev/null @@ -1,480 +0,0 @@ -package datadog.trace.civisibility - -import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.agent.test.asserts.TraceAssert -import datadog.trace.api.Config -import datadog.trace.api.DDSpanTypes -import datadog.trace.api.DDTags -import datadog.trace.api.civisibility.InstrumentationBridge -import datadog.trace.api.civisibility.config.ModuleExecutionSettings -import datadog.trace.api.civisibility.config.SkippableTest -import datadog.trace.api.config.CiVisibilityConfig -import datadog.trace.api.config.GeneralConfig -import datadog.trace.bootstrap.instrumentation.api.Tags -import datadog.trace.civisibility.codeowners.Codeowners -import datadog.trace.civisibility.config.JvmInfoFactory -import datadog.trace.civisibility.config.ModuleExecutionSettingsFactory -import datadog.trace.civisibility.coverage.NoopCoverageProbeStore -import datadog.trace.civisibility.decorator.TestDecorator -import datadog.trace.civisibility.decorator.TestDecoratorImpl -import datadog.trace.civisibility.events.BuildEventsHandlerImpl -import datadog.trace.civisibility.events.TestEventsHandlerImpl -import datadog.trace.civisibility.ipc.SignalServer -import datadog.trace.civisibility.source.MethodLinesResolver -import datadog.trace.civisibility.source.SourcePathResolver -import datadog.trace.civisibility.source.index.RepoIndexBuilder -import datadog.trace.core.DDSpan -import datadog.trace.util.Strings -import spock.lang.Unroll - -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.util.regex.Pattern - -@Unroll -abstract class CiVisibilityTest extends AgentTestRunner { - - protected static final Comparator> SORT_TRACES_BY_DESC_SIZE_THEN_BY_NAMES = new SortTracesByDescSizeThenByNames() - - static String dummyModule - static final String DUMMY_CI_TAG = "dummy_ci_tag" - static final String DUMMY_CI_TAG_VALUE = "dummy_ci_tag_value" - static final String DUMMY_SOURCE_PATH = "dummy_source_path" - static final int DUMMY_TEST_METHOD_START = 12 - static final int DUMMY_TEST_METHOD_END = 18 - static final Collection DUMMY_CODE_OWNERS = ["owner1", "owner2"] - static final Pattern ANY_MESSAGE = Pattern.compile(".*") - - private static Path agentKeyFile - - private static final List skippableTests = new ArrayList<>() - private static volatile boolean itrEnabled = false - - def setupSpec() { - def currentPath = Paths.get("").toAbsolutePath() - def rootPath = currentPath.parent - dummyModule = rootPath.relativize(currentPath) - - def sourcePathResolver = Stub(SourcePathResolver) - sourcePathResolver.getSourcePath(_) >> DUMMY_SOURCE_PATH - - def codeowners = Stub(Codeowners) - codeowners.getOwners(DUMMY_SOURCE_PATH) >> DUMMY_CODE_OWNERS - - def methodLinesResolver = Stub(MethodLinesResolver) - methodLinesResolver.getLines(_) >> new MethodLinesResolver.MethodLines(DUMMY_TEST_METHOD_START, DUMMY_TEST_METHOD_END) - - def moduleExecutionSettingsFactory = Stub(ModuleExecutionSettingsFactory) - moduleExecutionSettingsFactory.create(_, _) >> { - Map properties = [ - (CiVisibilityConfig.CIVISIBILITY_ITR_ENABLED): String.valueOf(itrEnabled) - ] - return new ModuleExecutionSettings(false, itrEnabled, properties, Collections.singletonMap(dummyModule, skippableTests), Collections.emptyList()) - } - - def coverageProbeStoreFactory = new NoopCoverageProbeStore.NoopCoverageProbeStoreFactory() - DDTestFrameworkSession.Factory testFrameworkSessionFactory = (String projectName, Path projectRoot, String component, Long startTime) -> { - def ciTags = [(DUMMY_CI_TAG): DUMMY_CI_TAG_VALUE] - TestDecorator testDecorator = new TestDecoratorImpl(component, ciTags) - return new DDTestFrameworkSessionImpl( - projectName, - startTime, - Config.get(), - testDecorator, - sourcePathResolver, - codeowners, - methodLinesResolver, - coverageProbeStoreFactory, - moduleExecutionSettingsFactory, - ) - } - - InstrumentationBridge.registerTestEventsHandlerFactory { - component, path -> - DDTestFrameworkSession testSession = testFrameworkSessionFactory.startSession(dummyModule, path, component, null) - DDTestFrameworkModule testModule = testSession.testModuleStart(dummyModule, null) - new TestEventsHandlerImpl(testSession, testModule) - } - - DDBuildSystemSession.Factory buildSystemSessionFactory = (String projectName, Path projectRoot, String startCommand, String component, Long startTime) -> { - def ciTags = [(DUMMY_CI_TAG): DUMMY_CI_TAG_VALUE] - TestDecorator testDecorator = new TestDecoratorImpl(component, ciTags) - TestModuleRegistry testModuleRegistry = new TestModuleRegistry() - SignalServer signalServer = new SignalServer() - RepoIndexBuilder repoIndexBuilder = Stub(RepoIndexBuilder) - return new DDBuildSystemSessionImpl( - projectName, - rootPath.toString(), - startCommand, - startTime, - Config.get(), - testModuleRegistry, - testDecorator, - sourcePathResolver, - codeowners, - methodLinesResolver, - moduleExecutionSettingsFactory, - coverageProbeStoreFactory, - signalServer, - repoIndexBuilder - ) - } - - InstrumentationBridge.registerBuildEventsHandlerFactory { - decorator -> new BuildEventsHandlerImpl<>(buildSystemSessionFactory, new JvmInfoFactory()) - } - - InstrumentationBridge.registerCoverageProbeStoreRegistry(coverageProbeStoreFactory) - } - - @Override - void setup() { - skippableTests.clear() - itrEnabled = false - } - - def givenSkippableTests(List tests) { - skippableTests.addAll(tests) - itrEnabled = true - } - - @Override - void configurePreAgent() { - super.configurePreAgent() - - agentKeyFile = Files.createTempFile("TestFrameworkTest", "dummy_agent_key") - Files.write(agentKeyFile, "dummy".getBytes()) - - injectSysConfig(GeneralConfig.API_KEY_FILE, agentKeyFile.toString()) - injectSysConfig(CiVisibilityConfig.CIVISIBILITY_ENABLED, "true") - injectSysConfig(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED, "true") - } - - def cleanupSpec() { - Files.deleteIfExists(agentKeyFile) - } - - Long testSessionSpan(final TraceAssert trace, - final int index, - final String testStatus, - final Map testTags = null, - final String resource = null, - final String testCommand = null, - final String testToolchain = null, - final Throwable exception = null, - final boolean verifyExceptionMessage = true) { - def testFramework = expectedTestFramework() - def testFrameworkVersion = expectedTestFrameworkVersion() - - def testSessionId - trace.span(index) { - testSessionId = span.getTag(Tags.TEST_SESSION_ID) - - parent() - operationName expectedOperationPrefix() + ".test_session" - resourceName resource ? resource : dummyModule - spanType DDSpanTypes.TEST_SESSION_END - errored exception != null - duration({ it > 1L }) - tags { - "$Tags.COMPONENT" component - "$Tags.SPAN_KIND" Tags.SPAN_KIND_TEST_SESSION - "$Tags.TEST_TYPE" TestDecorator.TEST_TYPE - if (testCommand) { - "$Tags.TEST_COMMAND" testCommand - } else { - // the default command for sessions that run without build system instrumentation - "$Tags.TEST_COMMAND" dummyModule - } - if (testToolchain) { - "$Tags.TEST_TOOLCHAIN" testToolchain - } - "$Tags.TEST_FRAMEWORK" testFramework - if (testFrameworkVersion) { - "$Tags.TEST_FRAMEWORK_VERSION" testFrameworkVersion - } - "$Tags.TEST_STATUS" testStatus - if (testTags) { - testTags.each { key, val -> tag(key, val) } - } - - if (exception) { - if (verifyExceptionMessage) { - errorTags(exception.class, exception.message) - } else { - errorTags(exception.class, ANY_MESSAGE) - } - } - - "$DUMMY_CI_TAG" DUMMY_CI_TAG_VALUE - - "$Tags.ENV" String - "$Tags.OS_VERSION" String - "$Tags.OS_PLATFORM" String - "$Tags.OS_ARCHITECTURE" String - "$Tags.RUNTIME_VENDOR" String - "$Tags.RUNTIME_NAME" String - "$Tags.RUNTIME_VERSION" String - "$DDTags.LIBRARY_VERSION_TAG_KEY" String - - "$Tags.TEST_SESSION_ID" Long - - defaultTags() - } - } - return testSessionId - } - - Long testModuleSpan(final TraceAssert trace, - final int index, - final Long testSessionId, - final String testStatus, - final Map testTags = null, - final Throwable exception = null, - final String resource = null, - final boolean verifyExceptionMessage = true) { - def testFramework = expectedTestFramework() - def testFrameworkVersion = expectedTestFrameworkVersion() - - def testModuleId - trace.span(index) { - testModuleId = span.getTag(Tags.TEST_MODULE_ID) - - parentSpanId(BigInteger.valueOf(testSessionId)) - operationName expectedOperationPrefix() + ".test_module" - resourceName resource ? resource : dummyModule - spanType DDSpanTypes.TEST_MODULE_END - errored exception != null - duration({ it > 1L }) - tags { - "$Tags.COMPONENT" component - "$Tags.SPAN_KIND" Tags.SPAN_KIND_TEST_MODULE - "$Tags.TEST_TYPE" TestDecorator.TEST_TYPE - "$Tags.TEST_MODULE" resource ? resource : dummyModule - "$Tags.TEST_FRAMEWORK" testFramework - if (testFrameworkVersion) { - "$Tags.TEST_FRAMEWORK_VERSION" testFrameworkVersion - } - "$Tags.TEST_STATUS" testStatus - if (testTags) { - testTags.each { key, val -> tag(key, val) } - } - - if (exception) { - if (verifyExceptionMessage) { - errorTags(exception.class, exception.message) - } else { - errorTags(exception.class, ANY_MESSAGE) - } - } - - "$DUMMY_CI_TAG" DUMMY_CI_TAG_VALUE - - "$Tags.ENV" String - "$Tags.OS_VERSION" String - "$Tags.OS_PLATFORM" String - "$Tags.OS_ARCHITECTURE" String - "$Tags.RUNTIME_VENDOR" String - "$Tags.RUNTIME_NAME" String - "$Tags.RUNTIME_VERSION" String - "$DDTags.LIBRARY_VERSION_TAG_KEY" String - - "$Tags.TEST_MODULE_ID" Long - - if (testSessionId) { - "$Tags.TEST_SESSION_ID" testSessionId - } - - defaultTags() - } - } - return testModuleId - } - - Long testSuiteSpan(final TraceAssert trace, - final int index, - final Long testSessionId, - final Long testModuleId, - final String testSuite, - final String testStatus, - final Map testTags = null, - final Throwable exception = null, - final boolean emptyDuration = false, - final Collection categories = null, - final boolean sourceFilePresent = true) { - def testFramework = expectedTestFramework() - def testFrameworkVersion = expectedTestFrameworkVersion() - - def testSuiteId - trace.span(index) { - testSuiteId = span.getTag(Tags.TEST_SUITE_ID) - - parentSpanId(BigInteger.valueOf(testModuleId)) - operationName expectedOperationPrefix() + ".test_suite" - resourceName testSuite - spanType DDSpanTypes.TEST_SUITE_END - errored exception != null - if (emptyDuration) { - duration({ it == 1L }) - } else { - duration({ it > 1L }) - } - tags { - "$Tags.COMPONENT" component - "$Tags.SPAN_KIND" Tags.SPAN_KIND_TEST_SUITE - "$Tags.TEST_TYPE" TestDecorator.TEST_TYPE - "$Tags.TEST_SESSION_ID" testSessionId - "$Tags.TEST_MODULE_ID" testModuleId - "$Tags.TEST_MODULE" dummyModule - "$Tags.TEST_SUITE" testSuite - "$Tags.TEST_FRAMEWORK" testFramework - if (testFrameworkVersion) { - "$Tags.TEST_FRAMEWORK_VERSION" testFrameworkVersion - } - "$Tags.TEST_STATUS" testStatus - if (testTags) { - testTags.each { key, val -> tag(key, val) } - } - if (sourceFilePresent) { - "$Tags.TEST_SOURCE_FILE" DUMMY_SOURCE_PATH - } - - if (exception) { - errorTags(exception.class, exception.message) - } - - if (categories) { - "$Tags.TEST_TRAITS" Strings.toJson(["category": Strings.toJson(categories)], true) - } - - "$DUMMY_CI_TAG" DUMMY_CI_TAG_VALUE - - "$Tags.ENV" String - "$Tags.OS_VERSION" String - "$Tags.OS_PLATFORM" String - "$Tags.OS_ARCHITECTURE" String - "$Tags.RUNTIME_VENDOR" String - "$Tags.RUNTIME_NAME" String - "$Tags.RUNTIME_VERSION" String - "$DDTags.LIBRARY_VERSION_TAG_KEY" String - - "$Tags.TEST_SUITE_ID" Long - - defaultTags() - } - } - return testSuiteId - } - - long testSpan(final TraceAssert trace, - final int index, - final Long testSessionId, - final Long testModuleId, - final Long testSuiteId, - final String testSuite, - final String testName, - final String testMethod, - final String testStatus, - final Map testTags = null, - final Throwable exception = null, - final boolean emptyDuration = false, - final Collection categories = null, - final boolean sourceFilePresent = true, - final boolean sourceMethodPresent = true) { - def testFramework = expectedTestFramework() - def testFrameworkVersion = expectedTestFrameworkVersion() - - def testId - trace.span(index) { - testId = span.getSpanId() - - parent() - operationName expectedOperationPrefix() + ".test" - resourceName "$testSuite.$testName" - spanType DDSpanTypes.TEST - errored exception != null - if (emptyDuration) { - duration({ it == 1L }) - } else { - duration({ it > 1L }) - } - tags { - "$Tags.COMPONENT" component - "$Tags.SPAN_KIND" Tags.SPAN_KIND_TEST - "$Tags.TEST_TYPE" TestDecorator.TEST_TYPE - "$Tags.TEST_SESSION_ID" testSessionId - "$Tags.TEST_MODULE_ID" testModuleId - "$Tags.TEST_SUITE_ID" testSuiteId - "$Tags.TEST_MODULE" dummyModule - "$Tags.TEST_SUITE" testSuite - "$Tags.TEST_NAME" testName - "$Tags.TEST_FRAMEWORK" testFramework - "$Tags.TEST_FRAMEWORK_VERSION" testFrameworkVersion - "$Tags.TEST_STATUS" testStatus - if (testTags) { - testTags.each { key, val -> tag(key, val) } - } - - if (sourceFilePresent) { - "$Tags.TEST_SOURCE_FILE" DUMMY_SOURCE_PATH - "$Tags.TEST_CODEOWNERS" Strings.toJson(DUMMY_CODE_OWNERS) - } - - if (sourceMethodPresent) { - "$Tags.TEST_SOURCE_METHOD" testMethod - "$Tags.TEST_SOURCE_START" DUMMY_TEST_METHOD_START - "$Tags.TEST_SOURCE_END" DUMMY_TEST_METHOD_END - } - - if (exception) { - errorTags(exception.class, exception.message) - } - - if (categories) { - "$Tags.TEST_TRAITS" Strings.toJson(["category": Strings.toJson(categories)], true) - } - - "$DUMMY_CI_TAG" DUMMY_CI_TAG_VALUE - - "$Tags.ENV" String - "$Tags.OS_VERSION" String - "$Tags.OS_PLATFORM" String - "$Tags.OS_ARCHITECTURE" String - "$Tags.RUNTIME_VENDOR" String - "$Tags.RUNTIME_NAME" String - "$Tags.RUNTIME_VERSION" String - "$DDTags.LIBRARY_VERSION_TAG_KEY" String - - defaultTags() - } - } - return testId - } - - String component = component() - - abstract String expectedOperationPrefix() - - abstract String expectedTestFramework() - - abstract String expectedTestFrameworkVersion() - - abstract String component() - - private static class SortTracesByDescSizeThenByNames implements Comparator> { - @Override - int compare(List o1, List o2) { - if (o1.size() != o2.size()) { - return o2.size() - o1.size() - } - return rootSpanTrace(o1) <=> rootSpanTrace(o2) - } - - String rootSpanTrace(List trace) { - assert !trace.isEmpty() - def rootSpan = trace.get(0).localRootSpan - return "${rootSpan.serviceName}/${rootSpan.operationName}/${rootSpan.resourceName}" - } - } -} diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy new file mode 100644 index 00000000000..05085095b2d --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy @@ -0,0 +1,229 @@ +package datadog.trace.civisibility + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.jayway.jsonpath.Configuration +import com.jayway.jsonpath.JsonPath +import com.jayway.jsonpath.Option +import com.jayway.jsonpath.ReadContext +import com.jayway.jsonpath.WriteContext +import freemarker.template.Template +import freemarker.template.TemplateExceptionHandler +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode + +import java.nio.file.Files +import java.nio.file.Paths +import java.util.regex.Pattern + +abstract class CiVisibilityTestUtils { + + static final List EVENT_DYNAMIC_PATHS = [ + path("content.trace_id"), + path("content.span_id"), + path("content.parent_id"), + path("content.test_session_id"), + path("content.test_module_id"), + path("content.test_suite_id"), + path("content.metrics.process_id"), + path("content.meta.['os.architecture']"), + path("content.meta.['os.platform']"), + path("content.meta.['os.version']"), + path("content.meta.['runtime.name']"), + path("content.meta.['runtime.vendor']"), + path("content.meta.['runtime.version']"), + path("content.meta.['ci.workspace_path']"), + path("content.meta.['error.message']"), + path("content.meta.['error.stack']"), + path("content.meta.library_version"), + path("content.meta.runtime-id"), + // Different events might or might not have the same start or duration. + // Regardless, the values of these fields should be treated as different + path("content.start", false), + path("content.duration", false), + path("content.meta.['_dd.p.tid']", false), + ] + + static final List COVERAGE_DYNAMIC_PATHS = [path("test_session_id"), path("test_suite_id"), path("span_id"),] + + private static final Comparator> EVENT_RESOURCE_COMPARATOR = Comparator.comparing((Map m) -> { + def content = (Map) m.get("content") + return content.get("resource") + }) + + /** + * Use this method to generate expected data templates + */ + static void generateTemplates(String baseTemplatesPath, List> events, List> coverages, Map additionalReplacements) { + events.sort(EVENT_RESOURCE_COMPARATOR) + + def templateGenerator = new TemplateGenerator(new LabelGenerator()) + def compiledAdditionalReplacements = compile(additionalReplacements.keySet()) + + Files.createDirectories(Paths.get(baseTemplatesPath)) + Files.write(Paths.get(baseTemplatesPath, "events.ftl"), templateGenerator.generateTemplate(events, EVENT_DYNAMIC_PATHS + compiledAdditionalReplacements).bytes) + Files.write(Paths.get(baseTemplatesPath, "coverages.ftl"), templateGenerator.generateTemplate(coverages, COVERAGE_DYNAMIC_PATHS + compiledAdditionalReplacements).bytes) + } + + static void assertData(String baseTemplatesPath, List> events, List> coverages, Map additionalReplacements) { + events.sort(EVENT_RESOURCE_COMPARATOR) + + def labelGenerator = new LabelGenerator() + def templateGenerator = new TemplateGenerator(labelGenerator) + + def replacementMap + replacementMap = templateGenerator.generateReplacementMap(events, EVENT_DYNAMIC_PATHS) + replacementMap = templateGenerator.generateReplacementMap(coverages, COVERAGE_DYNAMIC_PATHS) + + for (Map.Entry e : additionalReplacements.entrySet()) { + replacementMap.put(labelGenerator.forKey(e.key), "\"$e.value\"") + } + + def expectedEvents = getFreemarkerTemplate(baseTemplatesPath + "/events.ftl", replacementMap) + def actualEvents = JSON_MAPPER.writeValueAsString(events) + try { + JSONAssert.assertEquals(expectedEvents, actualEvents, JSONCompareMode.LENIENT) + } catch (AssertionError e) { + throw new AssertionError("Error while comparing expected events $expectedEvents to actual events $actualEvents", e) + } + + def expectedCoverages = getFreemarkerTemplate(baseTemplatesPath + "/coverages.ftl", replacementMap) + def actualCoverages = JSON_MAPPER.writeValueAsString(coverages) + try { + JSONAssert.assertEquals(expectedCoverages, actualCoverages, JSONCompareMode.LENIENT) + } catch (AssertionError e) { + throw new AssertionError("Error while comparing expected coverages $expectedCoverages to actual coverages $actualCoverages", e) + } + } + + static final Configuration JSON_PATH_CONFIG = Configuration.builder() + .options(Option.SUPPRESS_EXCEPTIONS) + .build() + + static final ObjectMapper JSON_MAPPER = new ObjectMapper() { { + enable(SerializationFeature.INDENT_OUTPUT) + } + } + + static final freemarker.template.Configuration FREEMARKER = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_30) { { + setClassLoaderForTemplateLoading(CiVisibilityTestUtils.classLoader, "") + setDefaultEncoding("UTF-8") + setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER) + setLogTemplateExceptions(true) + setWrapUncheckedExceptions(true) + setFallbackOnNullLoopVariable(false) + setNumberFormat("0.######") + } + } + + private static String getFreemarkerTemplate(String templatePath, Map replacements) { + Template coveragesTemplate = FREEMARKER.getTemplate(templatePath) + StringWriter coveragesOut = new StringWriter() + coveragesTemplate.process(replacements, coveragesOut) + return coveragesOut.toString() + } + + private static final class TemplateGenerator { + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\"(\\\$\\{.*?\\})\"") + + private final Map uniqueValues = new HashMap<>() + private final Map nonUniqueValues = new HashMap<>() + private final LabelGenerator label + + TemplateGenerator(LabelGenerator label) { + this.label = label + } + + String generateTemplate(Collection> objects, List dynamicPaths) { + for (Map object : objects) { + WriteContext ctx = JsonPath.parse(object, JSON_PATH_CONFIG) + for (DynamicPath dynamicPath : dynamicPaths) { + ctx.map(dynamicPath.path, (currentValue, config) -> { + if (dynamicPath.unique) { + return uniqueValues.computeIfAbsent(currentValue, (k) -> label.forTemplateKey(dynamicPath.rawPath)) + } else { + return label.forTemplateKey(dynamicPath.rawPath) + } + }) + } + + } + return JSON_MAPPER + .writeValueAsString(objects) + .replaceAll(PLACEHOLDER_PATTERN, "\$1") // remove quotes around placeholders + } + + Map generateReplacementMap(Collection> objects, List dynamicPaths) { + for (Map object : objects) { + ReadContext ctx = JsonPath.parse(object, JSON_PATH_CONFIG) + for (DynamicPath dynamicPath : dynamicPaths) { + def value = ctx.read(dynamicPath.path) + if (value != null) { + if (value instanceof String) { + value = '"' + // restore quotes around string values + value.replace('"', '\\"') + // escape quotes inside string values + '"' // restore quotes around string values + } + if (dynamicPath.unique) { + uniqueValues.computeIfAbsent(value, (k) -> label.forKey(dynamicPath.rawPath)) + } else { + nonUniqueValues.put(label.forKey(dynamicPath.rawPath), value) + } + } + } + } + return invert(uniqueValues) + nonUniqueValues + } + } + + private static final class LabelGenerator { + private static final Pattern ERASED_CHARS = Pattern.compile("[\\[\\]']") + private static final Pattern REPLACED_CHARS = Pattern.compile("[.-]") + + private final Map usageCounters = new HashMap<>() + + String forTemplateKey(String key) { + return "\${" + forKey(key) + "}" + } + + String forKey(String key) { + def usages = usageCounters.merge(key, 1, Integer::sum) + def sanitizedKey = key.replaceAll(ERASED_CHARS, "").replaceAll(REPLACED_CHARS, "_") + return sanitizedKey + (usages == 1 ? "" : "_${usages}") + } + } + + private static Map invert(Map map) { + Map inverted = new HashMap(map.size()) + for (Map.Entry e : map.entrySet()) { + inverted.put(e.value, e.key) + } + return inverted + } + + private static List compile(Iterable rawPaths) { + def compiledPaths = [] + for (String rawPath : rawPaths) { + compiledPaths += path(rawPath) + } + return compiledPaths + } + + private static DynamicPath path(String rawPath, boolean unique = true) { + return new DynamicPath(rawPath, JsonPath.compile(rawPath), unique) + } + + private static final class DynamicPath { + private final String rawPath + private final JsonPath path + // if true, same values are replaced with same placeholders; + // otherwise every path gets its own placeholder, even if its value is non-unique + private final boolean unique + + DynamicPath(String rawPath, JsonPath path, boolean unique) { + this.rawPath = rawPath + this.path = path + this.unique = unique + } + } +} diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java index 707877540f9..2562372dee8 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CapturedContext.java @@ -206,12 +206,13 @@ public void addReturn(CapturedValue retValue) { if (locals == null) { locals = new HashMap<>(); } - locals.put("@return", retValue); // special local name for the return value + locals.put(ValueReferences.RETURN_REF, retValue); // special local name for the return value extensions.put(ValueReferences.RETURN_EXTENSION_NAME, retValue); } public void addThrowable(Throwable t) { - this.throwable = new CapturedThrowable(t); + addThrowable(new CapturedThrowable(t)); + extensions.put(ValueReferences.EXCEPTION_EXTENSION_NAME, t); } public void addThrowable(CapturedThrowable capturedThrowable) { @@ -327,26 +328,14 @@ public Status evaluate( ValueReferences.DURATION_EXTENSION_NAME, duration / 1_000_000.0); // convert to ms } this.thisClassName = thisClassName; - boolean shouldEvaluate = resolveEvaluateAt(probeImplementation, methodLocation); + boolean shouldEvaluate = + MethodLocation.isSame(methodLocation, probeImplementation.getEvaluateAt()); if (shouldEvaluate) { - probeImplementation.evaluate(this, status); + probeImplementation.evaluate(this, status, methodLocation); } return status; } - private static boolean resolveEvaluateAt( - ProbeImplementation probeImplementation, MethodLocation methodLocation) { - if (methodLocation == MethodLocation.DEFAULT) { - // line probe, no evaluation of probe's evaluateAt - return true; - } - MethodLocation localEvaluateAt = probeImplementation.getEvaluateAt(); - if (methodLocation == MethodLocation.ENTRY) { - return localEvaluateAt == MethodLocation.DEFAULT || localEvaluateAt == MethodLocation.ENTRY; - } - return localEvaluateAt == methodLocation; - } - public Status getStatus(String probeId) { Status result = statusByProbeId.get(probeId); if (result == null) { diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java index ed56d4ca89f..085f814ed31 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/CorrelationAccess.java @@ -30,7 +30,7 @@ private CorrelationAccess() { MethodHandle traceIdHandle = null; MethodHandle spanIdHandle = null; // ignore correlations if tracer is not enabled - if (ConfigProvider.getInstance().getBoolean(TraceInstrumentationConfig.TRACE_ENABLED, false)) { + if (ConfigProvider.getInstance().getBoolean(TraceInstrumentationConfig.TRACE_ENABLED, true)) { try { Class clz = ClassLoader.getSystemClassLoader().loadClass(CORRELATION_IDENTIFIER_CLASSNAME); diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java index e2009f95e5f..2ec97949aad 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java @@ -191,19 +191,9 @@ public static DebuggerSpan createSpan(String operationName, String[] tags) { * * @return true if can proceed to capture data */ - public static boolean isReadyToCapture(String... probeIds) { - // TODO provide overloaded version without string array + public static boolean isReadyToCapture(Class callingClass, String... probeIds) { try { - if (probeIds == null || probeIds.length == 0) { - return false; - } - boolean result = false; - for (String probeId : probeIds) { - // if all probes are rate limited, we don't capture - result |= ProbeRateLimiter.tryProbe(probeId); - } - result = result && checkAndSetInProbe(); - return result; + return checkAndSetInProbe(); } catch (Exception ex) { LOGGER.debug("Error in isReadyToCapture: ", ex); return false; diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java index f07868286c4..7bceadf702d 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/MethodLocation.java @@ -3,5 +3,17 @@ public enum MethodLocation { DEFAULT, ENTRY, - EXIT + EXIT; + + public static boolean isSame(MethodLocation methodLocation, MethodLocation evaluateAt) { + if (methodLocation == MethodLocation.DEFAULT) { + // line probe, no evaluation of probe's evaluateAt + // MethodLocation.DEFAULT is used for line probe when evaluating the context + return true; + } + if (methodLocation == MethodLocation.ENTRY) { + return evaluateAt == MethodLocation.DEFAULT || evaluateAt == MethodLocation.ENTRY; + } + return methodLocation == evaluateAt; + } } diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeImplementation.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeImplementation.java index 80445b61fec..617b3aa6093 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeImplementation.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeImplementation.java @@ -16,7 +16,8 @@ public interface ProbeImplementation { String getStrTags(); - void evaluate(CapturedContext context, CapturedContext.Status status); + void evaluate( + CapturedContext context, CapturedContext.Status status, MethodLocation methodLocation); void commit( CapturedContext entryContext, @@ -84,7 +85,8 @@ public String getStrTags() { } @Override - public void evaluate(CapturedContext context, CapturedContext.Status status) {} + public void evaluate( + CapturedContext context, CapturedContext.Status status, MethodLocation methodLocation) {} @Override public void commit( diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeRateLimiter.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeRateLimiter.java index 211a1ee34f1..e7c7aa22f8d 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeRateLimiter.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/ProbeRateLimiter.java @@ -7,6 +7,7 @@ import java.time.temporal.ChronoUnit; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.DoubleFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ public class ProbeRateLimiter { new ConcurrentHashMap<>(); private static Sampler GLOBAL_SNAPSHOT_SAMPLER = createSampler(DEFAULT_GLOBAL_SNAPSHOT_RATE); private static Sampler GLOBAL_LOG_SAMPLER = createSampler(DEFAULT_GLOBAL_LOG_RATE); + private static DoubleFunction samplerSupplier = ProbeRateLimiter::createSampler; public static boolean tryProbe(String probeId) { RateLimitInfo rateLimitInfo = @@ -37,19 +39,19 @@ public static boolean tryProbe(String probeId) { private static RateLimitInfo getDefaultRateLimitInfo(String probeId) { LOGGER.debug("Setting sampling with default snapshot rate for probeId={}", probeId); - return new RateLimitInfo(createSampler(DEFAULT_SNAPSHOT_RATE), true); + return new RateLimitInfo(samplerSupplier.apply(DEFAULT_SNAPSHOT_RATE), true); } public static void setRate(String probeId, double rate, boolean isCaptureSnapshot) { - PROBE_SAMPLERS.put(probeId, new RateLimitInfo(createSampler(rate), isCaptureSnapshot)); + PROBE_SAMPLERS.put(probeId, new RateLimitInfo(samplerSupplier.apply(rate), isCaptureSnapshot)); } public static void setGlobalSnapshotRate(double rate) { - GLOBAL_SNAPSHOT_SAMPLER = createSampler(rate); + GLOBAL_SNAPSHOT_SAMPLER = samplerSupplier.apply(rate); } public static void setGlobalLogRate(double rate) { - GLOBAL_LOG_SAMPLER = createSampler(rate); + GLOBAL_LOG_SAMPLER = samplerSupplier.apply(rate); } public static void resetRate(String probeId) { @@ -65,15 +67,20 @@ public static void resetAll() { resetGlobalRate(); } + public static void setSamplerSupplier(DoubleFunction samplerSupplier) { + ProbeRateLimiter.samplerSupplier = + samplerSupplier != null ? samplerSupplier : ProbeRateLimiter::createSampler; + } + private static Sampler createSampler(double rate) { if (rate < 0) { return new ConstantSampler(true); } if (rate < 1) { int intRate = (int) Math.round(rate * 10); - return new AdaptiveSampler(TEN_SECONDS_WINDOW, intRate, 180, 16); + return new AdaptiveSampler(TEN_SECONDS_WINDOW, intRate, 180, 16, true); } - return new AdaptiveSampler(ONE_SECOND_WINDOW, (int) Math.round(rate), 180, 16); + return new AdaptiveSampler(ONE_SECOND_WINDOW, (int) Math.round(rate), 180, 16, true); } private static class RateLimitInfo { diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/el/ValueReferences.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/el/ValueReferences.java index 40ca3217251..2903bac33fc 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/el/ValueReferences.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/el/ValueReferences.java @@ -13,9 +13,11 @@ public final class ValueReferences { public static String DURATION_EXTENSION_NAME = "duration"; public static String RETURN_EXTENSION_NAME = "return"; public static String ITERATOR_EXTENSION_NAME = "it"; + public static String EXCEPTION_EXTENSION_NAME = "exception"; public static String DURATION_REF = SYNTHETIC_PREFIX + DURATION_EXTENSION_NAME; public static String RETURN_REF = SYNTHETIC_PREFIX + RETURN_EXTENSION_NAME; public static String ITERATOR_REF = SYNTHETIC_PREFIX + ITERATOR_EXTENSION_NAME; + public static String EXCEPTION_REF = SYNTHETIC_PREFIX + EXCEPTION_EXTENSION_NAME; public static String synthetic(String name) { return SYNTHETIC_PREFIX + name; diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java index 2d746395f68..69b720c60a9 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/Redaction.java @@ -13,35 +13,92 @@ public class Redaction { // Need to be a unique instance (new String) for reference equality (==) and // avoid internalization (intern) by the JVM because it's a string constant - public static final String REDACTED_VALUE = new String("REDACTED".toCharArray()); + public static final String REDACTED_VALUE = new String("redacted".toCharArray()); private static final Pattern COMMA_PATTERN = Pattern.compile(","); private static final List PREDEFINED_KEYWORDS = Arrays.asList( - "password", - "passwd", - "secret", + "2fa", + "accesstoken", + "aiohttpsession", + "apisecret", + "apisignature", "apikey", "auth", + "authtoken", + "authorization", + "ccnumber", + "certificatepin", + "cipher", + "clientid", + "clientsecret", + "config", + "connect.sid", + "cookie", "credentials", + "creditcard", + "csrf", + "csrftoken", + "cvv", + "databaseurl", + "dburl", + "encryptionkey", + "encryptionkeyid", + "env", + "gpgkey", + "jti", + "jwt", + "licensekey", + "masterkey", "mysqlpwd", + "nonce", + "oauth", + "oauth_token", + "otp", + "passhash", + "passwd", + "password", + "passwordb", + "pemfile", + "pgpkey", + "phpsessid", + "pin", + "pincode", + "pkcs8", "privatekey", - "token", - "ipaddress", + "publickey", + "recaptchakey", + "refreshtoken", + "routingnumber", + "salt", + "secret", + "secrettoken", + "secretKey", + "securityanswer", + "securitycode", + "securityquestion", + "serviceaccountcredentials", "session", - // django - "csrftoken", + "sessionkey", "sessionid", - // wsgi - "remoteaddr", - "xcsrftoken", - "xforwardedfor", "setcookie", - "cookie", - "authorization", + "signature", + "signaturekey", + "sshkey", + "ssn", + "symfony", + "token", + "transactionid", + "twiliotoken", + "usersession", + "voterid", "xapikey", + "xcsrftoken", "xforwardedfor", - "xrealip"); + "xrealip", + "xauthtoken", + "xsrftoken", + "pwd"); private static final Set KEYWORDS = ConcurrentHashMap.newKeySet(); private static ClassNameTrie typeTrie = ClassNameTrie.Builder.EMPTY_TRIE; private static List redactedClasses; diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java index b5aa5a0b5fa..5390ef5a6d5 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java @@ -43,6 +43,18 @@ public class WellKnownClasses { "java.util.concurrent.atomic.AtomicLong")); } + private static Set stringPrimitives = + new HashSet<>( + Arrays.asList( + "java.lang.Class", + "java.lang.String", + "java.time.Duration", + "java.time.Instant", + "java.time.LocalTime", + "java.time.LocalDate", + "java.time.LocalDateTime", + "java.util.UUID")); + /** * @return true if type is a final class and toString implementation is well known and side effect * free @@ -85,4 +97,8 @@ public static boolean isSizeSafe(Map map) { } return false; } + + public static boolean isStringPrimitive(String type) { + return stringPrimitives.contains(type); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/DSL.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/DSL.java index c23704ec325..95efa57412d 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/DSL.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/DSL.java @@ -15,6 +15,7 @@ import com.datadog.debugger.el.expressions.IfExpression; import com.datadog.debugger.el.expressions.IndexExpression; import com.datadog.debugger.el.expressions.IsEmptyExpression; +import com.datadog.debugger.el.expressions.IsUndefinedExpression; import com.datadog.debugger.el.expressions.LenExpression; import com.datadog.debugger.el.expressions.MatchesExpression; import com.datadog.debugger.el.expressions.NotExpression; @@ -205,4 +206,8 @@ public static WhenExpression when(BooleanExpression expression) { public static BooleanValueExpressionAdapter bool(BooleanExpression expression) { return new BooleanValueExpressionAdapter(expression); } + + public static IsUndefinedExpression isUndefined(ValueExpression valueExpression) { + return new IsUndefinedExpression(valueExpression); + } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/JsonToExpressionConverter.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/JsonToExpressionConverter.java index 6f6eee3b1bb..0fec4f7cd05 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/JsonToExpressionConverter.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/JsonToExpressionConverter.java @@ -214,6 +214,15 @@ private static BooleanExpression internalCreatePredicate(JsonReader reader, Stri } return DSL.isEmpty(asValueExpression(reader)); } + case "isUndefined": + { + JsonReader.Token token = reader.peek(); + if (token == BEGIN_ARRAY) { + throw new UnsupportedOperationException( + "Operation 'isUndefined' expects exactly one value argument"); + } + return DSL.isUndefined(asValueExpression(reader)); + } case "startsWith": { return createStringPredicateExpression(reader, DSL::startsWith); diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/RedactedException.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/RedactedException.java new file mode 100644 index 00000000000..a2ec6cdb02d --- /dev/null +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/RedactedException.java @@ -0,0 +1,7 @@ +package com.datadog.debugger.el; + +public class RedactedException extends EvaluationException { + public RedactedException(String message, String expr) { + super(message, expr); + } +} diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ExpressionHelper.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ExpressionHelper.java index e77f17286dd..43d5930dcb6 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ExpressionHelper.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ExpressionHelper.java @@ -1,13 +1,13 @@ package com.datadog.debugger.el.expressions; -import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.Expression; import com.datadog.debugger.el.PrettyPrintVisitor; +import com.datadog.debugger.el.RedactedException; public class ExpressionHelper { public static void throwRedactedException(Expression expr) { String strExpr = PrettyPrintVisitor.print(expr); - throw new EvaluationException( + throw new RedactedException( "Could not evaluate the expression because '" + strExpr + "' was redacted", strExpr); } } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsUndefinedExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsUndefinedExpression.java index 61581742fae..346c38bcca0 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsUndefinedExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/IsUndefinedExpression.java @@ -1,5 +1,6 @@ package com.datadog.debugger.el.expressions; +import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.Visitor; import datadog.trace.bootstrap.debugger.el.ValueReferenceResolver; @@ -21,8 +22,12 @@ public Boolean evaluate(ValueReferenceResolver valueRefResolver) { if (valueExpression == null) { return Boolean.FALSE; } - Value value = valueExpression.evaluate(valueRefResolver); - return value.isUndefined() ? Boolean.TRUE : Boolean.FALSE; + try { + Value value = valueExpression.evaluate(valueRefResolver); + return value.isUndefined() ? Boolean.TRUE : Boolean.FALSE; + } catch (EvaluationException ex) { + return Boolean.TRUE; + } } @Override diff --git a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ValueRefExpression.java b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ValueRefExpression.java index 75a82b54f48..87a3f747846 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ValueRefExpression.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/expressions/ValueRefExpression.java @@ -7,6 +7,7 @@ import com.datadog.debugger.el.Visitor; import datadog.trace.bootstrap.debugger.el.ValueReferenceResolver; import datadog.trace.bootstrap.debugger.util.Redaction; +import datadog.trace.bootstrap.debugger.util.WellKnownClasses; import java.util.Objects; /** An expression taking a reference path and resolving to {@linkplain Value} */ @@ -25,9 +26,14 @@ public Value evaluate(ValueReferenceResolver valueRefResolver) { } catch (RuntimeException ex) { throw new EvaluationException(ex.getMessage(), PrettyPrintVisitor.print(this)); } - if (symbol == Redaction.REDACTED_VALUE - || (symbol != null && Redaction.isRedactedType(symbol.getClass().getTypeName()))) { - ExpressionHelper.throwRedactedException(this); + if (symbol != null) { + String typeName = symbol.getClass().getTypeName(); + if (WellKnownClasses.isStringPrimitive(typeName)) { + symbol = symbol.toString(); + } + if (symbol == Redaction.REDACTED_VALUE || (Redaction.isRedactedType(typeName))) { + ExpressionHelper.throwRedactedException(this); + } } return Value.of(symbol); } diff --git a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/ProbeConditionTest.java b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/ProbeConditionTest.java index c9dd388426e..71357d7bdf1 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/ProbeConditionTest.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/ProbeConditionTest.java @@ -13,12 +13,14 @@ import datadog.trace.bootstrap.debugger.el.ValueReferenceResolver; import java.io.IOException; import java.io.InputStream; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import okio.Okio; import org.junit.jupiter.api.Test; @@ -193,6 +195,30 @@ void redaction() throws IOException { evaluationException.getMessage()); } + @Test + void stringPrimitives() throws IOException { + ProbeCondition probeCondition = load("/test_conditional_10.json"); + Map args = new HashMap<>(); + args.put("uuid", UUID.fromString("a3cbe9e7-edd3-4bef-8e5b-59bfcb04cf91")); + args.put("duration", Duration.ofSeconds(42)); + args.put("clazz", "foo".getClass()); + ValueReferenceResolver ctx = RefResolverHelper.createResolver(args, null, null); + assertTrue(probeCondition.execute(ctx)); + } + + @Test + void testBooleanOperation() throws Exception { + ProbeCondition probeCondition = load("/test_conditional_11.json"); + Map fields = new HashMap<>(); + fields.put("strField", "foobar"); + fields.put("emptyStr", ""); + fields.put("emptyList", new ArrayList<>()); + fields.put("emptyMap", new HashMap<>()); + fields.put("emptyArray", new Object[0]); + ValueReferenceResolver ctx = RefResolverHelper.createResolver(null, null, fields); + assertTrue(probeCondition.execute(ctx)); + } + private static ProbeCondition load(String resourcePath) throws IOException { InputStream input = ProbeConditionTest.class.getResourceAsStream(resourcePath); Moshi moshi = diff --git a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/IsUndefinedExpressionTest.java b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/IsUndefinedExpressionTest.java index 20ea0cade51..d3c592e8e66 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/IsUndefinedExpressionTest.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/IsUndefinedExpressionTest.java @@ -41,6 +41,8 @@ void testUndefinedValue() { new IsUndefinedExpression(DSL.value(Values.UNDEFINED_OBJECT)); assertTrue(expression.evaluate(resolver)); assertEquals("isUndefined(UNDEFINED)", print(expression)); + expression = new IsUndefinedExpression(DSL.ref("undefinedvar")); + assertTrue(expression.evaluate(resolver)); } @Test diff --git a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/ValueRefExpressionTest.java b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/ValueRefExpressionTest.java index be89476be39..eb966288523 100644 --- a/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/ValueRefExpressionTest.java +++ b/dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/expressions/ValueRefExpressionTest.java @@ -72,9 +72,11 @@ void contextRef() { long duration = TimeUnit.NANOSECONDS.convert(680, TimeUnit.MILLISECONDS); boolean returnVal = true; + Throwable exception = new RuntimeException("oops"); Map exts = new HashMap<>(); exts.put(ValueReferences.RETURN_EXTENSION_NAME, returnVal); exts.put(ValueReferences.DURATION_EXTENSION_NAME, duration); + exts.put(ValueReferences.EXCEPTION_EXTENSION_NAME, exception); ValueReferenceResolver resolver = RefResolverHelper.createResolver(null, null, values).withExtensions(exts); @@ -84,6 +86,9 @@ void contextRef() { expression = DSL.ref(ValueReferences.RETURN_REF); assertEquals(returnVal, expression.evaluate(resolver).getValue()); assertEquals("@return", print(expression)); + expression = DSL.ref(ValueReferences.EXCEPTION_REF); + assertEquals(exception, expression.evaluate(resolver).getValue()); + assertEquals("@exception", print(expression)); expression = DSL.ref(limitArg); assertEquals(limit, expression.evaluate(resolver).getValue()); assertEquals("limit", print(expression)); diff --git a/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_10.json b/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_10.json new file mode 100644 index 00000000000..87b2f6e072a --- /dev/null +++ b/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_10.json @@ -0,0 +1,31 @@ +{ + "dsl": "uuid == 'a3cbe9e7-edd3-4bef-8e5b-59bfcb04cf91' && duration == 'PT42S' && clazz == 'class java.lang.String'", + "json": { + "and": [{ + "eq": [ + { + "ref": "uuid" + }, + "a3cbe9e7-edd3-4bef-8e5b-59bfcb04cf91"] + }, { + "eq": [ + {"ref": "duration"}, + "PT42S"] + }, { + "eq": [ + {"ref": "clazz"}, + "class java.lang.String"] + } + ] + } +} + + "dsl": "isEmpty(emptyStr) && isEmpty(emptyList) && isEmpty(emptyMap) && isEmpty(emptyArray) && isUndefined(@exception)", + "json": { + "and": [ + {"isEmpty": {"ref": "emptyStr"}}, + {"isEmpty": {"ref": "emptyList"}}, + {"isEmpty": {"ref": "emptyMap"}}, + {"isEmpty": {"ref": "emptyArray"}}, + {"isUndefined": {"ref": "@exception"}} + diff --git a/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_11.json b/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_11.json new file mode 100644 index 00000000000..fe276464c3d --- /dev/null +++ b/dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_11.json @@ -0,0 +1,14 @@ +{ + "dsl": "isEmpty(emptyStr) && isEmpty(emptyList) && isEmpty(emptyMap) && isEmpty(emptyArray) && isUndefined(@exception)", + "json": { + "and": [ + {"isEmpty": {"ref": "emptyStr"}}, + {"isEmpty": {"ref": "emptyList"}}, + {"isEmpty": {"ref": "emptyMap"}}, + {"isEmpty": {"ref": "emptyArray"}}, + {"isUndefined": {"ref": "@exception"}} + ] + } +} + + diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationUpdater.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationUpdater.java index f45c17584b4..533d78e2a83 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationUpdater.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationUpdater.java @@ -258,11 +258,6 @@ private void applyRateLimiter(ConfigurationComparer changes) { : getDefaultRateLimitPerProbe(probe), probe.isCaptureSnapshot()); } - if (addedDefinitions instanceof SpanDecorationProbe) { - // Span decoration probes use the same instrumentation as log probes, but we don't want - // to sample here. - ProbeRateLimiter.setRate(addedDefinitions.getId(), -1, false); - } } // remove rate for all removed probes for (ProbeDefinition removedDefinition : changes.getRemovedDefinitions()) { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java index 5c864cbf33d..7c3b60d1c6f 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java @@ -3,7 +3,8 @@ import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP; import com.datadog.debugger.sink.DebuggerSink; -import com.datadog.debugger.sink.Sink; +import com.datadog.debugger.symbol.SymDBEnablement; +import com.datadog.debugger.symbol.SymbolAggregator; import com.datadog.debugger.uploader.BatchUploader; import datadog.communication.ddagent.DDAgentFeaturesDiscovery; import datadog.communication.ddagent.SharedCommunicationObjects; @@ -27,20 +28,21 @@ /** Debugger agent implementation */ public class DebuggerAgent { - private static final Logger log = LoggerFactory.getLogger(DebuggerAgent.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DebuggerAgent.class); private static ConfigurationPoller configurationPoller; - private static Sink sink; + private static DebuggerSink sink; private static String agentVersion; private static JsonSnapshotSerializer snapshotSerializer; + private static SymDBEnablement symDBEnablement; public static synchronized void run( Instrumentation instrumentation, SharedCommunicationObjects sco) { Config config = Config.get(); if (!config.isDebuggerEnabled()) { - log.info("Debugger agent disabled"); + LOGGER.info("Debugger agent disabled"); return; } - log.info("Starting Dynamic Instrumentation"); + LOGGER.info("Starting Dynamic Instrumentation"); ClassesToRetransformFinder classesToRetransformFinder = new ClassesToRetransformFinder(); setupSourceFileTracking(instrumentation, classesToRetransformFinder); Redaction.addUserDefinedKeywords(config); @@ -76,7 +78,18 @@ public static synchronized void run( } configurationPoller = sco.configurationPoller(config); if (configurationPoller != null) { - subscribeConfigurationPoller(config, configurationUpdater); + if (config.isDebuggerSymbolEnabled()) { + symDBEnablement = + new SymDBEnablement( + instrumentation, + config, + new SymbolAggregator( + debuggerSink.getSymbolSink(), config.getDebuggerSymbolFlushThreshold())); + if (config.isDebuggerSymbolForceUpload()) { + symDBEnablement.startSymbolExtraction(); + } + } + subscribeConfigurationPoller(config, configurationUpdater, symDBEnablement); try { /* Note: shutdown hooks are tricky because JVM holds reference for them forever preventing @@ -89,7 +102,7 @@ public static synchronized void run( // The JVM is already shutting down. } } else { - log.debug("No configuration poller available from SharedCommunicationObjects"); + LOGGER.debug("No configuration poller available from SharedCommunicationObjects"); } } @@ -100,7 +113,7 @@ private static void setupSourceFileTracking( private static void loadFromFile( Path probeFilePath, ConfigurationUpdater configurationUpdater, long maxPayloadSize) { - log.debug("try to load from file..."); + LOGGER.debug("try to load from file..."); try (InputStream inputStream = new SizeCheckedInputStream(new FileInputStream(probeFilePath.toFile()), maxPayloadSize)) { byte[] buffer = new byte[4096]; @@ -115,17 +128,22 @@ private static void loadFromFile( Configuration configuration = DebuggerProductChangesListener.Adapter.deserializeConfiguration( outputStream.toByteArray()); - log.debug("Probe definitions loaded from file {}", probeFilePath); + LOGGER.debug("Probe definitions loaded from file {}", probeFilePath); configurationUpdater.accept(configuration); } catch (IOException ex) { - log.error("Unable to load config file {}: {}", probeFilePath, ex); + LOGGER.error("Unable to load config file {}: {}", probeFilePath, ex); } } private static void subscribeConfigurationPoller( - Config config, ConfigurationUpdater configurationUpdater) { + Config config, ConfigurationUpdater configurationUpdater, SymDBEnablement symDBEnablement) { + LOGGER.debug("Subscribing to Live Debugging..."); configurationPoller.addListener( Product.LIVE_DEBUGGING, new DebuggerProductChangesListener(config, configurationUpdater)); + if (symDBEnablement != null && !config.isDebuggerSymbolForceUpload()) { + LOGGER.debug("Subscribing to Symbol DB..."); + configurationPoller.addListener(Product.LIVE_DEBUGGING_SYMBOL_DB, symDBEnablement); + } } static ClassFileTransformer setupInstrumentTheWorldTransformer( @@ -133,7 +151,7 @@ static ClassFileTransformer setupInstrumentTheWorldTransformer( Instrumentation instrumentation, DebuggerSink debuggerSink, StatsdMetricForwarder statsdMetricForwarder) { - log.info("install Instrument-The-World transformer"); + LOGGER.info("install Instrument-The-World transformer"); DebuggerTransformer transformer = createTransformer(config, Configuration.builder().build(), null, debuggerSink); DebuggerContext.init(transformer::instrumentTheWorldResolver, statsdMetricForwarder); @@ -145,7 +163,7 @@ public static String getAgentVersion() { return agentVersion; } - public static Sink getSink() { + public static DebuggerSink getSink() { return sink; } @@ -161,13 +179,13 @@ static void stop() { if (configurationPoller != null) { configurationPoller.stop(); } - if (sink != null && sink instanceof DebuggerSink) { - ((DebuggerSink) sink).stop(); + if (sink != null) { + sink.stop(); } } // Used only for tests - static void initSink(Sink sink) { + static void initSink(DebuggerSink sink) { DebuggerAgent.sink = sink; } @@ -198,7 +216,7 @@ public void run() { try { poller.stop(); } catch (Exception ex) { - log.warn("failed to shutdown ProbesPoller: ", ex); + LOGGER.warn("failed to shutdown ProbesPoller: ", ex); } } @@ -207,7 +225,7 @@ public void run() { try { uploader.shutdown(); } catch (Exception ex) { - log.warn("Failed to shutdown SnapshotUploader", ex); + LOGGER.warn("Failed to shutdown SnapshotUploader", ex); } } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerProductChangesListener.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerProductChangesListener.java index b6ae16553d3..438d7a4061e 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerProductChangesListener.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerProductChangesListener.java @@ -44,28 +44,27 @@ static class Adapter { MoshiHelper.createMoshiConfig().adapter(SpanDecorationProbe.class); static Configuration deserializeConfiguration(byte[] content) throws IOException { - return CONFIGURATION_JSON_ADAPTER.fromJson( - Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + return deserialize(CONFIGURATION_JSON_ADAPTER, content); } static MetricProbe deserializeMetricProbe(byte[] content) throws IOException { - return METRIC_PROBE_JSON_ADAPTER.fromJson( - Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + return deserialize(METRIC_PROBE_JSON_ADAPTER, content); } static LogProbe deserializeLogProbe(byte[] content) throws IOException { - return LOG_PROBE_JSON_ADAPTER.fromJson( - Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + return deserialize(LOG_PROBE_JSON_ADAPTER, content); } static SpanProbe deserializeSpanProbe(byte[] content) throws IOException { - return SPAN_PROBE_JSON_ADAPTER.fromJson( - Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + return deserialize(SPAN_PROBE_JSON_ADAPTER, content); } static SpanDecorationProbe deserializeSpanDecorationProbe(byte[] content) throws IOException { - return SPAN_DECORATION_PROBE_JSON_ADAPTER.fromJson( - Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + return deserialize(SPAN_DECORATION_PROBE_JSON_ADAPTER, content); + } + + private static T deserialize(JsonAdapter adapter, byte[] content) throws IOException { + return adapter.fromJson(Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); } } @@ -89,9 +88,7 @@ public void accept( byte[] content, datadog.remoteconfig.ConfigurationChangesListener.PollingRateHinter pollingRateHinter) throws IOException { - String configId = configKey.getConfigId(); - if (configId.startsWith("metricProbe_")) { MetricProbe metricProbe = Adapter.deserializeMetricProbe(content); configChunks.put(configId, (builder) -> builder.add(metricProbe)); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java similarity index 71% rename from dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java rename to dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java index 4554630e8eb..3f19bf2d2b2 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/LogMessageTemplateBuilder.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/StringTemplateBuilder.java @@ -3,17 +3,20 @@ import static com.datadog.debugger.util.ValueScriptHelper.serializeValue; import com.datadog.debugger.el.EvaluationException; +import com.datadog.debugger.el.RedactedException; import com.datadog.debugger.el.Value; import com.datadog.debugger.el.ValueScript; import com.datadog.debugger.probe.LogProbe; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.EvaluationError; +import datadog.trace.bootstrap.debugger.Limits; +import datadog.trace.bootstrap.debugger.util.Redaction; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LogMessageTemplateBuilder { - private static final Logger LOGGER = LoggerFactory.getLogger(LogMessageTemplateBuilder.class); +public class StringTemplateBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(StringTemplateBuilder.class); /** * Serialization limits for log messages. Most values are lower than snapshot because you can * directly reference values that are in your interest with Expression Language: @@ -21,8 +24,11 @@ public class LogMessageTemplateBuilder { */ private final List segments; - public LogMessageTemplateBuilder(List segments) { + private final Limits limits; + + public StringTemplateBuilder(List segments, Limits limits) { this.segments = segments; + this.limits = limits; } public String evaluate(CapturedContext context, LogProbe.LogStatus status) { @@ -43,13 +49,16 @@ public String evaluate(CapturedContext context, LogProbe.LogStatus status) { } else if (result.isNull()) { sb.append("null"); } else { - serializeValue(sb, segment.getParsedExpr().getDsl(), result.getValue(), status); + serializeValue( + sb, segment.getParsedExpr().getDsl(), result.getValue(), status, limits); } } catch (EvaluationException ex) { LOGGER.debug("Evaluation error: ", ex); status.addError(new EvaluationError(ex.getExpr(), ex.getMessage())); status.setLogTemplateErrors(true); - sb.append("{").append(ex.getMessage()).append("}"); + String msg = + ex instanceof RedactedException ? Redaction.REDACTED_VALUE : ex.getMessage(); + sb.append("{").append(msg).append("}"); } } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java index 3edb9a464f0..5c4da11bd66 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/ASMHelper.java @@ -9,6 +9,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; @@ -20,6 +21,7 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; @@ -164,7 +166,9 @@ public static void emitReflectiveCall( String methodName = getReflectiveMethodName(sort); // stack: [target_object, string] org.objectweb.asm.Type returnType = - sort == org.objectweb.asm.Type.OBJECT ? Types.OBJECT_TYPE : fieldType.getMainType(); + sort == org.objectweb.asm.Type.OBJECT || sort == org.objectweb.asm.Type.ARRAY + ? Types.OBJECT_TYPE + : fieldType.getMainType(); invokeStatic( insnList, REFLECTIVE_FIELD_VALUE_RESOLVER_TYPE, @@ -172,7 +176,7 @@ public static void emitReflectiveCall( returnType, targetType, Types.STRING_TYPE); - if (sort == org.objectweb.asm.Type.OBJECT) { + if (sort == org.objectweb.asm.Type.OBJECT || sort == org.objectweb.asm.Type.ARRAY) { insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, fieldType.getMainType().getInternalName())); } // stack: [field_value] @@ -197,12 +201,50 @@ private static String getReflectiveMethodName(int sort) { case org.objectweb.asm.Type.BOOLEAN: return "getFieldValueAsBoolean"; case org.objectweb.asm.Type.OBJECT: + case org.objectweb.asm.Type.ARRAY: return "getFieldValue"; default: throw new IllegalArgumentException("Unsupported type sort:" + sort); } } + public static List sortLocalVariables(List localVariables) { + List sortedLocalVars = new ArrayList<>(localVariables); + sortedLocalVars.sort(Comparator.comparingInt(o -> o.index)); + return sortedLocalVars; + } + + public static LocalVariableNode[] createLocalVarNodes(List sortedLocalVars) { + int maxIndex = sortedLocalVars.get(sortedLocalVars.size() - 1).index; + LocalVariableNode[] localVars = new LocalVariableNode[maxIndex + 1]; + for (LocalVariableNode localVariableNode : sortedLocalVars) { + localVars[localVariableNode.index] = localVariableNode; + } + return localVars; + } + + public static void adjustLocalVarsBasedOnArgs( + boolean isStatic, + LocalVariableNode[] localVars, + org.objectweb.asm.Type[] argTypes, + List sortedLocalVars) { + // assume that first local variables matches method arguments + // as stated into the JVM spec: + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.1 + // so we reassigned local var in arg slots if they are empty + if (argTypes.length < localVars.length) { + int slot = isStatic ? 0 : 1; + int localVarTableIdx = slot; + for (org.objectweb.asm.Type t : argTypes) { + if (localVars[slot] == null && localVarTableIdx < sortedLocalVars.size()) { + localVars[slot] = sortedLocalVars.get(localVarTableIdx); + } + slot += t.getSize(); + localVarTableIdx++; + } + } + } + /** Wraps ASM's {@link org.objectweb.asm.Type} with associated generic types */ public static class Type { private final org.objectweb.asm.Type mainType; diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/CapturedContextInstrumentor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/CapturedContextInstrumentor.java index 73ef9efb8fa..f728e14eb30 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/CapturedContextInstrumentor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/CapturedContextInstrumentor.java @@ -127,6 +127,8 @@ private boolean addLineCaptures(LineMap lineMap) { } if (beforeLabel != null) { InsnList insnList = new InsnList(); + ldc(insnList, Type.getObjectType(classNode.name)); + // stack [class, array] pushProbesIds(insnList); // stack [array] invokeStatic( @@ -134,6 +136,7 @@ private boolean addLineCaptures(LineMap lineMap) { DEBUGGER_CONTEXT_TYPE, "isReadyToCapture", Type.BOOLEAN_TYPE, + CLASS_TYPE, STRING_ARRAY_TYPE); // stack [boolean] LabelNode targetNode = new LabelNode(); @@ -314,10 +317,17 @@ private void instrumentMethodEnter() { methodNode.instructions.insert(methodEnterLabel, insnList); return; } + ldc(insnList, Type.getObjectType(classNode.name)); + // stack [class] pushProbesIds(insnList); - // stack [array] + // stack [class, array] invokeStatic( - insnList, DEBUGGER_CONTEXT_TYPE, "isReadyToCapture", Type.BOOLEAN_TYPE, STRING_ARRAY_TYPE); + insnList, + DEBUGGER_CONTEXT_TYPE, + "isReadyToCapture", + Type.BOOLEAN_TYPE, + CLASS_TYPE, + STRING_ARRAY_TYPE); // stack [boolean] LabelNode targetNode = new LabelNode(); LabelNode gotoNode = new LabelNode(); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/Instrumentor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/Instrumentor.java index 91799dfeff5..cda1012b299 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/Instrumentor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/Instrumentor.java @@ -1,14 +1,15 @@ package com.datadog.debugger.instrumentation; +import static com.datadog.debugger.instrumentation.ASMHelper.adjustLocalVarsBasedOnArgs; +import static com.datadog.debugger.instrumentation.ASMHelper.createLocalVarNodes; import static com.datadog.debugger.instrumentation.ASMHelper.ldc; +import static com.datadog.debugger.instrumentation.ASMHelper.sortLocalVariables; import static com.datadog.debugger.instrumentation.Types.STRING_TYPE; import com.datadog.debugger.instrumentation.DiagnosticMessage.Kind; import com.datadog.debugger.probe.ProbeDefinition; import com.datadog.debugger.probe.Where; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.List; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -75,29 +76,10 @@ private LocalVariableNode[] extractLocalVariables(Type[] argTypes) { if (methodNode.localVariables == null || methodNode.localVariables.isEmpty()) { return new LocalVariableNode[0]; } - List sortedLocalVars = new ArrayList<>(methodNode.localVariables); - sortedLocalVars.sort(Comparator.comparingInt(o -> o.index)); - int maxIndex = sortedLocalVars.get(sortedLocalVars.size() - 1).index; - LocalVariableNode[] localVars = new LocalVariableNode[maxIndex + 1]; + List sortedLocalVars = sortLocalVariables(methodNode.localVariables); + LocalVariableNode[] localVars = createLocalVarNodes(sortedLocalVars); + adjustLocalVarsBasedOnArgs(isStatic, localVars, argTypes, sortedLocalVars); localVarBaseOffset = sortedLocalVars.get(0).index; - for (LocalVariableNode localVariableNode : sortedLocalVars) { - localVars[localVariableNode.index] = localVariableNode; - } - // assume that first local variables matches method arguments - // as stated into the JVM spec: - // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.1 - // so we reassigned local var in arg slots if they are empty - if (argTypes.length < localVars.length) { - int slot = isStatic ? 0 : 1; - int localVarTableIdx = slot; - for (Type t : argTypes) { - if (localVars[slot] == null && localVarTableIdx < sortedLocalVars.size()) { - localVars[slot] = sortedLocalVars.get(localVarTableIdx); - } - slot += t.getSize(); - localVarTableIdx++; - } - } return localVars; } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java index 38642365cc4..6d361e5c1a9 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/instrumentation/MetricInstrumentor.java @@ -11,6 +11,8 @@ import static com.datadog.debugger.instrumentation.ASMHelper.ldc; import static com.datadog.debugger.instrumentation.Types.*; import static datadog.trace.util.Strings.getClassName; +import static org.objectweb.asm.Type.DOUBLE_TYPE; +import static org.objectweb.asm.Type.LONG_TYPE; import com.datadog.debugger.el.InvalidValueException; import com.datadog.debugger.el.Visitor; @@ -46,6 +48,7 @@ import com.datadog.debugger.el.values.StringValue; import com.datadog.debugger.probe.MetricProbe; import com.datadog.debugger.probe.Where; +import datadog.trace.bootstrap.debugger.MethodLocation; import datadog.trace.bootstrap.debugger.el.ValueReferences; import java.lang.reflect.Field; import java.util.ArrayList; @@ -141,6 +144,7 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { int size = 1; int storeOpCode = 0; int loadOpCode = 0; + Type returnType = null; switch (node.getOpcode()) { case Opcodes.RET: case Opcodes.RETURN: @@ -149,29 +153,35 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { storeOpCode = Opcodes.LSTORE; loadOpCode = Opcodes.LLOAD; size = 2; + returnType = Type.LONG_TYPE; break; case Opcodes.DRETURN: storeOpCode = Opcodes.DSTORE; loadOpCode = Opcodes.DLOAD; size = 2; + returnType = Type.DOUBLE_TYPE; break; case Opcodes.IRETURN: storeOpCode = Opcodes.ISTORE; loadOpCode = Opcodes.ILOAD; + returnType = Type.INT_TYPE; break; case Opcodes.FRETURN: storeOpCode = Opcodes.FSTORE; loadOpCode = Opcodes.FLOAD; + returnType = Type.FLOAT_TYPE; break; case Opcodes.ARETURN: storeOpCode = Opcodes.ASTORE; loadOpCode = Opcodes.ALOAD; + returnType = OBJECT_TYPE; break; default: throw new UnsupportedOperationException("Unsupported opcode: " + node.getOpcode()); } - InsnList insnList = wrapTryCatch(callMetric(metricProbe)); int tmpIdx = newVar(size); + InsnList insnList = + wrapTryCatch(callMetric(metricProbe, new ReturnContext(tmpIdx, loadOpCode, returnType))); // store return value from the stack to local before wrapped call insnList.insert(new VarInsnNode(storeOpCode, tmpIdx)); // restore return value to the stack after wrapped call @@ -179,7 +189,7 @@ protected InsnList getBeforeReturnInsnList(AbstractInsnNode node) { return insnList; } - private InsnList callCount(MetricProbe metricProbe) { + private InsnList callCount(MetricProbe metricProbe, ReturnContext returnContext) { if (metricProbe.getValue() == null) { InsnList insnList = new InsnList(); // consider the metric as an increment counter one @@ -203,16 +213,20 @@ private InsnList callCount(MetricProbe metricProbe) { // stack [] return insnList; } - return internalCallMetric(metricProbe); + return internalCallMetric(metricProbe, returnContext); } - private InsnList internalCallMetric(MetricProbe metricProbe) { + private InsnList internalCallMetric(MetricProbe metricProbe, ReturnContext returnContext) { InsnList insnList = new InsnList(); InsnList nullBranch = new InsnList(); VisitorResult result; Type resultType; try { - result = metricProbe.getValue().getExpr().accept(new MetricValueVisitor(this, nullBranch)); + result = + metricProbe + .getValue() + .getExpr() + .accept(new MetricValueVisitor(this, nullBranch, returnContext)); } catch (InvalidValueException | UnsupportedOperationException ex) { reportError(ex.getMessage()); return EMPTY_INSN_LIST; @@ -271,16 +285,20 @@ private Type convertIfRequired(Type currentType, InsnList insnList) { } private InsnList callMetric(MetricProbe metricProbe) { + return callMetric(metricProbe, null); + } + + private InsnList callMetric(MetricProbe metricProbe, ReturnContext returnContext) { switch (metricProbe.getKind()) { case COUNT: - return callCount(metricProbe); + return callCount(metricProbe, returnContext); case GAUGE: case HISTOGRAM: case DISTRIBUTION: if (metricProbe.getValue() == null) { return EMPTY_INSN_LIST; } - return internalCallMetric(metricProbe); + return internalCallMetric(metricProbe, returnContext); default: reportError(String.format("Unknown metric kind: %s", metricProbe.getKind())); } @@ -332,10 +350,13 @@ public VisitorResult(ASMHelper.Type type, InsnList insnList) { private static class MetricValueVisitor implements Visitor { private final MetricInstrumentor instrumentor; private final InsnList nullBranch; + private final ReturnContext returnContext; - public MetricValueVisitor(MetricInstrumentor instrumentor, InsnList nullBranch) { + public MetricValueVisitor( + MetricInstrumentor instrumentor, InsnList nullBranch, ReturnContext returnContext) { this.instrumentor = instrumentor; this.nullBranch = nullBranch; + this.returnContext = returnContext; } @Override @@ -463,7 +484,11 @@ public VisitorResult visit(ValueRefExpression valueRefExpression) { insnList.add(new VarInsnNode(Opcodes.ALOAD, 0)); // stack [this] } else { - currentType = tryRetrieve(name, insnList); + if (name.startsWith(ValueReferences.SYNTHETIC_PREFIX)) { + currentType = tryRetrieveSynthetic(name, insnList); + } else { + currentType = tryRetrieve(name, insnList); + } if (currentType == null) { throw new InvalidValueException("Cannot resolve symbol " + name); } @@ -727,5 +752,58 @@ private ASMHelper.Type tryRetrieveField( } return returnType; } + + private ASMHelper.Type tryRetrieveSynthetic(String name, InsnList insnList) { + if (name.equals(ValueReferences.RETURN_REF)) { + if (instrumentor.metricProbe.getEvaluateAt() != MethodLocation.EXIT) { + return null; + } + if (returnContext == null) { + return null; + } + VarInsnNode varInsnNode = + new VarInsnNode(returnContext.opLoad, returnContext.returnLocalVarIdx); + insnList.add(varInsnNode); + // stack [return_value] + return new ASMHelper.Type(returnContext.type); + } + if (name.equals(ValueReferences.DURATION_REF)) { + if (instrumentor.metricProbe.getEvaluateAt() != MethodLocation.EXIT) { + return null; + } + // call System.nanoTime at the beginning of the method + int var = instrumentor.newVar(LONG_TYPE); + InsnList nanoTimeList = new InsnList(); + invokeStatic(nanoTimeList, Type.getType(System.class), "nanoTime", LONG_TYPE); + nanoTimeList.add(new VarInsnNode(Opcodes.LSTORE, var)); + instrumentor.methodNode.instructions.insert(instrumentor.methodEnterLabel, nanoTimeList); + // diff nanoTime before calling metric + invokeStatic(insnList, Type.getType(System.class), "nanoTime", LONG_TYPE); + // stack [long] + insnList.add(new VarInsnNode(Opcodes.LLOAD, var)); + // stack [long, long] + insnList.add(new InsnNode(Opcodes.LSUB)); + // stack [long] + insnList.add(new InsnNode(Opcodes.L2D)); + insnList.add(new LdcInsnNode(1_000_000D)); + // stack [long, double] + insnList.add(new InsnNode(Opcodes.DDIV)); + // stack [double] + return new ASMHelper.Type(DOUBLE_TYPE); + } + return null; + } + } + + private static class ReturnContext { + private final int returnLocalVarIdx; + private final int opLoad; + private final Type type; + + public ReturnContext(int returnLocalVarIdx, int opLoad, Type type) { + this.returnLocalVarIdx = returnLocalVarIdx; + this.opLoad = opLoad; + this.type = type; + } } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java index 48628f10550..0f112938335 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java @@ -4,14 +4,14 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.agent.Generated; -import com.datadog.debugger.agent.LogMessageTemplateBuilder; +import com.datadog.debugger.agent.StringTemplateBuilder; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.el.ValueScript; import com.datadog.debugger.instrumentation.CapturedContextInstrumentor; import com.datadog.debugger.instrumentation.DiagnosticMessage; import com.datadog.debugger.instrumentation.InstrumentationResult; -import com.datadog.debugger.sink.Sink; +import com.datadog.debugger.sink.DebuggerSink; import com.datadog.debugger.sink.Snapshot; import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; @@ -41,6 +41,8 @@ /** Stores definition of a log probe */ public class LogProbe extends ProbeDefinition { private static final Logger LOGGER = LoggerFactory.getLogger(LogProbe.class); + private static final Limits LIMITS = new Limits(1, 3, 8192, 5); + private static final int LOG_MSG_LIMIT = 8192; /** Stores part of a templated message either a str or an expression */ public static class Segment { @@ -363,11 +365,17 @@ public InstrumentationResult.Status instrument( } @Override - public void evaluate(CapturedContext context, CapturedContext.Status status) { + public void evaluate( + CapturedContext context, CapturedContext.Status status, MethodLocation methodLocation) { if (!(status instanceof LogStatus)) { throw new IllegalStateException("Invalid status: " + status.getClass()); } + LogStatus logStatus = (LogStatus) status; + if (!hasCondition()) { + // sample when no condition associated + sample(logStatus, methodLocation); + } logStatus.setCondition(evaluateCondition(context, logStatus)); CapturedContext.CapturedThrowable throwable = context.getThrowable(); if (logStatus.hasConditionErrors() && throwable != null) { @@ -375,9 +383,33 @@ public void evaluate(CapturedContext context, CapturedContext.Status status) { new EvaluationError( "uncaught exception", throwable.getType() + ": " + throwable.getMessage())); } - if (logStatus.getCondition()) { - LogMessageTemplateBuilder logMessageBuilder = new LogMessageTemplateBuilder(segments); - logStatus.setMessage(logMessageBuilder.evaluate(context, logStatus)); + if (hasCondition() && (logStatus.getCondition() || logStatus.hasConditionErrors())) { + // sample if probe has condition and condition is true or has error + sample(logStatus, methodLocation); + } + if (logStatus.isSampled() && logStatus.getCondition()) { + StringTemplateBuilder logMessageBuilder = new StringTemplateBuilder(segments, LIMITS); + String msg = logMessageBuilder.evaluate(context, logStatus); + if (msg != null && msg.length() > LOG_MSG_LIMIT) { + StringBuilder sb = new StringBuilder(LOG_MSG_LIMIT + 3); + sb.append(msg, 0, LOG_MSG_LIMIT); + sb.append("..."); + msg = sb.toString(); + } + logStatus.setMessage(msg); + } + } + + private void sample(LogStatus logStatus, MethodLocation methodLocation) { + // sample only once and when we need to evaluate + if (!MethodLocation.isSame(methodLocation, evaluateAt)) { + return; + } + boolean sampled = ProbeRateLimiter.tryProbe(id); + logStatus.setSampled(sampled); + if (!sampled) { + LOGGER.debug("{} not sampled!", id); + DebuggerAgent.getSink().skipSnapshot(id, DebuggerContext.SkipCause.RATE); } } @@ -425,18 +457,11 @@ public void commit( spanId = exitContext.getSpanId(); break; } - Sink sink = DebuggerAgent.getSink(); + DebuggerSink sink = DebuggerAgent.getSink(); boolean shouldCommit = false; int maxDepth = capture != null ? capture.maxReferenceDepth : -1; Snapshot snapshot = new Snapshot(Thread.currentThread(), this, maxDepth); if (entryStatus.shouldSend() && exitStatus.shouldSend()) { - // only rate limit if a condition is defined - if (probeCondition != null) { - if (!ProbeRateLimiter.tryProbe(id)) { - sink.skipSnapshot(id, DebuggerContext.SkipCause.RATE); - return; - } - } snapshot.setTraceId(traceId); snapshot.setSpanId(spanId); if (isCaptureSnapshot()) { @@ -481,7 +506,7 @@ private LogStatus convertStatus(CapturedContext.Status status) { return (LogStatus) status; } - private void commitSnapshot(Snapshot snapshot, Sink sink) { + private void commitSnapshot(Snapshot snapshot, DebuggerSink sink) { /* * Record stack trace having the caller of this method as 'top' frame. * For this it is necessary to discard: @@ -501,18 +526,11 @@ public void commit(CapturedContext lineContext, int line) { if (status == null) { return; } - Sink sink = DebuggerAgent.getSink(); + DebuggerSink sink = DebuggerAgent.getSink(); int maxDepth = capture != null ? capture.maxReferenceDepth : -1; Snapshot snapshot = new Snapshot(Thread.currentThread(), this, maxDepth); boolean shouldCommit = false; if (status.shouldSend()) { - // only rate limit if a condition is defined - if (probeCondition != null) { - if (!ProbeRateLimiter.tryProbe(id)) { - sink.skipSnapshot(id, DebuggerContext.SkipCause.RATE); - return; - } - } snapshot.setTraceId(lineContext.getTraceId()); snapshot.setSpanId(lineContext.getSpanId()); if (isCaptureSnapshot()) { @@ -554,6 +572,7 @@ public static class LogStatus extends CapturedContext.Status { private boolean condition = true; private boolean hasLogTemplateErrors; private boolean hasConditionErrors; + private boolean sampled = true; private String message; public LogStatus(ProbeImplementation probeImplementation) { @@ -567,7 +586,7 @@ private LogStatus(ProbeImplementation probeImplementation, boolean condition) { @Override public boolean shouldFreezeContext() { - return probeImplementation.isCaptureSnapshot() && shouldSend(); + return sampled && probeImplementation.isCaptureSnapshot() && shouldSend(); } @Override @@ -576,7 +595,7 @@ public boolean isCapturing() { } public boolean shouldSend() { - return condition && !hasConditionErrors; + return sampled && condition && !hasConditionErrors; } public boolean shouldReportError() { @@ -614,6 +633,14 @@ public void setMessage(String message) { public String getMessage() { return message; } + + public void setSampled(boolean sampled) { + this.sampled = sampled; + } + + public boolean isSampled() { + return sampled; + } } @Generated diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/ProbeDefinition.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/ProbeDefinition.java index a02b1e95646..4de096f0619 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/ProbeDefinition.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/ProbeDefinition.java @@ -143,7 +143,8 @@ public ProbeLocation getLocation() { } @Override - public void evaluate(CapturedContext context, CapturedContext.Status status) {} + public void evaluate( + CapturedContext context, CapturedContext.Status status, MethodLocation methodLocation) {} @Override public void commit( diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java index 9bd2a410dfe..47b170f57b1 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/SpanDecorationProbe.java @@ -2,7 +2,7 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.agent.Generated; -import com.datadog.debugger.agent.LogMessageTemplateBuilder; +import com.datadog.debugger.agent.StringTemplateBuilder; import com.datadog.debugger.el.EvaluationException; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.instrumentation.CapturedContextInstrumentor; @@ -33,6 +33,7 @@ public class SpanDecorationProbe extends ProbeDefinition { private static final Logger LOGGER = LoggerFactory.getLogger(SpanDecorationProbe.class); private static final String PROBEID_DD_TAGS_FORMAT = "_dd.di.%s.probe_id"; private static final String EVALERROR_DD_TAGS_FORMAT = "_dd.di.%s.evaluation_error"; + private static final Limits LIMITS = new Limits(1, 3, 255, 5); public enum TargetSpan { ACTIVE, @@ -145,7 +146,8 @@ public InstrumentationResult.Status instrument( } @Override - public void evaluate(CapturedContext context, CapturedContext.Status status) { + public void evaluate( + CapturedContext context, CapturedContext.Status status, MethodLocation methodLocation) { for (Decoration decoration : decorations) { if (decoration.when != null) { try { @@ -165,7 +167,7 @@ public void evaluate(CapturedContext context, CapturedContext.Status status) { SpanDecorationStatus spanStatus = (SpanDecorationStatus) status; for (Tag tag : decoration.tags) { String tagName = sanitize(tag.name); - LogMessageTemplateBuilder builder = new LogMessageTemplateBuilder(tag.value.getSegments()); + StringTemplateBuilder builder = new StringTemplateBuilder(tag.value.getSegments(), LIMITS); LogProbe.LogStatus logStatus = new LogProbe.LogStatus(this); String tagValue = builder.evaluate(context, logStatus); if (logStatus.hasLogTemplateErrors()) { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/Where.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/Where.java index f6c245e5000..eeffaa3031a 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/Where.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/Where.java @@ -164,12 +164,19 @@ private static String removeFQN(String targetSignature) { } String result = targetSignature; for (String className : classes) { - Pattern classNamePattern = Pattern.compile("L" + className + ";"); - result = classNamePattern.matcher(result).replaceAll("L" + simplify(className) + ";"); + Pattern classNamePattern = Pattern.compile("L" + escapeClassName(className) + ";"); + result = + classNamePattern + .matcher(result) + .replaceAll("L" + escapeClassName(simplify(className)) + ";"); } return result; } + private static String escapeClassName(String className) { + return className.replace("$", "\\$"); + } + private static String simplify(String fqnClass) { if (fqnClass == null) { return null; diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/DebuggerSink.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/DebuggerSink.java index 8203869d9a0..fe16b42f6df 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/DebuggerSink.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/DebuggerSink.java @@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory; /** Collects data that needs to be sent to the backend: Snapshots, metrics and statuses */ -public class DebuggerSink implements Sink { +public class DebuggerSink { private static final Logger log = LoggerFactory.getLogger(DebuggerSink.class); private static final double FREE_CAPACITY_LOWER_THRESHOLD = 0.25; private static final double FREE_CAPACITY_UPPER_THRESHOLD = 0.75; @@ -30,6 +30,7 @@ public class DebuggerSink implements Sink { private final ProbeStatusSink probeStatusSink; private final SnapshotSink snapshotSink; + private final SymbolSink symbolSink; private final DebuggerMetrics debuggerMetrics; private final BatchUploader batchUploader; private final String tags; @@ -42,10 +43,11 @@ public class DebuggerSink implements Sink { public DebuggerSink(Config config) { this( config, - new BatchUploader(config), + new BatchUploader(config, config.getFinalDebuggerSnapshotUrl()), DebuggerMetrics.getInstance(config), new ProbeStatusSink(config), - new SnapshotSink(config)); + new SnapshotSink(config), + new SymbolSink(config)); } DebuggerSink(Config config, BatchUploader batchUploader) { @@ -54,16 +56,18 @@ public DebuggerSink(Config config) { batchUploader, DebuggerMetrics.getInstance(config), new ProbeStatusSink(config), - new SnapshotSink(config)); + new SnapshotSink(config), + new SymbolSink(config)); } public DebuggerSink(Config config, ProbeStatusSink probeStatusSink) { this( config, - new BatchUploader(config), + new BatchUploader(config, config.getFinalDebuggerSnapshotUrl()), DebuggerMetrics.getInstance(config), probeStatusSink, - new SnapshotSink(config)); + new SnapshotSink(config), + new SymbolSink(config)); } DebuggerSink(Config config, BatchUploader batchUploader, DebuggerMetrics debuggerMetrics) { @@ -72,7 +76,8 @@ public DebuggerSink(Config config, ProbeStatusSink probeStatusSink) { batchUploader, debuggerMetrics, new ProbeStatusSink(config), - new SnapshotSink(config)); + new SnapshotSink(config), + new SymbolSink(config)); } public DebuggerSink( @@ -80,12 +85,14 @@ public DebuggerSink( BatchUploader batchUploader, DebuggerMetrics debuggerMetrics, ProbeStatusSink probeStatusSink, - SnapshotSink snapshotSink) { + SnapshotSink snapshotSink, + SymbolSink symbolSink) { this.batchUploader = batchUploader; tags = getDefaultTagsMergedWithGlobalTags(config); this.debuggerMetrics = debuggerMetrics; this.probeStatusSink = probeStatusSink; this.snapshotSink = snapshotSink; + this.symbolSink = symbolSink; this.uploadFlushInterval = config.getDebuggerUploadFlushInterval(); } @@ -136,7 +143,10 @@ public BatchUploader getSnapshotUploader() { return batchUploader; } - @Override + public SymbolSink getSymbolSink() { + return symbolSink; + } + public void addSnapshot(Snapshot snapshot) { boolean added = snapshotSink.offer(snapshot); if (!added) { @@ -160,15 +170,16 @@ private void reschedule() { // visible for testing void flush(DebuggerSink ignored) { + symbolSink.flush(); List diagnostics = probeStatusSink.getSerializedDiagnostics(); List snapshots = snapshotSink.getSerializedSnapshots(); if (snapshots.size() + diagnostics.size() == 0) { return; } - if (snapshots.size() >= 1) { + if (snapshots.size() > 0) { uploadPayloads(snapshots); } - if (diagnostics.size() >= 1) { + if (diagnostics.size() > 0) { uploadPayloads(diagnostics); } } @@ -248,7 +259,6 @@ private void reportError(ProbeId probeId, DiagnosticMessage msg) { } /** Notifies the snapshot was skipped for one of the SkipCause reason */ - @Override public void skipSnapshot(String probeId, DebuggerContext.SkipCause cause) { String causeTag; switch (cause) { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/Sink.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/Sink.java deleted file mode 100644 index 687c83ef747..00000000000 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/Sink.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.datadog.debugger.sink; - -import datadog.trace.bootstrap.debugger.DebuggerContext; - -public interface Sink { - void addSnapshot(Snapshot snapshot); - - void skipSnapshot(String probeId, DebuggerContext.SkipCause cause); -} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java index 559bd259424..4ceca7b547e 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SnapshotSink.java @@ -2,7 +2,7 @@ import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.util.ExceptionHelper; -import com.datadog.debugger.util.SnapshotSlicer; +import com.datadog.debugger.util.SnapshotPruner; import datadog.trace.api.Config; import datadog.trace.relocate.api.RatelimitedLogger; import datadog.trace.util.TagsHelper; @@ -64,20 +64,13 @@ public boolean offer(Snapshot snapshot) { String serializeSnapshot(String serviceName, Snapshot snapshot) { String str = DebuggerAgent.getSnapshotSerializer().serializeSnapshot(serviceName, snapshot); - int currentMaxDepth = snapshot.getMaxDepth(); - while (str.length() > MAX_SNAPSHOT_SIZE && currentMaxDepth >= 0) { + String prunedStr = SnapshotPruner.prune(str, MAX_SNAPSHOT_SIZE, 4); + if (prunedStr.length() != str.length()) { LOGGER.debug( - "serializing snapshot breached 1MB limit: {}, reducing depth level {} -> {}", + "serializing snapshot breached 1MB limit, reducing size from {} -> {}", str.length(), - currentMaxDepth, - currentMaxDepth - 1); - currentMaxDepth -= 1; - str = SnapshotSlicer.slice(currentMaxDepth, str); + prunedStr.length()); } - if (str.length() > MAX_SNAPSHOT_SIZE) { - ratelimitedLogger.warn( - "Snapshot is too large even after reducing depth to 0: {}", str.length()); - } - return str; + return prunedStr; } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SymbolSink.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SymbolSink.java new file mode 100644 index 00000000000..1ff688a35ad --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/SymbolSink.java @@ -0,0 +1,85 @@ +package com.datadog.debugger.sink; + +import com.datadog.debugger.symbol.Scope; +import com.datadog.debugger.symbol.ServiceVersion; +import com.datadog.debugger.uploader.BatchUploader; +import com.datadog.debugger.util.ExceptionHelper; +import com.datadog.debugger.util.MoshiHelper; +import com.squareup.moshi.JsonAdapter; +import datadog.trace.api.Config; +import datadog.trace.util.TagsHelper; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SymbolSink { + + private static final Logger LOGGER = LoggerFactory.getLogger(SymbolSink.class); + private static final int CAPACITY = 1024; + private static final JsonAdapter SERVICE_VERSION_ADAPTER = + MoshiHelper.createMoshiSymbol().adapter(ServiceVersion.class); + private static final String EVENT_FORMAT = + "{%n" + + "\"ddsource\": \"dd_debugger\",%n" + + "\"service\": \"%s\",%n" + + "\"runtimeId\": \"%s\"%n" + + "}"; + + private final String serviceName; + private final String env; + private final String version; + private final BatchUploader symbolUploader; + private final BlockingQueue scopes = new ArrayBlockingQueue<>(CAPACITY); + private final BatchUploader.MultiPartContent event; + + public SymbolSink(Config config) { + this(config, new BatchUploader(config, config.getFinalDebuggerSymDBUrl())); + } + + SymbolSink(Config config, BatchUploader symbolUploader) { + this.serviceName = TagsHelper.sanitize(config.getServiceName()); + this.env = config.getEnv(); + this.version = config.getVersion(); + this.symbolUploader = symbolUploader; + byte[] eventContent = + String.format(EVENT_FORMAT, serviceName, config.getRuntimeId()) + .getBytes(StandardCharsets.UTF_8); + this.event = new BatchUploader.MultiPartContent(eventContent, "event", "event.json"); + } + + public boolean addScope(Scope jarScope) { + ServiceVersion serviceVersion = + new ServiceVersion(serviceName, env, version, "JAVA", Collections.singletonList(jarScope)); + return scopes.offer(serviceVersion); + } + + public void flush() { + if (scopes.isEmpty()) { + return; + } + List scopesToSerialize = new ArrayList<>(); + scopes.drainTo(scopesToSerialize); + LOGGER.debug("Sending {} scopes", scopesToSerialize.size()); + for (ServiceVersion serviceVersion : scopesToSerialize) { + try { + String json = SERVICE_VERSION_ADAPTER.toJson(serviceVersion); + LOGGER.debug( + "Sending scope: {}, size={}", + serviceVersion.getScopes().get(0).getName(), + json.length()); + symbolUploader.uploadAsMultipart( + "", + event, + new BatchUploader.MultiPartContent( + json.getBytes(StandardCharsets.UTF_8), "file", "file.json")); + } catch (Exception e) { + ExceptionHelper.logException(LOGGER, e, "Error during scope serialization:"); + } + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/JarScanner.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/JarScanner.java new file mode 100644 index 00000000000..028335685b5 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/JarScanner.java @@ -0,0 +1,62 @@ +package com.datadog.debugger.symbol; + +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JarScanner { + private static final Logger LOGGER = LoggerFactory.getLogger(JarScanner.class); + private static final String JAR_FILE_PREFIX = "jar:file:"; + private static final String FILE_PREFIX = "file:"; + // Spring prefixes: + // https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html + private static final String SPRING_CLASSES_PREFIX = "BOOT-INF/classes/"; + private static final String SPRING_DEPS_PREFIX = "BOOT-INF/lib/"; + + public static Path extractJarPath(Class clazz) throws URISyntaxException { + return extractJarPath(clazz.getProtectionDomain()); + } + + public static Path extractJarPath(ProtectionDomain protectionDomain) throws URISyntaxException { + if (protectionDomain == null) { + return null; + } + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource == null) { + return null; + } + URL location = codeSource.getLocation(); + if (location == null) { + return null; + } + String locationStr = location.toString(); + LOGGER.debug("CodeSource Location={}", locationStr); + if (locationStr.startsWith(JAR_FILE_PREFIX)) { + int idx = locationStr.indexOf("!/"); + if (idx != -1) { + return getPathFromPrefixedFileName(locationStr, JAR_FILE_PREFIX, idx); + } + } else if (locationStr.startsWith(FILE_PREFIX)) { + return getPathFromPrefixedFileName(locationStr, FILE_PREFIX, locationStr.length()); + } + return null; + } + + public static String trimPrefixes(String classFilePath) { + if (classFilePath.startsWith(SPRING_CLASSES_PREFIX)) { + return classFilePath.substring(SPRING_CLASSES_PREFIX.length()); + } + return classFilePath; + } + + private static Path getPathFromPrefixedFileName(String locationStr, String prefix, int endIdx) { + String fileName = locationStr.substring(prefix.length(), endIdx); + LOGGER.debug("jar filename={}", fileName); + return Paths.get(fileName); + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java new file mode 100644 index 00000000000..78599d993d1 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/LanguageSpecifics.java @@ -0,0 +1,144 @@ +package com.datadog.debugger.symbol; + +import com.datadog.debugger.agent.Generated; +import com.squareup.moshi.Json; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +public class LanguageSpecifics { + @Json(name = "access_modifiers") + private final List accessModifiers; + + private final List annotations; + + @Json(name = "super_class") + private final String superClass; + + private final List interfaces; + + @Json(name = "return_type") + private final String returnType; + + public LanguageSpecifics( + List accessModifiers, + List annotations, + String superClass, + List interfaces, + String returnType) { + this.accessModifiers = accessModifiers; + this.annotations = annotations; + this.superClass = superClass; + this.interfaces = interfaces; + this.returnType = returnType; + } + + public List getAccessModifiers() { + return accessModifiers; + } + + public List getAnnotations() { + return annotations; + } + + public String getSuperClass() { + return superClass; + } + + public List getInterfaces() { + return interfaces; + } + + public String getReturnType() { + return returnType; + } + + @Generated + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LanguageSpecifics that = (LanguageSpecifics) o; + return Objects.equals(accessModifiers, that.accessModifiers) + && Objects.equals(annotations, that.annotations) + && Objects.equals(superClass, that.superClass) + && Objects.equals(interfaces, that.interfaces) + && Objects.equals(returnType, that.returnType); + } + + @Generated + @Override + public int hashCode() { + return Objects.hash(accessModifiers, annotations, superClass, interfaces, returnType); + } + + @Generated + @Override + public String toString() { + return "LanguageSpecifics{" + + "accessModifiers=" + + accessModifiers + + ", annotations=" + + annotations + + ", superClass='" + + superClass + + '\'' + + ", interfaces=" + + interfaces + + ", returnType='" + + returnType + + '\'' + + '}'; + } + + public static class Builder { + private List accessModifiers; + private List annotations; + private String superClass; + private List interfaces; + private String returnType; + + public Builder addModifiers(Collection modifiers) { + if (modifiers == null || modifiers.isEmpty()) { + this.accessModifiers = null; + return this; + } + accessModifiers = new ArrayList<>(modifiers); + return this; + } + + public Builder addAnnotations(Collection annotations) { + if (annotations == null || annotations.isEmpty()) { + this.annotations = null; + return this; + } + this.annotations = new ArrayList<>(annotations); + return this; + } + + public Builder superClass(String superClass) { + this.superClass = superClass; + return this; + } + + public Builder addInterfaces(Collection interfaces) { + if (interfaces == null || interfaces.isEmpty()) { + this.interfaces = null; + return this; + } + this.interfaces = new ArrayList<>(interfaces); + return this; + } + + public Builder returnType(String returnType) { + this.returnType = returnType; + return this; + } + + public LanguageSpecifics build() { + return new LanguageSpecifics( + accessModifiers, annotations, superClass, interfaces, returnType); + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java new file mode 100644 index 00000000000..bd0012bfb03 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java @@ -0,0 +1,149 @@ +package com.datadog.debugger.symbol; + +import com.squareup.moshi.Json; +import java.util.List; + +public class Scope { + @Json(name = "scope_type") + private final ScopeType scopeType; + + @Json(name = "source_file") + private final String sourceFile; + + @Json(name = "start_line") + private final int startLine; + + @Json(name = "end_line") + private final int endLine; + + private final String name; + + @Json(name = "language_specifics") + private final LanguageSpecifics languageSpecifics; + + private final List symbols; + private final List scopes; + + public Scope( + ScopeType scopeType, + String sourceFile, + int startLine, + int endLine, + String name, + LanguageSpecifics languageSpecifics, + List symbols, + List scopes) { + this.scopeType = scopeType; + this.sourceFile = sourceFile; + this.startLine = startLine; + this.endLine = endLine; + this.name = name; + this.languageSpecifics = languageSpecifics; + this.symbols = symbols; + this.scopes = scopes; + } + + public ScopeType getScopeType() { + return scopeType; + } + + public String getSourceFile() { + return sourceFile; + } + + public int getStartLine() { + return startLine; + } + + public int getEndLine() { + return endLine; + } + + public String getName() { + return name; + } + + public LanguageSpecifics getLanguageSpecifics() { + return languageSpecifics; + } + + public List getSymbols() { + return symbols; + } + + public List getScopes() { + return scopes; + } + + @Override + public String toString() { + return "Scope{" + + "scopeType=" + + scopeType + + ", sourceFile='" + + sourceFile + + '\'' + + ", startLine=" + + startLine + + ", endLine=" + + endLine + + ", name='" + + name + + '\'' + + ", languageSpecifics=" + + languageSpecifics + + ", symbols=" + + symbols + + ", scopes=" + + scopes + + '}'; + } + + public static Builder builder( + ScopeType scopeType, String sourceFile, int startLine, int endLine) { + return new Builder(scopeType, sourceFile, startLine, endLine); + } + + public static class Builder { + private final ScopeType scopeType; + private final String sourceFile; + private final int startLine; + private final int endLine; + private String name; + private LanguageSpecifics languageSpecifics; + private List symbols; + private List scopes; + + public Builder(ScopeType scopeType, String sourceFile, int startLine, int endLine) { + this.scopeType = scopeType; + this.sourceFile = sourceFile; + this.startLine = startLine; + this.endLine = endLine; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder languageSpecifics(LanguageSpecifics languageSpecifics) { + this.languageSpecifics = languageSpecifics; + return this; + } + + public Builder symbols(List symbols) { + this.symbols = symbols; + return this; + } + + public Builder scopes(List scopes) { + this.scopes = scopes; + return this; + } + + public Scope build() { + return new Scope( + scopeType, sourceFile, startLine, endLine, name, languageSpecifics, symbols, scopes); + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeType.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeType.java new file mode 100644 index 00000000000..99d8c004a55 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeType.java @@ -0,0 +1,9 @@ +package com.datadog.debugger.symbol; + +public enum ScopeType { + JAR, + CLASS, + METHOD, + LOCAL, + CLOSURE +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ServiceVersion.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ServiceVersion.java new file mode 100644 index 00000000000..a88fc25da63 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ServiceVersion.java @@ -0,0 +1,25 @@ +package com.datadog.debugger.symbol; + +import java.util.List; + +public class ServiceVersion { + private final String service; + + private final String env; + private final String version; + private final String language; + private final List scopes; + + public ServiceVersion( + String service, String env, String version, String language, List scopes) { + this.service = service; + this.env = env; + this.version = version; + this.language = language; + this.scopes = scopes; + } + + public List getScopes() { + return scopes; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java new file mode 100644 index 00000000000..16184bce602 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java @@ -0,0 +1,207 @@ +package com.datadog.debugger.symbol; + +import static com.datadog.debugger.symbol.JarScanner.trimPrefixes; + +import com.datadog.debugger.agent.AllowListHelper; +import com.datadog.debugger.agent.Configuration; +import com.datadog.debugger.util.MoshiHelper; +import com.squareup.moshi.JsonAdapter; +import datadog.remoteconfig.ConfigurationChangesListener; +import datadog.remoteconfig.state.ParsedConfigKey; +import datadog.remoteconfig.state.ProductListener; +import datadog.trace.api.Config; +import datadog.trace.util.AgentTaskScheduler; +import datadog.trace.util.Strings; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; +import okio.Okio; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SymDBEnablement implements ProductListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SymDBEnablement.class); + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + private static final JsonAdapter SYM_DB_JSON_ADAPTER = + MoshiHelper.createMoshiConfig().adapter(SymDbRemoteConfigRecord.class); + private static final String SYM_DB_RC_KEY = "symDb"; + private static final int READ_BUFFER_SIZE = 4096; + private static final int CLASSFILE_BUFFER_SIZE = 8192; + + private final Instrumentation instrumentation; + private final Config config; + private final SymbolAggregator symbolAggregator; + private SymbolExtractionTransformer symbolExtractionTransformer; + private volatile long lastUploadTimestamp; + private AtomicBoolean starting = new AtomicBoolean(); + + public SymDBEnablement( + Instrumentation instrumentation, Config config, SymbolAggregator symbolAggregator) { + this.instrumentation = instrumentation; + this.config = config; + this.symbolAggregator = symbolAggregator; + } + + @Override + public void accept( + ParsedConfigKey configKey, + byte[] content, + ConfigurationChangesListener.PollingRateHinter pollingRateHinter) + throws IOException { + if (configKey.getConfigId().equals(SYM_DB_RC_KEY)) { + SymDbRemoteConfigRecord symDb = deserializeSymDb(content); + if (symDb.isUploadSymbols()) { + // can be long, make it async + AgentTaskScheduler.INSTANCE.execute(this::startSymbolExtraction); + } else { + stopSymbolExtraction(); + } + } else { + LOGGER.debug("unsupported configuration id {}", configKey.getConfigId()); + } + } + + @Override + public void remove( + ParsedConfigKey configKey, ConfigurationChangesListener.PollingRateHinter pollingRateHinter) + throws IOException { + if (configKey.getConfigId().equals(SYM_DB_RC_KEY)) { + stopSymbolExtraction(); + } + } + + @Override + public void commit(ConfigurationChangesListener.PollingRateHinter pollingRateHinter) {} + + private static SymDbRemoteConfigRecord deserializeSymDb(byte[] content) throws IOException { + return SYM_DB_JSON_ADAPTER.fromJson( + Okio.buffer(Okio.source(new ByteArrayInputStream(content)))); + } + + public void stopSymbolExtraction() { + LOGGER.debug("Stopping symbol extraction."); + instrumentation.removeTransformer(symbolExtractionTransformer); + } + + long getLastUploadTimestamp() { + return lastUploadTimestamp; + } + + public void startSymbolExtraction() { + if (!starting.compareAndSet(false, true)) { + return; + } + try { + LOGGER.debug("Starting symbol extraction..."); + if (lastUploadTimestamp > 0) { + LOGGER.debug( + "Last upload was on {}", + LocalDateTime.ofInstant( + Instant.ofEpochMilli(lastUploadTimestamp), ZoneId.systemDefault())); + return; + } + String includes = config.getDebuggerSymbolIncludes(); + AllowListHelper allowListHelper = new AllowListHelper(buildFilterList(includes)); + symbolAggregator.loadedClassesProcessStarted(); + try { + symbolExtractionTransformer = + new SymbolExtractionTransformer(allowListHelper, symbolAggregator); + instrumentation.addTransformer(symbolExtractionTransformer); + extractSymbolForLoadedClasses(allowListHelper); + lastUploadTimestamp = System.currentTimeMillis(); + } finally { + symbolAggregator.loadedClassesProcessEnded(); + } + } finally { + starting.set(false); + } + } + + private void extractSymbolForLoadedClasses(AllowListHelper allowListHelper) { + Class[] classesToExtract; + try { + classesToExtract = + Arrays.stream(instrumentation.getAllLoadedClasses()) + .filter(clazz -> allowListHelper.isAllowed(clazz.getTypeName())) + .filter(instrumentation::isModifiableClass) + .toArray(Class[]::new); + } catch (Throwable ex) { + LOGGER.debug("Failed to get all loaded classes", ex); + return; + } + Set alreadyScannedJars = new HashSet<>(); + byte[] buffer = new byte[READ_BUFFER_SIZE]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(CLASSFILE_BUFFER_SIZE); + for (Class clazz : classesToExtract) { + Path jarPath; + try { + jarPath = JarScanner.extractJarPath(clazz); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + if (jarPath == null) { + continue; + } + if (!Files.exists(jarPath)) { + continue; + } + if (alreadyScannedJars.contains(jarPath.toString())) { + continue; + } + try { + try (JarFile jarFile = new JarFile(jarPath.toFile())) { + jarFile.stream() + .filter(jarEntry -> jarEntry.getName().endsWith(".class")) + .filter( + jarEntry -> + allowListHelper.isAllowed( + Strings.getClassName(trimPrefixes(jarEntry.getName())))) + .forEach(jarEntry -> parseJarEntry(jarEntry, jarFile, jarPath, baos, buffer)); + } + alreadyScannedJars.add(jarPath.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private void parseJarEntry( + JarEntry jarEntry, JarFile jarFile, Path jarPath, ByteArrayOutputStream baos, byte[] buffer) { + LOGGER.debug("parsing jarEntry class: {}", jarEntry.getName()); + try { + InputStream inputStream = jarFile.getInputStream(jarEntry); + int readBytes; + baos.reset(); + while ((readBytes = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, readBytes); + } + symbolAggregator.parseClass(jarEntry.getName(), baos.toByteArray(), jarPath.toString()); + } catch (IOException ex) { + LOGGER.debug("Exception during parsing jarEntry class: {}", jarEntry.getName(), ex); + } + } + + private Configuration.FilterList buildFilterList(String includes) { + if (includes == null || includes.isEmpty()) { + return null; + } + String[] includeParts = COMMA_PATTERN.split(includes); + return new Configuration.FilterList(Arrays.asList(includeParts), Collections.emptyList()); + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDbRemoteConfigRecord.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDbRemoteConfigRecord.java new file mode 100644 index 00000000000..85cc2159ae9 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDbRemoteConfigRecord.java @@ -0,0 +1,16 @@ +package com.datadog.debugger.symbol; + +import com.squareup.moshi.Json; + +public class SymDbRemoteConfigRecord { + @Json(name = "upload_symbols") + private final boolean uploadSymbols; + + public SymDbRemoteConfigRecord(boolean uploadSymbols) { + this.uploadSymbols = uploadSymbols; + } + + public boolean isUploadSymbols() { + return uploadSymbols; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java new file mode 100644 index 00000000000..66deef6ee9f --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Symbol.java @@ -0,0 +1,66 @@ +package com.datadog.debugger.symbol; + +import com.squareup.moshi.Json; + +public class Symbol { + @Json(name = "symbol_type") + private final SymbolType symbolType; + + private final String name; + private final int line; + private final String type; + + @Json(name = "language_specifics") + private final LanguageSpecifics languageSpecifics; + + public Symbol( + SymbolType symbolType, + String name, + int line, + String type, + LanguageSpecifics languageSpecifics) { + this.symbolType = symbolType; + this.name = name; + this.line = line; + this.type = type; + this.languageSpecifics = languageSpecifics; + } + + public SymbolType getSymbolType() { + return symbolType; + } + + public String getName() { + return name; + } + + public int getLine() { + return line; + } + + public String getType() { + return type; + } + + public LanguageSpecifics getLanguageSpecifics() { + return languageSpecifics; + } + + @Override + public String toString() { + return "Symbol{" + + "symbolType=" + + symbolType + + ", name='" + + name + + '\'' + + ", line=" + + line + + ", type='" + + type + + '\'' + + ", languageSpecifics=" + + languageSpecifics + + '}'; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java new file mode 100644 index 00000000000..a10fccfb986 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java @@ -0,0 +1,123 @@ +package com.datadog.debugger.symbol; + +import static com.datadog.debugger.symbol.JarScanner.trimPrefixes; + +import com.datadog.debugger.sink.SymbolSink; +import datadog.trace.util.AgentTaskScheduler; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SymbolAggregator { + private static final Logger LOGGER = LoggerFactory.getLogger(SymbolAggregator.class); + private static final String CLASS_SUFFIX = ".class"; + + private final SymbolSink sink; + private final int symbolFlushThreshold; + private final Map jarScopesByName = new HashMap<>(); + private final AgentTaskScheduler.Scheduled scheduled; + private final Object jarScopeLock = new Object(); + private int totalClasses; + private volatile Set loadedClasses; + + public SymbolAggregator(SymbolSink sink, int symbolFlushThreshold) { + this.sink = sink; + this.symbolFlushThreshold = symbolFlushThreshold; + scheduled = + AgentTaskScheduler.INSTANCE.scheduleAtFixedRate( + this::flushRemainingScopes, this, 0, 1, TimeUnit.SECONDS); + } + + public void parseClass( + String className, byte[] classfileBuffer, ProtectionDomain protectionDomain) { + try { + String jarName = "DEFAULT"; + Path jarPath = JarScanner.extractJarPath(protectionDomain); + if (jarPath != null && Files.exists(jarPath)) { + LOGGER.debug("jarpath: {}", jarPath); + jarName = jarPath.toString(); + } + parseClass(className, classfileBuffer, jarName); + } catch (Exception ex) { + LOGGER.debug("Error parsing class: {}", className, ex); + } + } + + public void parseClass(String className, byte[] classfileBuffer, String jarName) { + if (className == null) { + return; + } + className = trimPrefixes(className); + if (className.endsWith(CLASS_SUFFIX)) { + className = className.substring(0, className.length() - CLASS_SUFFIX.length()); + } + Set localLoadedClasses = loadedClasses; + if (localLoadedClasses != null && !localLoadedClasses.add(className)) { + // class already loaded and symbol extracted + return; + } + LOGGER.debug("Extracting Symbols from: {}, located in: {}", className, jarName); + Scope jarScope = SymbolExtractor.extract(classfileBuffer, jarName); + addJarScope(jarScope, false); + } + + private void flushRemainingScopes(SymbolAggregator symbolAggregator) { + synchronized (jarScopeLock) { + if (jarScopesByName.isEmpty()) { + return; + } + LOGGER.debug("Flush remaining scopes"); + addJarScope(null, true); // force flush remaining scopes + } + } + + private void addJarScope(Scope jarScope, boolean forceFlush) { + List scopes = Collections.emptyList(); + synchronized (jarScopeLock) { + if (jarScope != null) { + Scope scope = jarScopesByName.get(jarScope.getName()); + if (scope != null) { + scope.getScopes().addAll(jarScope.getScopes()); + } else { + jarScopesByName.put(jarScope.getName(), jarScope); + } + totalClasses++; + } + if (totalClasses >= symbolFlushThreshold || forceFlush) { + scopes = new ArrayList<>(jarScopesByName.values()); + jarScopesByName.clear(); + totalClasses = 0; + } + } + if (!scopes.isEmpty()) { + LOGGER.debug("dumping {} jar scopes to sink", scopes.size()); + for (Scope scope : scopes) { + LOGGER.debug( + "dumping {} class scopes to sink from scope: {}", + scope.getScopes().size(), + scope.getName()); + sink.addScope(scope); + } + } + } + + void loadedClassesProcessStarted() { + // to avoid duplicate symbol extraction we keep track of loaded classes + // during the loaded class extraction phase + loadedClasses = ConcurrentHashMap.newKeySet(); + } + + void loadedClassesProcessEnded() { + loadedClasses = null; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java new file mode 100644 index 00000000000..fc18ce17fcd --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java @@ -0,0 +1,67 @@ +package com.datadog.debugger.symbol; + +import com.datadog.debugger.agent.AllowListHelper; +import com.datadog.debugger.sink.SymbolSink; +import datadog.trace.api.Config; +import datadog.trace.util.Strings; +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SymbolExtractionTransformer implements ClassFileTransformer { + + private static final Logger LOGGER = LoggerFactory.getLogger(SymbolExtractionTransformer.class); + + private final AllowListHelper allowListHelper; + private final SymbolAggregator symbolAggregator; + + public SymbolExtractionTransformer() { + this( + new AllowListHelper(null), + new SymbolAggregator( + new SymbolSink(Config.get()), Config.get().getDebuggerSymbolFlushThreshold())); + } + + public SymbolExtractionTransformer( + AllowListHelper allowListHelper, SymbolAggregator symbolAggregator) { + this.allowListHelper = allowListHelper; + this.symbolAggregator = symbolAggregator; + } + + @Override + public byte[] transform( + ClassLoader loader, + String className, + Class classBeingRedefined, + ProtectionDomain protectionDomain, + byte[] classfileBuffer) { + if (className == null) { + return null; + } + try { + if (allowListHelper.isAllowAll()) { + if (className.startsWith("java/") + || className.startsWith("javax/") + || className.startsWith("jdk/") + || className.startsWith("sun/") + || className.startsWith("com/sun/") + || className.startsWith("datadog/") + || className.startsWith("com/datadog/")) { + return null; + } + } else if (!allowListHelper.isAllowed(Strings.getClassName(className))) { + return null; + } + symbolAggregator.parseClass(className, classfileBuffer, protectionDomain); + return null; + } catch (Exception ex) { + LOGGER.debug("Error during extraction: ", ex); + return null; + } + } + + AllowListHelper getAllowListHelper() { + return allowListHelper; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java new file mode 100644 index 00000000000..84d0ff4ffab --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java @@ -0,0 +1,469 @@ +package com.datadog.debugger.symbol; + +import static com.datadog.debugger.instrumentation.ASMHelper.adjustLocalVarsBasedOnArgs; +import static com.datadog.debugger.instrumentation.ASMHelper.createLocalVarNodes; +import static com.datadog.debugger.instrumentation.ASMHelper.sortLocalVariables; + +import com.datadog.debugger.instrumentation.ASMHelper; +import datadog.trace.util.Strings; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.slf4j.LoggerFactory; + +public class SymbolExtractor { + + public static Scope extract(byte[] classFileBuffer, String jarName) { + ClassNode classNode = parseClassFile(classFileBuffer); + return extractScopes(classNode, jarName); + } + + private static Scope extractScopes(ClassNode classNode, String jarName) { + try { + String sourceFile = extractSourceFile(classNode); + List methodScopes = extractMethods(classNode, sourceFile); + int classStartLine = Integer.MAX_VALUE; + int classEndLine = 0; + for (Scope scope : methodScopes) { + classStartLine = Math.min(classStartLine, scope.getStartLine()); + classEndLine = Math.max(classEndLine, scope.getEndLine()); + } + List fields = extractFields(classNode); + LanguageSpecifics classSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractClassModifiers(classNode.access)) + .addInterfaces(extractInterfaces(classNode)) + .addAnnotations(extractAnnotations(classNode.visibleAnnotations)) + .superClass(Strings.getClassName(classNode.superName)) + .build(); + Scope classScope = + Scope.builder(ScopeType.CLASS, sourceFile, classStartLine, classEndLine) + .name(Strings.getClassName(classNode.name)) + .scopes(methodScopes) + .symbols(fields) + .languageSpecifics(classSpecifics) + .build(); + return Scope.builder(ScopeType.JAR, jarName, 0, 0) + .name(jarName) + .scopes(new ArrayList<>(Collections.singletonList(classScope))) + .build(); + } catch (Exception ex) { + LoggerFactory.getLogger(SymbolExtractor.class).info("", ex); + return null; + } + } + + private static Collection extractInterfaces(ClassNode classNode) { + if (classNode.interfaces.isEmpty()) { + return Collections.emptyList(); + } + return classNode.interfaces.stream().map(Strings::getClassName).collect(Collectors.toList()); + } + + private static List extractFields(ClassNode classNode) { + List fields = new ArrayList<>(); + for (FieldNode fieldNode : classNode.fields) { + SymbolType symbolType = + ASMHelper.isStaticField(fieldNode) ? SymbolType.STATIC_FIELD : SymbolType.FIELD; + LanguageSpecifics fieldSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractFieldModifiers(fieldNode.access)) + .addAnnotations(extractAnnotations(fieldNode.visibleAnnotations)) + .build(); + fields.add( + new Symbol( + symbolType, + fieldNode.name, + 0, + Type.getType(fieldNode.desc).getClassName(), + fieldSpecifics)); + } + return fields; + } + + private static List extractMethods(ClassNode classNode, String sourceFile) { + List methodScopes = new ArrayList<>(); + for (MethodNode method : classNode.methods) { + MethodLineInfo methodLineInfo = extractMethodLineInfo(method); + List varScopes = new ArrayList<>(); + List methodSymbols = new ArrayList<>(); + int localVarBaseSlot = extractArgs(method, methodSymbols, methodLineInfo.start); + extractScopesFromVariables( + sourceFile, method, methodLineInfo.lineMap, varScopes, localVarBaseSlot); + ScopeType methodScopeType = ScopeType.METHOD; + if (method.name.startsWith("lambda$")) { + methodScopeType = ScopeType.CLOSURE; + } + LanguageSpecifics methodSpecifics = + new LanguageSpecifics.Builder() + .addModifiers(extractMethodModifiers(classNode, method, method.access)) + .addAnnotations(extractAnnotations(method.visibleAnnotations)) + .returnType(Type.getType(method.desc).getReturnType().getClassName()) + .build(); + Scope methodScope = + Scope.builder(methodScopeType, sourceFile, methodLineInfo.start, methodLineInfo.end) + .name(method.name) + .scopes(varScopes) + .symbols(methodSymbols) + .languageSpecifics(methodSpecifics) + .build(); + methodScopes.add(methodScope); + } + return methodScopes; + } + + private static Collection extractClassModifiers(int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_SUPER: + break; // not interesting + case Opcodes.ACC_INTERFACE: + results.add("interface"); + break; + case Opcodes.ACC_ABSTRACT: + results.add("abstract"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + case Opcodes.ACC_ANNOTATION: + results.add("annotation"); + break; + case Opcodes.ACC_ENUM: + results.add("enum"); + break; + case Opcodes.ACC_DEPRECATED: + results.add("deprecated"); + break; + default: + throw new IllegalArgumentException("Invalid access modifiers: " + bit); + } + } + return results; + } + + private static Collection extractMethodModifiers( + ClassNode classNode, MethodNode methodNode, int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_SYNCHRONIZED: + results.add("synchronized"); + break; + case Opcodes.ACC_BRIDGE: + results.add("(bridge)"); + break; + case Opcodes.ACC_VARARGS: + results.add("(varargs)"); + break; + case Opcodes.ACC_NATIVE: + results.add("native"); + break; + case Opcodes.ACC_ABSTRACT: + results.add("abstract"); + break; + case Opcodes.ACC_STRICT: + results.add("strictfp"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + case Opcodes.ACC_DEPRECATED: + results.add("deprecated"); + break; + default: + throw new IllegalArgumentException( + "Invalid access modifiers method[" + methodNode.name + methodNode.desc + "]: " + bit); + } + } + // if class is an interface && method as code this is a default method + if ((classNode.access & Opcodes.ACC_INTERFACE) > 0 && methodNode.instructions.size() > 0) { + results.add("default"); + } + return results; + } + + private static Collection extractFieldModifiers(int access) { + List results = new ArrayList<>(); + for (int remaining = access, bit; remaining != 0; remaining -= bit) { + bit = Integer.lowestOneBit(remaining); + switch (bit) { + case Opcodes.ACC_PUBLIC: + results.add("public"); + break; + case Opcodes.ACC_PRIVATE: + results.add("private"); + break; + case Opcodes.ACC_PROTECTED: + results.add("protected"); + break; + case Opcodes.ACC_STATIC: + results.add("static"); + break; + case Opcodes.ACC_FINAL: + results.add("final"); + break; + case Opcodes.ACC_VOLATILE: + results.add("volatile"); + break; + case Opcodes.ACC_TRANSIENT: + results.add("transient"); + break; + case Opcodes.ACC_SYNTHETIC: + results.add("synthetic"); + break; + case Opcodes.ACC_ENUM: + results.add("enum"); + break; + case Opcodes.ACC_DEPRECATED: + results.add("deprecated"); + break; + default: + throw new IllegalArgumentException("Invalid access modifiers: " + bit); + } + } + return results; + } + + private static Collection extractAnnotations(List annotationNodes) { + if (annotationNodes == null || annotationNodes.isEmpty()) { + return Collections.emptyList(); + } + List results = new ArrayList<>(); + for (AnnotationNode annotationNode : annotationNodes) { + StringBuilder sb = new StringBuilder("@"); + sb.append(Type.getType(annotationNode.desc).getClassName()); + results.add(sb.toString()); + } + return results; + } + + private static String extractSourceFile(ClassNode classNode) { + String packageName = classNode.name; + int idx = packageName.lastIndexOf('/'); + packageName = idx >= 0 ? packageName.substring(0, idx + 1) : ""; + return packageName + classNode.sourceFile; + } + + private static int extractArgs( + MethodNode method, List methodSymbols, int methodStartLine) { + boolean isStatic = (method.access & Opcodes.ACC_STATIC) != 0; + int slot = isStatic ? 0 : 1; + if (method.localVariables == null || method.localVariables.size() == 0) { + return slot; + } + Type[] argTypes = Type.getArgumentTypes(method.desc); + if (argTypes.length == 0) { + return slot; + } + List sortedLocalVars = sortLocalVariables(method.localVariables); + LocalVariableNode[] localVarsBySlot = createLocalVarNodes(sortedLocalVars); + adjustLocalVarsBasedOnArgs(isStatic, localVarsBySlot, argTypes, sortedLocalVars); + for (Type argType : argTypes) { + if (slot >= localVarsBySlot.length) { + break; + } + String argName = localVarsBySlot[slot] != null ? localVarsBySlot[slot].name : "p" + slot; + methodSymbols.add( + new Symbol(SymbolType.ARG, argName, methodStartLine, argType.getClassName(), null)); + slot += argType.getSize(); + } + return slot; + } + + private static void extractScopesFromVariables( + String sourceFile, + MethodNode methodNode, + Map monotonicLineMap, + List varScopes, + int localVarBaseSlot) { + if (methodNode.localVariables == null) { + return; + } + // using a LinkedHashMap only for having a stable order of local scopes (tests) + Map> varsByEndLabel = new LinkedHashMap<>(); + for (int i = 0; i < methodNode.localVariables.size(); i++) { + LocalVariableNode localVariable = methodNode.localVariables.get(i); + if (localVariable.index < localVarBaseSlot) { + continue; + } + varsByEndLabel.merge( + localVariable.end, + new ArrayList<>(Collections.singletonList(localVariable)), + (curr, next) -> { + curr.addAll(next); + return curr; + }); + } + List tmpScopes = new ArrayList<>(); + for (Map.Entry> entry : varsByEndLabel.entrySet()) { + List varSymbols = new ArrayList<>(); + int minLine = Integer.MAX_VALUE; + for (LocalVariableNode var : entry.getValue()) { + int line = monotonicLineMap.get(var.start.getLabel()); + minLine = Math.min(line, minLine); + varSymbols.add( + new Symbol( + SymbolType.LOCAL, var.name, line, Type.getType(var.desc).getClassName(), null)); + } + int endLine = monotonicLineMap.get(entry.getKey().getLabel()); + Scope varScope = + Scope.builder(ScopeType.LOCAL, sourceFile, minLine, endLine) + .symbols(varSymbols) + .scopes(new ArrayList<>()) + .build(); + tmpScopes.add(varScope); + } + nestScopes(varScopes, tmpScopes); + } + + private static Scope removeWidestScope(List scopes) { + Scope widestScope = null; + for (Scope scope : scopes) { + widestScope = widestScope != null ? maxScope(widestScope, scope) : scope; + } + // Remove the actual widest instance from the list, based on reference equality + scopes.remove(widestScope); + return widestScope; + } + + private static void nestScopes(List outerScopes, List scopes) { + Scope widestScope = removeWidestScope(scopes); + if (widestScope == null) { + return; + } + outerScopes.add(widestScope); + for (Scope scope : scopes) { + boolean added = false; + for (Scope outerScope : outerScopes) { + if (isInnerScope(outerScope, scope)) { + outerScope.getScopes().add(scope); + added = true; + break; + } + } + if (!added) { + outerScopes.add(scope); + } + } + for (Scope outerScope : outerScopes) { + List tmpScopes = new ArrayList<>(outerScope.getScopes()); + outerScope.getScopes().clear(); + nestScopes(outerScope.getScopes(), tmpScopes); + } + } + + private static boolean isInnerScope(Scope enclosingScope, Scope scope) { + return scope.getStartLine() >= enclosingScope.getStartLine() + && scope.getEndLine() <= enclosingScope.getEndLine(); + } + + private static Scope maxScope(Scope scope1, Scope scope2) { + return scope1.getStartLine() > scope2.getStartLine() + || scope1.getEndLine() < scope2.getEndLine() + ? scope2 + : scope1; + } + + private static int getFirstLine(MethodNode methodNode) { + AbstractInsnNode node = methodNode.instructions.getFirst(); + while (node != null) { + if (node.getType() == AbstractInsnNode.LINE) { + LineNumberNode lineNumberNode = (LineNumberNode) node; + return lineNumberNode.line; + } + node = node.getNext(); + } + return 0; + } + + private static MethodLineInfo extractMethodLineInfo(MethodNode methodNode) { + Map map = new HashMap<>(); + int startLine = getFirstLine(methodNode); + int maxLine = startLine; + AbstractInsnNode node = methodNode.instructions.getFirst(); + while (node != null) { + if (node.getType() == AbstractInsnNode.LINE) { + LineNumberNode lineNumberNode = (LineNumberNode) node; + maxLine = Math.max(lineNumberNode.line, maxLine); + } + if (node.getType() == AbstractInsnNode.LABEL) { + if (node instanceof LabelNode) { + LabelNode labelNode = (LabelNode) node; + map.put(labelNode.getLabel(), maxLine); + } + } + node = node.getNext(); + } + return new MethodLineInfo(startLine, maxLine, map); + } + + private static ClassNode parseClassFile(byte[] classfileBuffer) { + ClassReader reader = new ClassReader(classfileBuffer); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, ClassReader.SKIP_FRAMES); + return classNode; + } + + public static class MethodLineInfo { + final int start; + final int end; + final Map lineMap; + + public MethodLineInfo(int start, int end, Map lineMap) { + this.start = start; + this.end = end; + this.lineMap = lineMap; + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolType.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolType.java new file mode 100644 index 00000000000..94b5b889c74 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolType.java @@ -0,0 +1,8 @@ +package com.datadog.debugger.symbol; + +public enum SymbolType { + FIELD, + STATIC_FIELD, + ARG, + LOCAL +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/uploader/BatchUploader.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/uploader/BatchUploader.java index 3f7a9fe813e..15dbaaf604d 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/uploader/BatchUploader.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/uploader/BatchUploader.java @@ -18,10 +18,10 @@ import java.util.concurrent.TimeoutException; import okhttp3.Call; import okhttp3.Callback; -import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import okhttp3.HttpUrl; import okhttp3.MediaType; +import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; @@ -32,6 +32,30 @@ /** Handles batching logic of upload requests sent to the intake */ public class BatchUploader { + public static class MultiPartContent { + private final byte[] content; + private final String partName; + private final String fileName; + + public MultiPartContent(byte[] content, String partName, String fileName) { + this.content = content; + this.partName = partName; + this.fileName = fileName; + } + + public byte[] getContent() { + return content; + } + + public String getPartName() { + return partName; + } + + public String getFileName() { + return fileName; + } + } + private static final Logger log = LoggerFactory.getLogger(BatchUploader.class); private static final int MINUTES_BETWEEN_ERROR_LOG = 5; private static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); @@ -54,23 +78,23 @@ public class BatchUploader { private final Phaser inflightRequests = new Phaser(1); - public BatchUploader(Config config) { - this(config, new RatelimitedLogger(log, MINUTES_BETWEEN_ERROR_LOG, TimeUnit.MINUTES)); + public BatchUploader(Config config, String endpoint) { + this(config, endpoint, new RatelimitedLogger(log, MINUTES_BETWEEN_ERROR_LOG, TimeUnit.MINUTES)); } - BatchUploader(Config config, RatelimitedLogger ratelimitedLogger) { - this(config, ratelimitedLogger, ContainerInfo.get().containerId); + BatchUploader(Config config, String endpoint, RatelimitedLogger ratelimitedLogger) { + this(config, endpoint, ratelimitedLogger, ContainerInfo.get().containerId); } // Visible for testing - BatchUploader(Config config, RatelimitedLogger ratelimitedLogger, String containerId) { + BatchUploader( + Config config, String endpoint, RatelimitedLogger ratelimitedLogger, String containerId) { instrumentTheWorld = config.isDebuggerInstrumentTheWorld(); - String url = config.getFinalDebuggerSnapshotUrl(); - if (url == null || url.length() == 0) { - throw new IllegalArgumentException("Snapshot url is empty"); + if (endpoint == null || endpoint.length() == 0) { + throw new IllegalArgumentException("Endpoint url is empty"); } - urlBase = HttpUrl.get(url); - log.debug("Started SnapshotUploader with target url {}", urlBase); + urlBase = HttpUrl.get(endpoint); + log.debug("Started BatchUploader with target url {}", urlBase); apiKey = config.getApiKey(); this.ratelimitedLogger = ratelimitedLogger; responseCallback = new ResponseCallback(ratelimitedLogger, inflightRequests); @@ -84,10 +108,6 @@ public BatchUploader(Config config) { new SynchronousQueue<>(), new AgentThreadFactory(DEBUGGER_HTTP_DISPATCHER)); this.containerId = containerId; - // Reusing connections causes non daemon threads to be created which causes agent to prevent app - // from exiting. See https://github.com/square/okhttp/issues/4029 for some details. - ConnectionPool connectionPool = new ConnectionPool(MAX_RUNNING_REQUESTS, 1, TimeUnit.SECONDS); - Duration requestTimeout = Duration.ofSeconds(config.getDebuggerUploadTimeout()); client = OkHttpUtils.buildHttpClient( @@ -110,21 +130,41 @@ public void upload(byte[] batch) { } public void upload(byte[] batch, String tags) { + doUpload(() -> makeUploadRequest(batch, tags)); + } + + public void uploadAsMultipart(String tags, MultiPartContent... parts) { + doUpload(() -> makeMultipartUploadRequest(tags, parts)); + } + + private void makeMultipartUploadRequest(String tags, MultiPartContent[] parts) { + int contentLength = 0; + MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); + for (MultiPartContent part : parts) { + RequestBody fileBody = RequestBody.create(APPLICATION_JSON, part.content); + contentLength += part.content.length; + builder.addFormDataPart(part.partName, part.fileName, fileBody); + } + MultipartBody body = builder.build(); + buildAndSendRequest(body, contentLength, tags); + } + + private void doUpload(Runnable makeRequest) { if (instrumentTheWorld) { // no upload in Instrument-The-World mode return; } try { if (canEnqueueMoreRequests()) { - makeUploadRequest(batch, tags); + makeRequest.run(); debuggerMetrics.count("batch.uploaded", 1); } else { debuggerMetrics.count("request.queue.full", 1); ratelimitedLogger.warn("Cannot upload batch data: too many enqueued requests!"); } - } catch (final IllegalStateException | IOException e) { + } catch (Exception ex) { debuggerMetrics.count("batch.upload.error", 1); - ratelimitedLogger.warn("Problem uploading batch!", e); + ratelimitedLogger.warn("Problem uploading batch!", ex); } } @@ -136,11 +176,15 @@ OkHttpClient getClient() { return client; } - private void makeUploadRequest(byte[] json, String tags) throws IOException { + private void makeUploadRequest(byte[] json, String tags) { + int contentLength = json.length; // use RequestBody.create(MediaType, byte[]) to avoid changing Content-Type to // "Content-Type: application/json; charset=UTF-8" which is not recognized - int contentLength = json.length; RequestBody body = RequestBody.create(APPLICATION_JSON, json); + buildAndSendRequest(body, contentLength, tags); + } + + private void buildAndSendRequest(RequestBody body, int contentLength, String tags) { debuggerMetrics.histogram("batch.uploader.request.size", contentLength); if (log.isDebugEnabled()) { log.debug("Uploading batch data size={} bytes", contentLength); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java index 47f1828e773..31e132639a6 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java @@ -46,4 +46,8 @@ public static JsonAdapter> createGenericAdapter() { ParameterizedType type = Types.newParameterizedType(Map.class, String.class, Object.class); return new Moshi.Builder().build().adapter(type); } + + public static Moshi createMoshiSymbol() { + return new Moshi.Builder().build(); + } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiSnapshotHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiSnapshotHelper.java index 7cec03f035c..5f5a9e63ee4 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiSnapshotHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiSnapshotHelper.java @@ -44,7 +44,8 @@ public class MoshiSnapshotHelper { public static final String COLLECTION_SIZE_REASON = "collectionSize"; public static final String TIMEOUT_REASON = "timeout"; public static final String DEPTH_REASON = "depth"; - public static final String REDACTED_REASON = "redacted"; + public static final String REDACTED_IDENT_REASON = "redactedIdent"; + public static final String REDACTED_TYPE_REASON = "redactedType"; public static final String TYPE = "type"; public static final String VALUE = "value"; public static final String FIELDS = "fields"; @@ -462,10 +463,16 @@ public void notCaptured(SerializerWithLimits.NotCapturedReason reason) throws Ex jsonWriter.value(TIMEOUT_REASON); break; } - case REDACTED: + case REDACTED_IDENT: { jsonWriter.name(NOT_CAPTURED_REASON); - jsonWriter.value(REDACTED_REASON); + jsonWriter.value(REDACTED_IDENT_REASON); + break; + } + case REDACTED_TYPE: + { + jsonWriter.name(NOT_CAPTURED_REASON); + jsonWriter.value(REDACTED_TYPE_REASON); break; } default: diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java index 68a7530a238..5d2da23f805 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SerializerWithLimits.java @@ -50,7 +50,8 @@ enum NotCapturedReason { MAX_DEPTH, FIELD_COUNT, TIMEOUT, - REDACTED + REDACTED_IDENT, + REDACTED_TYPE } public interface TokenWriter { @@ -120,8 +121,14 @@ public void serialize(Object value, String type, Limits limits) throws Exception throw new IllegalArgumentException("Type is required for serialization"); } tokenWriter.prologue(value, type); - if (value == REDACTED_VALUE || Redaction.isRedactedType(type)) { - tokenWriter.notCaptured(NotCapturedReason.REDACTED); + NotCapturedReason reason = null; + if (value == REDACTED_VALUE) { + reason = NotCapturedReason.REDACTED_IDENT; + } else if (Redaction.isRedactedType(type)) { + reason = NotCapturedReason.REDACTED_TYPE; + } + if (reason != null) { + tokenWriter.notCaptured(reason); tokenWriter.epilogue(value); return; } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java new file mode 100644 index 00000000000..216441237b0 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotPruner.java @@ -0,0 +1,240 @@ +package com.datadog.debugger.util; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.function.Supplier; + +public class SnapshotPruner { + private static final String NOT_CAPTURED_REASON = "notCapturedReason"; + private static final String DEPTH = "depth"; + private static final String PRUNED = "{\"pruned\":true}"; + + private State state = State.OBJECT; + private final Deque stack = new ArrayDeque<>(32); + private int currentLevel; + private int strMatchIdx; + private Supplier onStringMatches; + private String matchingString; + private Node root; + + public static String prune(String snapshot, int maxTargetedSize, int minLevel) { + int delta = snapshot.length() - maxTargetedSize; + if (delta <= 0) { + return snapshot; + } + SnapshotPruner snapshotPruner = new SnapshotPruner(snapshot); + Collection leaves = snapshotPruner.getLeaves(minLevel); + PriorityQueue sortedLeaves = + new PriorityQueue<>( + Comparator.comparing((Node n) -> n.notCapturedDepth) + .thenComparingInt((Node n) -> n.level) + .thenComparing((Node n) -> n.notCaptured) + .thenComparingInt(Node::size) + .reversed()); + sortedLeaves.addAll(leaves); + int total = 0; + Map nodes = new HashMap<>(); + while (!sortedLeaves.isEmpty()) { + Node leaf = sortedLeaves.poll(); + nodes.put(leaf.start, leaf); + total += leaf.size() - PRUNED.length(); + if (total > delta) break; + Node parent = leaf.parent; + if (parent == null) { + break; + } + parent.pruned++; + if (parent.pruned >= parent.children.size() && parent.level >= minLevel) { + // We have pruned all the children of this parent node, so we can + // treat it as a leaf now. + parent.notCaptured = true; + parent.notCapturedDepth = true; + sortedLeaves.offer(parent); + for (Node child : parent.children) { + nodes.remove(child.start); + total -= child.size() - PRUNED.length(); + } + } + } + List prunedNodes = new ArrayList<>(nodes.values()); + prunedNodes.sort(Comparator.comparing((Node n) -> n.start)); + StringBuilder sb = new StringBuilder(); + sb.append(snapshot, 0, prunedNodes.get(0).start); + for (int i = 1; i < prunedNodes.size(); i++) { + sb.append(PRUNED); + sb.append(snapshot, prunedNodes.get(i - 1).end + 1, prunedNodes.get(i).start); + } + sb.append(PRUNED); + sb.append(snapshot, prunedNodes.get(prunedNodes.size() - 1).end + 1, snapshot.length()); + return sb.toString(); + } + + private Collection getLeaves(int minLevel) { + if (root == null) { + return Collections.emptyList(); + } + return root.getLeaves(minLevel); + } + + private SnapshotPruner(String snapshot) { + for (int i = 0; i < snapshot.length(); i++) { + state = state.parse(this, snapshot.charAt(i), i); + if (state == null) { + break; + } + } + } + + enum State { + OBJECT { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '{': + { + Node n = new Node(index, pruner.currentLevel); + pruner.currentLevel++; + if (!pruner.stack.isEmpty()) { + n.parent = pruner.stack.peekLast(); + n.parent.children.add(n); + } + pruner.stack.addLast(n); + return this; + } + case '}': + { + Node n = pruner.stack.removeLast(); + n.end = index; + pruner.currentLevel--; + if (pruner.stack.isEmpty()) { + pruner.root = n; + return null; + } + return this; + } + case '"': + { + pruner.strMatchIdx = 0; + pruner.matchingString = NOT_CAPTURED_REASON; + pruner.onStringMatches = + () -> { + Node n = pruner.stack.peekLast(); + if (n == null) { + throw new IllegalStateException("empty stack"); + } + n.notCaptured = true; + return NOT_CAPTURED; + }; + return STRING; + } + default: + return this; + } + } + }, + STRING { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '"': + { + if (pruner.strMatchIdx == pruner.matchingString.length()) { + return pruner.onStringMatches.get(); + } + return OBJECT; + } + case '\\': + { + pruner.strMatchIdx = -1; + return ESCAPE; + } + default: + if (pruner.strMatchIdx > -1) { + char current = pruner.matchingString.charAt(pruner.strMatchIdx++); + if (c != current) { + pruner.strMatchIdx = -1; + } + } + return this; + } + } + }, + NOT_CAPTURED { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + switch (c) { + case '"': + { + pruner.strMatchIdx = 0; + pruner.matchingString = DEPTH; + pruner.onStringMatches = + () -> { + Node n = pruner.stack.peekLast(); + if (n == null) { + throw new IllegalStateException("empty stack"); + } + n.notCapturedDepth = true; + return OBJECT; + }; + return STRING; + } + case ' ': + case ':': + case '\n': + case '\t': + case '\r': + return this; + default: + return OBJECT; + } + } + }, + ESCAPE { + @Override + public State parse(SnapshotPruner pruner, char c, int index) { + return STRING; + } + }; + + public abstract State parse(SnapshotPruner pruner, char c, int index); + } + + private static class Node { + int pruned; + Node parent; + List children = new ArrayList<>(); + final int start; + int end; + final int level; + boolean notCaptured; + boolean notCapturedDepth; + + public Node(int start, int level) { + this.start = start; + this.level = level; + } + + public Collection getLeaves(int minLevel) { + if (children.isEmpty() && level >= minLevel) { + return Collections.singleton(this); + } + Collection results = new ArrayList<>(); + for (int i = children.size() - 1; i >= 0; i--) { + results.addAll(children.get(i).getLeaves(minLevel)); + } + return results; + } + + public int size() { + return end - start + 1; + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java deleted file mode 100644 index 43232c0647d..00000000000 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/SnapshotSlicer.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.datadog.debugger.util; - -import static com.datadog.debugger.util.MoshiSnapshotHelper.DEPTH_REASON; -import static com.datadog.debugger.util.MoshiSnapshotHelper.ELEMENTS; -import static com.datadog.debugger.util.MoshiSnapshotHelper.ENTRIES; -import static com.datadog.debugger.util.MoshiSnapshotHelper.FIELDS; -import static com.datadog.debugger.util.MoshiSnapshotHelper.NOT_CAPTURED_REASON; - -import com.squareup.moshi.JsonReader; -import com.squareup.moshi.JsonWriter; -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Deque; -import okio.Buffer; - -/** Reduces the serialized snapshot by removing last depth level (fields or collections) */ -public class SnapshotSlicer { - - private final Deque tokenLevels = new ArrayDeque<>(32); - private final Deque fieldsLevels = new ArrayDeque<>(32); - private final int maxDepth; - private boolean copy = true; - private int skipTokenLevel = -1; - private JsonReader jsonReader; - private JsonWriter jsonWriter; - - public static String slice(int maxDepth, String snapshot) { - return new SnapshotSlicer(maxDepth).internalSlice(snapshot); - } - - private SnapshotSlicer(int maxDepth) { - this.maxDepth = maxDepth; - } - - private String internalSlice(String snapshot) { - try { - Buffer writeBuffer = new Buffer(); - jsonWriter = JsonWriter.of(writeBuffer); - jsonReader = JsonReader.of(new Buffer().writeUtf8(snapshot)); - while (jsonReader.hasNext()) { - JsonReader.Token token = jsonReader.peek(); - processToken(token); - handleLevelEnd(); - } - return writeBuffer.readUtf8(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private void processToken(JsonReader.Token token) throws IOException { - switch (token) { - case BEGIN_ARRAY: - tokenLevels.addLast(token); - jsonReader.beginArray(); - if (copy) { - jsonWriter.beginArray(); - } - break; - case BEGIN_OBJECT: - tokenLevels.addLast(token); - jsonReader.beginObject(); - if (copy) { - jsonWriter.beginObject(); - } - break; - case NAME: - String name = jsonReader.nextName(); - if (FIELDS.equals(name)) { - fieldsLevels.addLast(tokenLevels.size()); - if (copy && fieldsLevels.size() > maxDepth) { - jsonWriter.name(NOT_CAPTURED_REASON); - jsonWriter.value(DEPTH_REASON); - copy = false; - skipTokenLevel = tokenLevels.size(); - } - } - if (ELEMENTS.equals(name) || ENTRIES.equals(name)) { - fieldsLevels.addLast(tokenLevels.size()); - } - if (copy) { - jsonWriter.name(name); - } - break; - case BOOLEAN: - boolean b = jsonReader.nextBoolean(); - if (copy) { - jsonWriter.value(b); - } - break; - case NULL: - jsonReader.nextNull(); - if (copy) { - jsonWriter.nullValue(); - } - break; - case NUMBER: - // Moshi always consider numbers as decimal. - // need to parse it as string and detect if dot is present - // or not to determine ints/longs vs doubles - String numberStrValue = jsonReader.nextString(); - if (copy) { - if (numberStrValue.indexOf('.') > 0) { - jsonWriter.value(Double.parseDouble(numberStrValue)); - } else { - jsonWriter.value(Long.parseLong(numberStrValue)); - } - } - break; - case STRING: - String s = jsonReader.nextString(); - if (copy) { - jsonWriter.value(s); - } - break; - default: - throw new UnsupportedOperationException("Unsupported token: " + token); - } - } - - private void handleLevelEnd() throws IOException { - while (!jsonReader.hasNext() && !tokenLevels.isEmpty()) { - JsonReader.Token lastToken = tokenLevels.removeLast(); - switch (lastToken) { - case BEGIN_ARRAY: - jsonReader.endArray(); - if (copy) { - jsonWriter.endArray(); - } - break; - case BEGIN_OBJECT: - jsonReader.endObject(); - if (copy) { - jsonWriter.endObject(); - } - if (!fieldsLevels.isEmpty() && tokenLevels.size() == fieldsLevels.peekLast().intValue()) { - fieldsLevels.removeLast(); - } - if (!copy && skipTokenLevel == tokenLevels.size()) { - copy = true; - skipTokenLevel = -1; - } - break; - default: - throw new UnsupportedOperationException("Unsupported last token: " + lastToken); - } - } - } -} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/StringTokenWriter.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/StringTokenWriter.java index 838f1c45491..0a553863521 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/StringTokenWriter.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/StringTokenWriter.java @@ -1,5 +1,9 @@ package com.datadog.debugger.util; +import static com.datadog.debugger.util.MoshiSnapshotHelper.REDACTED_IDENT_REASON; +import static com.datadog.debugger.util.MoshiSnapshotHelper.REDACTED_TYPE_REASON; +import static com.datadog.debugger.util.MoshiSnapshotHelper.TIMEOUT_REASON; + import com.datadog.debugger.el.Value; import datadog.trace.bootstrap.debugger.EvaluationError; import java.lang.reflect.Field; @@ -149,14 +153,19 @@ public void handleFieldException(Exception ex, Field field) { public void notCaptured(SerializerWithLimits.NotCapturedReason reason) { switch (reason) { case MAX_DEPTH: - case TIMEOUT: sb.append("..."); break; + case TIMEOUT: + sb.append("{").append(TIMEOUT_REASON).append("}"); + break; case FIELD_COUNT: sb.append(", ..."); break; - case REDACTED: - sb.append("{redacted}"); + case REDACTED_IDENT: + sb.append("{").append(REDACTED_IDENT_REASON).append("}"); + break; + case REDACTED_TYPE: + sb.append("{").append(REDACTED_TYPE_REASON).append("}"); break; default: throw new RuntimeException("Unsupported NotCapturedReason: " + reason); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java index fb4ee1cfccf..60e25f8254c 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ValueScriptHelper.java @@ -9,10 +9,8 @@ import java.time.temporal.ChronoUnit; public class ValueScriptHelper { - private static final Limits LIMITS = new Limits(1, 3, 255, 5); - public static void serializeValue( - StringBuilder sb, String expr, Object value, CapturedContext.Status status) { + StringBuilder sb, String expr, Object value, CapturedContext.Status status, Limits limits) { Duration timeout = Duration.of(Config.get().getDebuggerCaptureTimeout(), ChronoUnit.MILLIS); TimeoutChecker timeoutChecker = new TimeoutChecker(timeout); SerializerWithLimits serializer = @@ -21,7 +19,7 @@ public static void serializeValue( serializer.serialize( value, value != null ? value.getClass().getTypeName() : Object.class.getTypeName(), - LIMITS); + limits); } catch (Exception ex) { status.addError(new EvaluationError(expr, ex.getMessage())); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index e54c151dfb1..5c5a7cea0df 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -4,7 +4,10 @@ import static com.datadog.debugger.util.MoshiSnapshotHelper.DEPTH_REASON; import static com.datadog.debugger.util.MoshiSnapshotHelper.FIELD_COUNT_REASON; import static com.datadog.debugger.util.MoshiSnapshotHelper.NOT_CAPTURED_REASON; +import static com.datadog.debugger.util.MoshiSnapshotHelper.REDACTED_IDENT_REASON; +import static com.datadog.debugger.util.MoshiSnapshotHelper.REDACTED_TYPE_REASON; import static com.datadog.debugger.util.TestHelper.setFieldInConfig; +import static datadog.trace.bootstrap.debugger.util.Redaction.REDACTED_VALUE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -27,7 +30,6 @@ import com.datadog.debugger.instrumentation.InstrumentationResult; import com.datadog.debugger.probe.LogProbe; import com.datadog.debugger.probe.ProbeDefinition; -import com.datadog.debugger.sink.DebuggerSink; import com.datadog.debugger.sink.ProbeStatusSink; import com.datadog.debugger.sink.Snapshot; import com.datadog.debugger.util.MoshiHelper; @@ -35,6 +37,7 @@ import com.datadog.debugger.util.SerializerWithLimits; import com.squareup.moshi.JsonAdapter; import datadog.trace.api.Config; +import datadog.trace.api.sampling.Sampler; import datadog.trace.bootstrap.debugger.*; import datadog.trace.bootstrap.debugger.el.ValueReferences; import datadog.trace.bootstrap.debugger.util.Redaction; @@ -117,7 +120,7 @@ public void methodNotFound() throws IOException, URISyntaxException { installSingleProbe(CLASS_NAME, "foobar", null); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); verify(probeStatusSink) .addError(eq(PROBE_ID), eq("Cannot find method CapturedSnapshot01::foobar")); } @@ -129,25 +132,25 @@ public void methodProbe() throws IOException, URISyntaxException { installSingleProbe(CLASS_NAME, "main", "int (java.lang.String)"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); Snapshot snapshot = assertOneSnapshot(listener); - Assertions.assertNotNull(snapshot.getCaptures().getEntry()); - Assertions.assertNotNull(snapshot.getCaptures().getReturn()); + assertNotNull(snapshot.getCaptures().getEntry()); + assertNotNull(snapshot.getCaptures().getReturn()); assertCaptureArgs(snapshot.getCaptures().getEntry(), "arg", "java.lang.String", "1"); assertCaptureArgs(snapshot.getCaptures().getReturn(), "arg", "java.lang.String", "1"); assertTrue(snapshot.getDuration() > 0); assertTrue(snapshot.getStack().size() > 0); - Assertions.assertEquals("CapturedSnapshot01.main", snapshot.getStack().get(0).getFunction()); + assertEquals("CapturedSnapshot01.main", snapshot.getStack().get(0).getFunction()); } @Test public void singleLineProbe() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; DebuggerTransformerTest.TestSnapshotListener listener = - installSingleProbe(CLASS_NAME, "main", "int (java.lang.String)", "8"); + installSingleProbeAtExit(CLASS_NAME, "main", "int (java.lang.String)", "8"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -155,7 +158,7 @@ public void singleLineProbe() throws IOException, URISyntaxException { assertCaptureArgs(snapshot.getCaptures().getLines().get(8), "arg", "java.lang.String", "1"); assertCaptureLocals(snapshot.getCaptures().getLines().get(8), "var1", "int", "1"); assertTrue(snapshot.getStack().size() > 0); - Assertions.assertEquals("CapturedSnapshot01.java", snapshot.getStack().get(0).getFileName()); + assertEquals("CapturedSnapshot01.java", snapshot.getStack().get(0).getFileName()); } @Test @@ -167,8 +170,8 @@ public void resolutionFails() throws IOException, URISyntaxException { DebuggerContext.init((id, clazz) -> null, null); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); - Assertions.assertEquals(0, listener.snapshots.size()); + assertEquals(3, result); + assertEquals(0, listener.snapshots.size()); } @Test @@ -186,8 +189,8 @@ public void resolutionThrows() throws IOException, URISyntaxException { null); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); - Assertions.assertEquals(0, listener.snapshots.size()); + assertEquals(3, result); + assertEquals(0, listener.snapshots.size()); } @Test @@ -197,7 +200,7 @@ public void constructor() throws IOException, URISyntaxException { installSingleProbe(CLASS_NAME, "", "(String, Object)"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "f").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); assertOneSnapshot(listener); } @@ -208,7 +211,7 @@ public void overloadedConstructor() throws IOException, URISyntaxException { installSingleProbe(CLASS_NAME, "", "()"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "f").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); assertOneSnapshot(listener); } @@ -218,7 +221,7 @@ public void veryOldClassFile() throws Exception { DebuggerTransformerTest.TestSnapshotListener listener = installSingleProbe(CLASS_NAME, "", "()"); Class testClass = Class.forName(CLASS_NAME); - Assertions.assertNotNull(testClass); + assertNotNull(testClass); testClass.newInstance(); assertOneSnapshot(listener); } @@ -229,7 +232,7 @@ public void oldJavacBug() throws Exception { DebuggerTransformerTest.TestSnapshotListener listener = installSingleProbe(CLASS_NAME, "main", null); Class testClass = Class.forName(CLASS_NAME); - Assertions.assertNotNull(testClass); + assertNotNull(testClass); int result = Reflect.on(testClass).call("main", "").get(); assertEquals(45, result); assertEquals(0, listener.snapshots.size()); @@ -242,7 +245,7 @@ public void nestedConstructor() throws Exception { installSingleProbe(CLASS_NAME, "", "(Throwable)"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "init").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); assertOneSnapshot(listener); } @@ -253,7 +256,7 @@ public void nestedConstructor2() throws Exception { installSingleProbe(CLASS_NAME, "", "(int)"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); } @@ -264,7 +267,7 @@ public void nestedConstructor3() throws Exception { installSingleProbe(CLASS_NAME, "", "(int, int, int)"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); } @@ -275,14 +278,14 @@ public void inheritedConstructor() throws Exception { installSingleProbe(CLASS_NAME + "$Inherited", "", null); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); assertCaptureFields( snapshot.getCaptures().getEntry(), "obj2", "java.lang.Object", (String) null); CapturedContext.CapturedValue obj2 = snapshot.getCaptures().getReturn().getFields().get("obj2"); Map fields = getFields(obj2); - Assertions.assertEquals(24, fields.get("intValue").getValue()); - Assertions.assertEquals(3.14, fields.get("doubleValue").getValue()); + assertEquals(24, fields.get("intValue").getValue()); + assertEquals(3.14, fields.get("doubleValue").getValue()); } @Test @@ -295,7 +298,7 @@ public void largeStackInheritedConstructor() throws Exception { createProbe(PROBE_ID2, CLASS_NAME, "", "(String, long, String)")); Class testClass = compileAndLoadClass(CLASS_NAME); long result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(4_000_000_001L, result); + assertEquals(4_000_000_001L, result); assertSnapshots(listener, 2, PROBE_ID2, PROBE_ID1); } @@ -309,7 +312,7 @@ public void multiMethods() throws IOException, URISyntaxException { createProbe(PROBE_ID2, CLASS_NAME, "f2", "(int)")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); List snapshots = assertSnapshots(listener, 2, PROBE_ID1, PROBE_ID2); Snapshot snapshot0 = snapshots.get(0); assertCaptureArgs(snapshot0.getCaptures().getEntry(), "value", "int", "31"); @@ -328,7 +331,7 @@ public void multiProbeSameMethod() throws IOException, URISyntaxException { installProbes(CLASS_NAME, probe, probe2); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); List snapshots = assertSnapshots(listener, 2, PROBE_ID1, PROBE_ID2); Snapshot snapshot0 = snapshots.get(0); assertCaptureArgs(snapshot0.getCaptures().getEntry(), "value", "int", "31"); @@ -342,9 +345,9 @@ private List assertSnapshots( DebuggerTransformerTest.TestSnapshotListener listener, int expectedCount, ProbeId... probeIds) { - Assertions.assertEquals(expectedCount, listener.snapshots.size()); + assertEquals(expectedCount, listener.snapshots.size()); for (int i = 0; i < probeIds.length; i++) { - Assertions.assertEquals(probeIds[i].getId(), listener.snapshots.get(i).getProbe().getId()); + assertEquals(probeIds[i].getId(), listener.snapshots.get(i).getProbe().getId()); } return listener.snapshots; } @@ -356,7 +359,7 @@ public void catchBlock() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "f", "()")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "f").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); assertOneSnapshot(listener); } @@ -373,7 +376,7 @@ public void insideSynchronizedBlock() throws IOException, URISyntaxException { PROBE_ID, CLASS_NAME, "synchronizedBlock", "(int)", LINE_START + "-" + LINE_END)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "synchronizedBlock").get(); - Assertions.assertEquals(76, result); + assertEquals(76, result); List snapshots = assertSnapshots(listener, 10); int count = 31; for (int i = 0; i < 10; i++) { @@ -401,7 +404,7 @@ public void outsideSynchronizedBlock() throws IOException, URISyntaxException { PROBE_ID, CLASS_NAME, "synchronizedBlock", "(int)", LINE_START + "-" + LINE_END)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "synchronizedBlock").get(); - Assertions.assertEquals(76, result); + assertEquals(76, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -417,7 +420,7 @@ public void sourceFileProbe() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createSourceFileProbe(PROBE_ID, CLASS_NAME + ".java", 4)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -434,7 +437,7 @@ public void simpleSourceFileProbe() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createSourceFileProbe(PROBE_ID, "CapturedSnapshot10.java", 11)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -454,7 +457,7 @@ public void sourceFileProbeFullPath() throws IOException, URISyntaxException { createSourceFileProbe(PROBE_ID, "src/main/java/" + DIR_CLASS_NAME + ".java", 11)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -474,7 +477,7 @@ public void sourceFileProbeFullPathTopLevelClass() throws IOException, URISyntax createSourceFileProbe(PROBE_ID, "src/main/java/" + DIR_CLASS_NAME + ".java", 21)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(42 * 42, result); + assertEquals(42 * 42, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -496,7 +499,7 @@ public void methodProbeLineProbeMix() throws IOException, URISyntaxException { createProbe(PROBE_ID2, CLASS_NAME, "main", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); List snapshots = assertSnapshots(listener, 2, PROBE_ID1, PROBE_ID2); Snapshot snapshot0 = snapshots.get(0); assertNull(snapshot0.getCaptures().getEntry()); @@ -504,13 +507,13 @@ public void methodProbeLineProbeMix() throws IOException, URISyntaxException { Assertions.assertEquals(1, snapshot0.getCaptures().getLines().size()); Assertions.assertEquals( "com.datadog.debugger.CapturedSnapshot11", snapshot0.getProbe().getLocation().getType()); - Assertions.assertEquals("main", snapshot0.getProbe().getLocation().getMethod()); + assertEquals("main", snapshot0.getProbe().getLocation().getMethod()); assertCaptureArgs(snapshot0.getCaptures().getLines().get(10), "arg", "java.lang.String", "2"); assertCaptureLocals(snapshot0.getCaptures().getLines().get(10), "var1", "int", "1"); Snapshot snapshot1 = snapshots.get(1); - Assertions.assertEquals( + assertEquals( "com.datadog.debugger.CapturedSnapshot11", snapshot1.getProbe().getLocation().getType()); - Assertions.assertEquals("main", snapshot1.getProbe().getLocation().getMethod()); + assertEquals("main", snapshot1.getProbe().getLocation().getMethod()); assertCaptureArgs(snapshot1.getCaptures().getEntry(), "arg", "java.lang.String", "2"); assertCaptureReturnValue(snapshot1.getCaptures().getReturn(), "int", "2"); } @@ -524,7 +527,7 @@ public void sourceFileProbeScala() throws IOException, URISyntaxException { String source = getFixtureContent("/" + FILE_NAME); Class testClass = ScalaHelper.compileAndLoad(source, CLASS_NAME, FILE_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -543,7 +546,7 @@ public void sourceFileProbeGroovy() throws IOException, URISyntaxException { GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); Class testClass = groovyClassLoader.parseClass(source); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -559,13 +562,13 @@ public void sourceFileProbeKotlin() { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, createSourceFileProbe(PROBE_ID, CLASS_NAME + ".kt", 4)); URL resource = CapturedSnapshotTest.class.getResource("/" + CLASS_NAME + ".kt"); - Assertions.assertNotNull(resource); + assertNotNull(resource); List filesToDelete = new ArrayList<>(); Class testClass = KotlinHelper.compileAndLoad(CLASS_NAME, resource.getFile(), filesToDelete); try { Object companion = Reflect.on(testClass).get("Companion"); int result = Reflect.on(companion).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); Snapshot snapshot = assertOneSnapshot(listener); assertNull(snapshot.getCaptures().getEntry()); assertNull(snapshot.getCaptures().getReturn()); @@ -589,7 +592,7 @@ public void fieldExtractor() throws IOException, URISyntaxException { installProbes(CLASS_NAME, simpleDataProbe, compositeDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); List snapshots = assertSnapshots(listener, 2, PROBE_ID1, PROBE_ID2); Snapshot simpleSnapshot = snapshots.get(0); Map expectedSimpleFields = new HashMap<>(); @@ -621,7 +624,7 @@ public void fieldExtractorDeep2() throws IOException, URISyntaxException { installProbes(CLASS_NAME, compositeDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext.CapturedValue returnValue = snapshot.getCaptures().getReturn().getLocals().get("@return"); @@ -631,11 +634,10 @@ public void fieldExtractorDeep2() throws IOException, URISyntaxException { CapturedContext.CapturedValue s1 = fields.get("s1"); Map s1Fields = (Map) s1.getValue(); - Assertions.assertEquals("101", String.valueOf(s1Fields.get("intValue").getValue())); - Assertions.assertEquals("foo1", s1Fields.get("strValue").getValue()); - Assertions.assertEquals("null", String.valueOf(s1Fields.get("listValue").getValue())); - Assertions.assertEquals( - DEPTH_REASON, String.valueOf(s1Fields.get("listValue").getNotCapturedReason())); + assertEquals("101", String.valueOf(s1Fields.get("intValue").getValue())); + assertEquals("foo1", s1Fields.get("strValue").getValue()); + assertEquals("null", String.valueOf(s1Fields.get("listValue").getValue())); + assertEquals(DEPTH_REASON, String.valueOf(s1Fields.get("listValue").getNotCapturedReason())); } @Test @@ -647,7 +649,7 @@ public void fieldExtractorLength() throws IOException, URISyntaxException { installProbes(CLASS_NAME, simpleDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); Map expectedFields = new HashMap<>(); expectedFields.put("intValue", "42"); @@ -666,14 +668,13 @@ public void fieldExtractorDisabled() throws IOException, URISyntaxException { installProbes(CLASS_NAME, simpleDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext.CapturedValue simpleData = snapshot.getCaptures().getReturn().getLocals().get("simpleData"); Map fields = getFields(simpleData); - Assertions.assertEquals(1, fields.size()); - Assertions.assertEquals( - DEPTH_REASON, fields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); + assertEquals(1, fields.size()); + assertEquals(DEPTH_REASON, fields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); } @Test @@ -685,13 +686,13 @@ public void fieldExtractorDepth0() throws IOException, URISyntaxException { installProbes(CLASS_NAME, simpleDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext.CapturedValue simpleData = snapshot.getCaptures().getReturn().getLocals().get("simpleData"); Map simpleDataFields = getFields(simpleData); - Assertions.assertEquals(1, simpleDataFields.size()); - Assertions.assertEquals( + assertEquals(1, simpleDataFields.size()); + assertEquals( DEPTH_REASON, simpleDataFields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); } @@ -704,15 +705,15 @@ public void fieldExtractorDepth1() throws IOException, URISyntaxException { installProbes(CLASS_NAME, simpleDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext.CapturedValue simpleData = snapshot.getCaptures().getReturn().getLocals().get("simpleData"); Map simpleDataFields = getFields(simpleData); - Assertions.assertEquals(4, simpleDataFields.size()); - Assertions.assertEquals("foo", simpleDataFields.get("strValue").getValue()); - Assertions.assertEquals(42, simpleDataFields.get("intValue").getValue()); - Assertions.assertEquals(DEPTH_REASON, simpleDataFields.get("listValue").getNotCapturedReason()); + assertEquals(4, simpleDataFields.size()); + assertEquals("foo", simpleDataFields.get("strValue").getValue()); + assertEquals(42, simpleDataFields.get("intValue").getValue()); + assertEquals(DEPTH_REASON, simpleDataFields.get("listValue").getNotCapturedReason()); } @Test @@ -725,29 +726,28 @@ public void fieldExtractorCount2() throws IOException, URISyntaxException { installProbes(CLASS_NAME, compositeDataProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(143, result); + assertEquals(143, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext.CapturedValue returnValue = snapshot.getCaptures().getReturn().getLocals().get("@return"); - Assertions.assertEquals("CapturedSnapshot04$CompositeData", returnValue.getType()); + assertEquals("CapturedSnapshot04$CompositeData", returnValue.getType()); Map fields = getFields(returnValue); - Assertions.assertEquals(3, fields.size()); - Assertions.assertEquals( - FIELD_COUNT_REASON, fields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); + assertEquals(3, fields.size()); + assertEquals(FIELD_COUNT_REASON, fields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); Map s1Fields = (Map) fields.get("s1").getValue(); - Assertions.assertEquals("foo1", s1Fields.get("strValue").getValue()); - Assertions.assertEquals(101, s1Fields.get("intValue").getValue()); + assertEquals("foo1", s1Fields.get("strValue").getValue()); + assertEquals(101, s1Fields.get("intValue").getValue()); Map s2Fields = (Map) fields.get("s2").getValue(); - Assertions.assertEquals("foo2", s2Fields.get("strValue").getValue()); - Assertions.assertEquals(202, s2Fields.get("intValue").getValue()); + assertEquals("foo2", s2Fields.get("strValue").getValue()); + assertEquals(202, s2Fields.get("intValue").getValue()); CapturedContext.CapturedValue compositeData = snapshot.getCaptures().getReturn().getLocals().get("compositeData"); Map compositeDataFields = getFields(compositeData); - Assertions.assertEquals(3, compositeDataFields.size()); - Assertions.assertEquals( + assertEquals(3, compositeDataFields.size()); + assertEquals( FIELD_COUNT_REASON, compositeDataFields.get("@" + NOT_CAPTURED_REASON).getNotCapturedReason()); assertTrue(compositeDataFields.containsKey("s1")); @@ -765,7 +765,7 @@ public void uncaughtException() throws IOException, URISyntaxException { Reflect.on(testClass).call("main", "triggerUncaughtException").get(); Assertions.fail("should not reach this code"); } catch (ReflectException ex) { - Assertions.assertEquals("oops", ex.getCause().getCause().getMessage()); + assertEquals("oops", ex.getCause().getCause().getMessage()); } Snapshot snapshot = assertOneSnapshot(listener); assertCaptureThrowable( @@ -776,6 +776,40 @@ public void uncaughtException() throws IOException, URISyntaxException { 7); } + @Test + public void uncaughtExceptionCondition() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot05"; + final String LOG_TEMPLATE = "exception msg={@exception.detailMessage}"; + LogProbe probe = + createProbeBuilder(PROBE_ID, CLASS_NAME, "triggerUncaughtException", "()") + .evaluateAt(MethodLocation.EXIT) + .when( + new ProbeCondition( + DSL.when( + DSL.eq( + DSL.getMember(DSL.ref("@exception"), "detailMessage"), + DSL.value("oops"))), + "@exception.detailMessage == 'oops'")) + .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .build(); + DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, probe); + Class testClass = compileAndLoadClass(CLASS_NAME); + try { + Reflect.on(testClass).call("main", "triggerUncaughtException").get(); + Assertions.fail("should not reach this code"); + } catch (ReflectException ex) { + assertEquals("oops", ex.getCause().getCause().getMessage()); + } + Snapshot snapshot = assertOneSnapshot(listener); + assertEquals("exception msg=oops", snapshot.getMessage()); + assertCaptureThrowable( + snapshot.getCaptures().getReturn(), + "java.lang.IllegalStateException", + "oops", + "CapturedSnapshot05.triggerUncaughtException", + 7); + } + @Test public void caughtException() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot05"; @@ -784,7 +818,7 @@ public void caughtException() throws IOException, URISyntaxException { CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "triggerCaughtException", "()")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "triggerCaughtException").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); assertCaptureThrowable( snapshot.getCaptures().getCaughtExceptions().get(0), @@ -794,6 +828,25 @@ public void caughtException() throws IOException, URISyntaxException { 12); } + @Test + public void noUncaughtExceptionCondition() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + final String LOG_TEMPLATE = "exception?: {isUndefined(@exception)}"; + LogProbe probe = + createProbeBuilder(PROBE_ID, CLASS_NAME, "main", "int (String)") + .evaluateAt(MethodLocation.EXIT) + .when( + new ProbeCondition( + DSL.when(DSL.isUndefined(DSL.ref("@exception"))), "isUndefined(@exception)")) + .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .build(); + DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, probe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "2").get(); + assertEquals(2, result); + Snapshot snapshot = assertOneSnapshot(listener); + } + @Test public void rateLimitSnapshot() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; @@ -808,7 +861,7 @@ public void rateLimitSnapshot() throws IOException, URISyntaxException { Class testClass = compileAndLoadClass(CLASS_NAME); for (int i = 0; i < 100; i++) { int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); } assertTrue(listener.snapshots.size() < 20); } @@ -828,7 +881,7 @@ public void globalRateLimitSnapshot() throws IOException, URISyntaxException { Class testClass = compileAndLoadClass(CLASS_NAME); for (int i = 0; i < 100; i++) { int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); } assertTrue(listener.snapshots.size() < 20, "actual snapshots: " + listener.snapshots.size()); } @@ -865,11 +918,45 @@ public void simpleConditionTest() throws IOException, URISyntaxException { int result = Reflect.on(testClass).call("main", String.valueOf(i)).get(); assertTrue((i == 2 && result == 2) || result == 3); } - Assertions.assertEquals(1, listener.snapshots.size()); + assertEquals(1, listener.snapshots.size()); assertCaptureArgs( listener.snapshots.get(0).getCaptures().getReturn(), "arg", "java.lang.String", "5"); } + @Test + public void lineProbeCondition() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot08"; + LogProbe logProbe = + createProbeBuilder(PROBE_ID, CLASS_NAME, "doit", "int (java.lang.String)", "34") + .when( + new ProbeCondition( + DSL.when( + DSL.and( + // this is always true + DSL.and( + // this reference is resolved directly from the snapshot + DSL.eq(DSL.ref("fld"), DSL.value(11)), + // this reference chain needs to use reflection + DSL.eq( + DSL.getMember( + DSL.getMember( + DSL.getMember(DSL.ref("typed"), "fld"), "fld"), + "msg"), + DSL.value("hello"))), + DSL.eq(DSL.ref("arg"), DSL.value("5")))), + "(fld == 11 && typed.fld.fld.msg == \"hello\") && arg == '5'")) + .build(); + DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + for (int i = 0; i < 100; i++) { + int result = Reflect.on(testClass).call("main", String.valueOf(i)).get(); + assertTrue((i == 2 && result == 2) || result == 3); + } + assertEquals(1, listener.snapshots.size()); + assertCaptureArgs( + listener.snapshots.get(0).getCaptures().getLines().get(34), "arg", "java.lang.String", "5"); + } + @Test public void staticFieldCondition() throws IOException, URISyntaxException { final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot19"; @@ -883,7 +970,7 @@ public void staticFieldCondition() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "0").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); Map staticFields = snapshot.getCaptures().getReturn().getStaticFields(); @@ -906,8 +993,8 @@ public void simpleFalseConditionTest() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "0").get(); - Assertions.assertEquals(3, result); - Assertions.assertEquals(0, listener.snapshots.size()); + assertEquals(3, result); + assertEquals(0, listener.snapshots.size()); } @Test @@ -928,7 +1015,7 @@ public void nullCondition() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbes); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(1, listener.snapshots.size()); + assertEquals(1, listener.snapshots.size()); List evaluationErrors = listener.snapshots.get(0).getEvaluationErrors(); Assertions.assertEquals(1, evaluationErrors.size()); Assertions.assertEquals("nullTyped.fld.fld", evaluationErrors.get(0).getExpr()); @@ -990,8 +1077,8 @@ private List doMergedProbeConditions( installProbes(CLASS_NAME, probe1, probe2); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); - Assertions.assertEquals(expectedSnapshots, listener.snapshots.size()); + assertEquals(3, result); + assertEquals(expectedSnapshots, listener.snapshots.size()); return listener.snapshots; } @@ -1109,9 +1196,9 @@ public void fields() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "f", "()")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "f").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); - assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 7); // +2 for correlation ids assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); assertCaptureFields(snapshot.getCaptures().getEntry(), "doubleValue", "double", "3.14"); assertCaptureFields( @@ -1123,7 +1210,7 @@ public void fields() throws IOException, URISyntaxException { Arrays.asList("foo", "bar")); assertCaptureFields( snapshot.getCaptures().getEntry(), "strMap", "java.util.HashMap", Collections.emptyMap()); - assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 7); // +2 for correlation ids assertCaptureFields(snapshot.getCaptures().getReturn(), "intValue", "int", "48"); assertCaptureFields(snapshot.getCaptures().getReturn(), "doubleValue", "double", "3.14"); assertCaptureFields(snapshot.getCaptures().getReturn(), "strValue", "java.lang.String", "done"); @@ -1150,14 +1237,14 @@ public void inheritedFields() throws IOException, URISyntaxException { installProbes(INHERITED_CLASS_NAME, probe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "inherited").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); // Only Declared fields in the current class are captured, not inherited fields - assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getEntry(), 7); // +2 for correlation ids assertCaptureFields( snapshot.getCaptures().getEntry(), "strValue", "java.lang.String", "foobar"); assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); - assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 5); + assertCaptureFieldCount(snapshot.getCaptures().getReturn(), 7); // +2 for correlation ids assertCaptureFields( snapshot.getCaptures().getReturn(), "strValue", "java.lang.String", "barfoo"); assertCaptureFields(snapshot.getCaptures().getEntry(), "intValue", "int", "24"); @@ -1170,7 +1257,7 @@ public void staticFields() throws IOException, URISyntaxException { installSingleProbe(CLASS_NAME, "", "()"); Class testClass = compileAndLoadClass(CLASS_NAME); long result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(4_000_000_001L, result); + assertEquals(4_000_000_001L, result); Snapshot snapshot = assertOneSnapshot(listener); Map staticFields = snapshot.getCaptures().getEntry().getStaticFields(); @@ -1195,14 +1282,16 @@ public void staticInheritedFields() throws IOException, URISyntaxException { installProbes(INHERITED_CLASS_NAME, logProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "inherited").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); Map staticFields = snapshot.getCaptures().getReturn().getStaticFields(); - assertEquals(5, staticFields.size()); + assertEquals(7, staticFields.size()); assertEquals("barfoo", getValue(staticFields.get("strValue"))); assertEquals("48", getValue(staticFields.get("intValue"))); assertEquals("6.28", getValue(staticFields.get("doubleValue"))); + assertEquals("[1, 2, 3, 4]", getValue(staticFields.get("longValues"))); + assertEquals("[foo, bar]", getValue(staticFields.get("strValues"))); } @Test @@ -1215,10 +1304,10 @@ public void staticLambda() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, null, null, "33")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "static", "email@address").get(); - Assertions.assertEquals(8, result); + assertEquals(8, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext context = snapshot.getCaptures().getLines().get(33); - Assertions.assertNotNull(context); + assertNotNull(context); assertCaptureLocals(context, "idx", "int", "5"); } @@ -1232,10 +1321,10 @@ public void capturingLambda() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, null, null, "44")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "capturing", "email@address").get(); - Assertions.assertEquals(8, result); + assertEquals(8, result); Snapshot snapshot = assertOneSnapshot(listener); CapturedContext context = snapshot.getCaptures().getLines().get(44); - Assertions.assertNotNull(context); + assertNotNull(context); assertCaptureLocals(context, "idx", "int", "5"); assertCaptureFields(context, "strValue", "java.lang.String", "email@address"); } @@ -1260,7 +1349,7 @@ public void tracerInstrumentedClass() throws Exception { // it's important there is no null key in this map, as Jackson is not happy about it // it's means here that argument names are not resolved correctly Assertions.assertFalse(arguments.containsKey(null)); - Assertions.assertEquals(4, arguments.size()); + assertEquals(4, arguments.size()); assertTrue(arguments.containsKey("this")); assertTrue(arguments.containsKey("apiKey")); assertTrue(arguments.containsKey("uriInfo")); @@ -1276,16 +1365,14 @@ public void noCodeMethods() throws Exception { installProbes(CLASS_NAME, nativeMethodProbe, abstractMethodProbe); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(1, result); + assertEquals(1, result); ArgumentCaptor probeIdCaptor = ArgumentCaptor.forClass(ProbeId.class); ArgumentCaptor strCaptor = ArgumentCaptor.forClass(String.class); verify(probeStatusSink, times(2)).addError(probeIdCaptor.capture(), strCaptor.capture()); - Assertions.assertEquals(PROBE_ID1.getId(), probeIdCaptor.getAllValues().get(0).getId()); - Assertions.assertEquals( - "Cannot instrument an abstract or native method", strCaptor.getAllValues().get(0)); - Assertions.assertEquals(PROBE_ID2.getId(), probeIdCaptor.getAllValues().get(1).getId()); - Assertions.assertEquals( - "Cannot instrument an abstract or native method", strCaptor.getAllValues().get(1)); + assertEquals(PROBE_ID1.getId(), probeIdCaptor.getAllValues().get(0).getId()); + assertEquals("Cannot instrument an abstract or native method", strCaptor.getAllValues().get(0)); + assertEquals(PROBE_ID2.getId(), probeIdCaptor.getAllValues().get(1).getId()); + assertEquals("Cannot instrument an abstract or native method", strCaptor.getAllValues().get(1)); } @Test @@ -1300,7 +1387,7 @@ public void duplicateClassDefinition() throws Exception { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, abstractMethodProbe); Class testClass = compileAndLoadClass(CLASS_NAME); - Assertions.assertNotNull(testClass); + assertNotNull(testClass); } @Test @@ -1310,7 +1397,7 @@ public void overloadedMethods() throws Exception { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "overload", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(63, result); + assertEquals(63, result); List snapshots = assertSnapshots(listener, 4, PROBE_ID, PROBE_ID, PROBE_ID, PROBE_ID); assertCaptureReturnValue(snapshots.get(0).getCaptures().getReturn(), "int", "42"); assertCaptureArgs(snapshots.get(1).getCaptures().getEntry(), "s", "java.lang.String", "1"); @@ -1326,7 +1413,7 @@ public void noDebugInfoEmptyMethod() throws Exception { Map classFileBuffers = compile(CLASS_NAME, SourceCompiler.DebugInfo.NONE); Class testClass = loadClass(CLASS_NAME, classFileBuffers); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(48, result); + assertEquals(48, result); assertOneSnapshot(listener); } @@ -1343,8 +1430,8 @@ public void instrumentTheWorld() throws Exception { instr.removeTransformer(currentTransformer); } int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); - Assertions.assertEquals(1, listener.snapshots.size()); + assertEquals(2, result); + assertEquals(1, listener.snapshots.size()); ProbeImplementation probeImplementation = listener.snapshots.get(0).getProbe(); assertTrue(probeImplementation.isCaptureSnapshot()); assertEquals("main", probeImplementation.getLocation().getMethod()); @@ -1365,8 +1452,8 @@ public void instrumentTheWorld_excludeClass(String excludeFileName) throws Excep instr.removeTransformer(currentTransformer); } int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); - Assertions.assertEquals(0, listener.snapshots.size()); + assertEquals(2, result); + assertEquals(0, listener.snapshots.size()); } @Test @@ -1376,7 +1463,7 @@ public void objectDynamicType() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "processWithArg", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(50, result); + assertEquals(50, result); Snapshot snapshot = assertOneSnapshot(listener); assertCaptureArgs(snapshot.getCaptures().getEntry(), "obj", "java.lang.Integer", "42"); assertCaptureFields( @@ -1392,7 +1479,7 @@ public void exceptionAsLocalVariable() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, null, null, "14")); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(42, result); + assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); Map expectedFields = new HashMap<>(); expectedFields.put("detailMessage", "For input string: \"a\""); @@ -1415,7 +1502,7 @@ public void evaluateAtEntry() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbes); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); assertOneSnapshot(listener); } @@ -1432,7 +1519,7 @@ public void evaluateAtExit() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbes); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); assertOneSnapshot(listener); } @@ -1449,10 +1536,10 @@ public void evaluateAtExitFalse() throws IOException, URISyntaxException { DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, logProbes); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); - Assertions.assertEquals(0, listener.snapshots.size()); + assertEquals(3, result); + assertEquals(0, listener.snapshots.size()); assertTrue(listener.skipped); - Assertions.assertEquals(DebuggerContext.SkipCause.CONDITION, listener.cause); + assertEquals(DebuggerContext.SkipCause.CONDITION, listener.cause); } @Test @@ -1469,7 +1556,7 @@ public void uncaughtExceptionConditionLocalVar() throws IOException, URISyntaxEx Reflect.on(testClass).call("main", "triggerUncaughtException").get(); Assertions.fail("should not reach this code"); } catch (ReflectException ex) { - Assertions.assertEquals("oops", ex.getCause().getCause().getMessage()); + assertEquals("oops", ex.getCause().getCause().getMessage()); } Snapshot snapshot = assertOneSnapshot(listener); assertCaptureThrowable( @@ -1478,10 +1565,9 @@ public void uncaughtExceptionConditionLocalVar() throws IOException, URISyntaxEx "oops", "CapturedSnapshot05.triggerUncaughtException", 7); - Assertions.assertEquals(2, snapshot.getEvaluationErrors().size()); - Assertions.assertEquals( - "Cannot find symbol: after", snapshot.getEvaluationErrors().get(0).getMessage()); - Assertions.assertEquals( + assertEquals(2, snapshot.getEvaluationErrors().size()); + assertEquals("Cannot find symbol: after", snapshot.getEvaluationErrors().get(0).getMessage()); + assertEquals( "java.lang.IllegalStateException: oops", snapshot.getEvaluationErrors().get(1).getMessage()); } @@ -1494,7 +1580,7 @@ public void enumConstructorArgs() throws IOException, URISyntaxException { installProbes(ENUM_CLASS, createProbe(PROBE_ID, ENUM_CLASS, "", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); assertSnapshots(listener, 3, PROBE_ID); Map arguments = listener.snapshots.get(0).getCaptures().getEntry().getArguments(); @@ -1511,7 +1597,7 @@ public void enumValues() throws IOException, URISyntaxException { installProbes(CLASS_NAME, createProbe(PROBE_ID, CLASS_NAME, "convert", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "2").get(); - Assertions.assertEquals(2, result); + assertEquals(2, result); Snapshot snapshot = assertOneSnapshot(listener); assertCaptureReturnValue( snapshot.getCaptures().getReturn(), @@ -1527,7 +1613,7 @@ public void recursiveCapture() throws IOException, URISyntaxException { installProbes(INNER_CLASS, createProbe(PROBE_ID, INNER_CLASS, "size", null)); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "").get(); - Assertions.assertEquals(1, result); + assertEquals(1, result); } @Test @@ -1541,7 +1627,7 @@ public void recursiveCaptureException() throws IOException, URISyntaxException { Reflect.on(testClass).call("main", "exception").get(); Assertions.fail("should not reach this code"); } catch (ReflectException ex) { - Assertions.assertEquals("not supported", ex.getCause().getCause().getMessage()); + assertEquals("not supported", ex.getCause().getCause().getMessage()); } } @@ -1584,10 +1670,10 @@ private Snapshot doUnknownCount(String CLASS_NAME) throws IOException, URISyntax public void beforeForLoopLineProbe() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot02"; DebuggerTransformerTest.TestSnapshotListener listener = - installSingleProbe(CLASS_NAME, null, null, "46"); + installSingleProbeAtExit(CLASS_NAME, null, null, "46"); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "synchronizedBlock").get(); - Assertions.assertEquals(76, result); + assertEquals(76, result); Snapshot snapshot = assertOneSnapshot(listener); assertCaptureLocals(snapshot.getCaptures().getLines().get(46), "count", "int", "31"); } @@ -1599,16 +1685,18 @@ public void dupLineProbeSameTemplate() throws IOException, URISyntaxException { LogProbe probe1 = createProbeBuilder(PROBE_ID1, CLASS_NAME, null, null, "39") .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .evaluateAt(MethodLocation.EXIT) .build(); LogProbe probe2 = createProbeBuilder(PROBE_ID2, CLASS_NAME, null, null, "39") .template(LOG_TEMPLATE, parseTemplate(LOG_TEMPLATE)) + .evaluateAt(MethodLocation.EXIT) .build(); DebuggerTransformerTest.TestSnapshotListener listener = installProbes(CLASS_NAME, probe1, probe2); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); - Assertions.assertEquals(3, result); + assertEquals(3, result); List snapshots = assertSnapshots(listener, 2, PROBE_ID1, PROBE_ID2); for (Snapshot snapshot : snapshots) { assertEquals("msg1=hello", snapshot.getMessage()); @@ -1632,17 +1720,23 @@ public void keywordRedaction() throws IOException, URISyntaxException { Assertions.assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); assertEquals( - "arg=secret123 secret={Could not evaluate the expression because 'secret' was redacted} password={Could not evaluate the expression because 'this.password' was redacted} fromMap={Could not evaluate the expression because 'strMap[\"password\"]' was redacted}", + "arg=secret123 secret={" + + REDACTED_VALUE + + "} password={" + + REDACTED_VALUE + + "} fromMap={" + + REDACTED_VALUE + + "}", snapshot.getMessage()); CapturedContext.CapturedValue secretLocalVar = snapshot.getCaptures().getReturn().getLocals().get("secret"); CapturedContext.CapturedValue secretValued = VALUE_ADAPTER.fromJson(secretLocalVar.getStrValue()); - assertEquals("redacted", secretValued.getNotCapturedReason()); + assertEquals(REDACTED_IDENT_REASON, secretValued.getNotCapturedReason()); Map thisFields = getFields(snapshot.getCaptures().getReturn().getArguments().get("this")); CapturedContext.CapturedValue passwordField = thisFields.get("password"); - assertEquals("redacted", passwordField.getNotCapturedReason()); + assertEquals(REDACTED_IDENT_REASON, passwordField.getNotCapturedReason()); Map strMap = (Map) thisFields.get("strMap").getValue(); assertNull(strMap.get("password")); } @@ -1742,12 +1836,20 @@ public void typeRedactionSnapshot() throws IOException, URISyntaxException { Assertions.assertEquals(42, result); Snapshot snapshot = assertOneSnapshot(listener); assertEquals( - "arg=secret123 credentials={Could not evaluate the expression because 'creds' was redacted} user={Could not evaluate the expression because 'this.creds' was redacted} code={Could not evaluate the expression because 'creds' was redacted} dave={Could not evaluate the expression because 'credMap[\"dave\"]' was redacted}", + "arg=secret123 credentials={" + + REDACTED_VALUE + + "} user={" + + REDACTED_VALUE + + "} code={" + + REDACTED_VALUE + + "} dave={" + + REDACTED_VALUE + + "}", snapshot.getMessage()); Map thisFields = getFields(snapshot.getCaptures().getReturn().getArguments().get("this")); CapturedContext.CapturedValue credsField = thisFields.get("creds"); - assertEquals("redacted", credsField.getNotCapturedReason()); + assertEquals(REDACTED_TYPE_REASON, credsField.getNotCapturedReason()); Map credMap = (Map) thisFields.get("credMap").getValue(); assertNull(credMap.get("dave")); } @@ -1811,6 +1913,56 @@ public void typeRedactionCondition() throws IOException, URISyntaxException { snapshots.get(2).getEvaluationErrors().get(0).getMessage()); } + @Test + public void ensureCallingSamplingMethodProbe() throws IOException, URISyntaxException { + doSamplingTest(this::methodProbe, 1, 1); + } + + @Test + public void ensureCallingSamplingProbeCondition() throws IOException, URISyntaxException { + doSamplingTest(this::simpleConditionTest, 1, 1); + } + + @Test + public void ensureCallingSamplingProbeConditionError() throws IOException, URISyntaxException { + doSamplingTest(this::nullCondition, 1, 1); + } + + @Test + public void ensureCallingSamplingDupMethodProbeCondition() + throws IOException, URISyntaxException { + doSamplingTest(this::mergedProbesWithAdditionalProbeConditionTest, 2, 2); + } + + @Test + public void ensureCallingSamplingLineProbe() throws IOException, URISyntaxException { + doSamplingTest(this::singleLineProbe, 1, 1); + } + + @Test + public void ensureCallingSamplingLineProbeCondition() throws IOException, URISyntaxException { + doSamplingTest(this::lineProbeCondition, 1, 1); + } + + interface TestMethod { + void run() throws IOException, URISyntaxException; + } + + private void doSamplingTest(TestMethod testRun, int expectedGlobalCount, int expectedProbeCount) + throws IOException, URISyntaxException { + MockSampler probeSampler = new MockSampler(); + MockSampler globalSampler = new MockSampler(); + ProbeRateLimiter.setSamplerSupplier(rate -> rate < 101 ? probeSampler : globalSampler); + ProbeRateLimiter.setGlobalSnapshotRate(1000); + try { + testRun.run(); + } finally { + ProbeRateLimiter.setSamplerSupplier(null); + } + assertEquals(expectedGlobalCount, globalSampler.callCount); + assertEquals(expectedProbeCount, probeSampler.callCount); + } + private DebuggerTransformerTest.TestSnapshotListener setupInstrumentTheWorldTransformer( String excludeFileName) { Config config = mock(Config.class); @@ -1820,13 +1972,13 @@ private DebuggerTransformerTest.TestSnapshotListener setupInstrumentTheWorldTran when(config.getDebuggerExcludeFiles()).thenReturn(excludeFileName); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); when(config.getDebuggerUploadBatchSize()).thenReturn(100); DebuggerTransformerTest.TestSnapshotListener listener = - new DebuggerTransformerTest.TestSnapshotListener(); + new DebuggerTransformerTest.TestSnapshotListener(config, mock(ProbeStatusSink.class)); DebuggerAgentHelper.injectSink(listener); currentTransformer = - DebuggerAgent.setupInstrumentTheWorldTransformer( - config, instr, new DebuggerSink(config), null); + DebuggerAgent.setupInstrumentTheWorldTransformer(config, instr, listener, null); DebuggerContext.initClassFilter(new DenyListHelper(null)); return listener; } @@ -1846,9 +1998,9 @@ private void setCorrelationSingleton(Object instance) { } private Snapshot assertOneSnapshot(DebuggerTransformerTest.TestSnapshotListener listener) { - Assertions.assertEquals(1, listener.snapshots.size()); + assertEquals(1, listener.snapshots.size()); Snapshot snapshot = listener.snapshots.get(0); - Assertions.assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); + assertEquals(PROBE_ID.getId(), snapshot.getProbe().getId()); return snapshot; } @@ -1858,6 +2010,12 @@ private DebuggerTransformerTest.TestSnapshotListener installSingleProbe( return installProbes(typeName, logProbes); } + private DebuggerTransformerTest.TestSnapshotListener installSingleProbeAtExit( + String typeName, String methodName, String signature, String... lines) { + LogProbe logProbes = createProbeAtExit(PROBE_ID, typeName, methodName, signature, lines); + return installProbes(typeName, logProbes); + } + private DebuggerTransformerTest.TestSnapshotListener installProbes( String expectedClassName, Configuration configuration) { Config config = mock(Config.class); @@ -1866,18 +2024,15 @@ private DebuggerTransformerTest.TestSnapshotListener installProbes( when(config.isDebuggerVerifyByteCode()).thenReturn(true); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); Collection logProbes = configuration.getLogProbes(); instrumentationListener = new MockInstrumentationListener(); probeStatusSink = mock(ProbeStatusSink.class); + DebuggerTransformerTest.TestSnapshotListener listener = + new DebuggerTransformerTest.TestSnapshotListener(config, probeStatusSink); currentTransformer = - new DebuggerTransformer( - config, - configuration, - instrumentationListener, - new DebuggerSink(config, probeStatusSink)); + new DebuggerTransformer(config, configuration, instrumentationListener, listener); instr.addTransformer(currentTransformer); - DebuggerTransformerTest.TestSnapshotListener listener = - new DebuggerTransformerTest.TestSnapshotListener(); DebuggerAgentHelper.injectSink(listener); DebuggerContext.init( (id, callingClass) -> resolver(id, callingClass, expectedClassName, logProbes), null); @@ -1897,7 +2052,7 @@ private DebuggerTransformerTest.TestSnapshotListener installProbes( private ProbeImplementation resolver( String id, Class callingClass, String expectedClassName, Collection logProbes) { - Assertions.assertEquals(expectedClassName, callingClass.getName()); + assertEquals(expectedClassName, callingClass.getName()); for (LogProbe probe : logProbes) { if (probe.getId().equals(id)) { return probe; @@ -1919,30 +2074,29 @@ private DebuggerTransformerTest.TestSnapshotListener installProbes( private void assertCaptureArgs( CapturedContext context, String name, String typeName, String value) { CapturedContext.CapturedValue capturedValue = context.getArguments().get(name); - Assertions.assertEquals(typeName, capturedValue.getType()); - Assertions.assertEquals(value, getValue(capturedValue)); + assertEquals(typeName, capturedValue.getType()); + assertEquals(value, getValue(capturedValue)); } private void assertCaptureLocals( CapturedContext context, String name, String typeName, String value) { CapturedContext.CapturedValue localVar = context.getLocals().get(name); - Assertions.assertEquals(typeName, localVar.getType()); - Assertions.assertEquals(value, getValue(localVar)); + assertEquals(typeName, localVar.getType()); + assertEquals(value, getValue(localVar)); } private void assertCaptureLocals( CapturedContext context, String name, String typeName, Map expectedFields) { CapturedContext.CapturedValue localVar = context.getLocals().get(name); - Assertions.assertEquals(typeName, localVar.getType()); + assertEquals(typeName, localVar.getType()); Map fields = getFields(localVar); for (Map.Entry entry : expectedFields.entrySet()) { assertTrue(fields.containsKey(entry.getKey())); CapturedContext.CapturedValue fieldCapturedValue = fields.get(entry.getKey()); if (fieldCapturedValue.getNotCapturedReason() != null) { - Assertions.assertEquals( - entry.getValue(), String.valueOf(fieldCapturedValue.getNotCapturedReason())); + assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getNotCapturedReason())); } else { - Assertions.assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getValue())); + assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getValue())); } } } @@ -1950,18 +2104,18 @@ private void assertCaptureLocals( private void assertCaptureFields( CapturedContext context, String name, String typeName, String value) { CapturedContext.CapturedValue field = context.getFields().get(name); - Assertions.assertEquals(typeName, field.getType()); - Assertions.assertEquals(value, getValue(field)); + assertEquals(typeName, field.getType()); + assertEquals(value, getValue(field)); } private void assertCaptureFields( CapturedContext context, String name, String typeName, Collection collection) { CapturedContext.CapturedValue field = context.getFields().get(name); - Assertions.assertEquals(typeName, field.getType()); + assertEquals(typeName, field.getType()); Iterator iterator = collection.iterator(); for (Object obj : getCollection(field)) { if (iterator.hasNext()) { - Assertions.assertEquals(iterator.next(), obj); + assertEquals(iterator.next(), obj); } else { Assertions.fail("not same number of elements"); } @@ -1971,38 +2125,37 @@ private void assertCaptureFields( private void assertCaptureFields( CapturedContext context, String name, String typeName, Map expectedMap) { CapturedContext.CapturedValue field = context.getFields().get(name); - Assertions.assertEquals(typeName, field.getType()); + assertEquals(typeName, field.getType()); Map map = getMap(field); - Assertions.assertEquals(expectedMap.size(), map.size()); + assertEquals(expectedMap.size(), map.size()); for (Map.Entry entry : map.entrySet()) { assertTrue(expectedMap.containsKey(entry.getKey())); - Assertions.assertEquals(expectedMap.get(entry.getKey()), entry.getValue()); + assertEquals(expectedMap.get(entry.getKey()), entry.getValue()); } } private void assertCaptureFieldCount(CapturedContext context, int expectedFieldCount) { - Assertions.assertEquals(expectedFieldCount, context.getFields().size()); + assertEquals(expectedFieldCount, context.getFields().size()); } private void assertCaptureReturnValue(CapturedContext context, String typeName, String value) { CapturedContext.CapturedValue returnValue = context.getLocals().get("@return"); - Assertions.assertEquals(typeName, returnValue.getType()); - Assertions.assertEquals(value, getValue(returnValue)); + assertEquals(typeName, returnValue.getType()); + assertEquals(value, getValue(returnValue)); } private void assertCaptureReturnValue( CapturedContext context, String typeName, Map expectedFields) { CapturedContext.CapturedValue returnValue = context.getLocals().get("@return"); - Assertions.assertEquals(typeName, returnValue.getType()); + assertEquals(typeName, returnValue.getType()); Map fields = getFields(returnValue); for (Map.Entry entry : expectedFields.entrySet()) { assertTrue(fields.containsKey(entry.getKey())); CapturedContext.CapturedValue fieldCapturedValue = fields.get(entry.getKey()); if (fieldCapturedValue.getNotCapturedReason() != null) { - Assertions.assertEquals( - entry.getValue(), String.valueOf(fieldCapturedValue.getNotCapturedReason())); + assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getNotCapturedReason())); } else { - Assertions.assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getValue())); + assertEquals(entry.getValue(), String.valueOf(fieldCapturedValue.getValue())); } } } @@ -2019,13 +2172,13 @@ private void assertCaptureThrowable( String message, String methodName, int lineNumber) { - Assertions.assertNotNull(throwable); - Assertions.assertEquals(typeName, throwable.getType()); - Assertions.assertEquals(message, throwable.getMessage()); - Assertions.assertNotNull(throwable.getStacktrace()); + assertNotNull(throwable); + assertEquals(typeName, throwable.getType()); + assertEquals(message, throwable.getMessage()); + assertNotNull(throwable.getStacktrace()); Assertions.assertFalse(throwable.getStacktrace().isEmpty()); - Assertions.assertEquals(methodName, throwable.getStacktrace().get(0).getFunction()); - Assertions.assertEquals(lineNumber, throwable.getStacktrace().get(0).getLineNumber()); + assertEquals(methodName, throwable.getStacktrace().get(0).getFunction()); + assertEquals(lineNumber, throwable.getStacktrace().get(0).getLineNumber()); } private static String getValue(CapturedContext.CapturedValue capturedValue) { @@ -2036,6 +2189,12 @@ private static String getValue(CapturedContext.CapturedValue capturedValue) { Assertions.fail("NotCapturedReason: " + valued.getNotCapturedReason()); } Object obj = valued.getValue(); + if (obj != null && obj.getClass().isArray()) { + if (obj.getClass().getComponentType().isPrimitive()) { + return primitiveArrayToString(obj); + } + return Arrays.toString((Object[]) obj); + } return obj != null ? String.valueOf(obj) : null; } catch (IOException e) { e.printStackTrace(); @@ -2043,6 +2202,35 @@ private static String getValue(CapturedContext.CapturedValue capturedValue) { } } + private static String primitiveArrayToString(Object obj) { + Class componentType = obj.getClass().getComponentType(); + if (componentType == long.class) { + return Arrays.toString((long[]) obj); + } + if (componentType == int.class) { + return Arrays.toString((int[]) obj); + } + if (componentType == short.class) { + return Arrays.toString((short[]) obj); + } + if (componentType == char.class) { + return Arrays.toString((char[]) obj); + } + if (componentType == byte.class) { + return Arrays.toString((byte[]) obj); + } + if (componentType == boolean.class) { + return Arrays.toString((boolean[]) obj); + } + if (componentType == float.class) { + return Arrays.toString((float[]) obj); + } + if (componentType == double.class) { + return Arrays.toString((double[]) obj); + } + return null; + } + public static Map getFields( CapturedContext.CapturedValue capturedValue) { try { @@ -2118,6 +2306,13 @@ private static LogProbe createProbe( return createProbeBuilder(id, typeName, methodName, signature, lines).build(); } + private static LogProbe createProbeAtExit( + ProbeId id, String typeName, String methodName, String signature, String... lines) { + return createProbeBuilder(id, typeName, methodName, signature, lines) + .evaluateAt(MethodLocation.EXIT) + .build(); + } + private static LogProbe.Builder createProbeBuilder( ProbeId id, String typeName, String methodName, String signature, String... lines) { return LogProbe.builder() @@ -2135,6 +2330,7 @@ private static LogProbe createSourceFileProbe(ProbeId id, String sourceFile, int .probeId(id) .captureSnapshot(true) .where(null, null, null, line, sourceFile) + .evaluateAt(MethodLocation.EXIT) .build(); } @@ -2147,6 +2343,27 @@ public void instrumentationResult(ProbeDefinition definition, InstrumentationRes } } + static class MockSampler implements Sampler { + + int callCount; + + @Override + public boolean sample() { + callCount++; + return true; + } + + @Override + public boolean keep() { + return false; + } + + @Override + public boolean drop() { + return false; + } + } + static class KotlinHelper { public static Class compileAndLoad( String className, String sourceFileName, List outputFilesToDelete) { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationUpdaterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationUpdaterTest.java index 30749fdf549..5261dfb892f 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationUpdaterTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationUpdaterTest.java @@ -60,6 +60,7 @@ public class ConfigurationUpdaterTest { void setUp() { lenient().when(tracerConfig.getFinalDebuggerSnapshotUrl()).thenReturn("http://localhost"); lenient().when(tracerConfig.getDebuggerUploadBatchSize()).thenReturn(100); + lenient().when(tracerConfig.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost"); debuggerSinkWithMockStatusSink = new DebuggerSink(tracerConfig, probeStatusSink); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentHelper.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentHelper.java index c584a569689..121b6bf6e98 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentHelper.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentHelper.java @@ -1,9 +1,9 @@ package com.datadog.debugger.agent; -import com.datadog.debugger.sink.Sink; +import com.datadog.debugger.sink.DebuggerSink; public class DebuggerAgentHelper { - public static void injectSink(Sink sink) { + public static void injectSink(DebuggerSink sink) { DebuggerAgent.initSink(sink); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerTransformerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerTransformerTest.java index e4be8e3bb89..8097de84ce2 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerTransformerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerTransformerTest.java @@ -14,7 +14,6 @@ import com.datadog.debugger.probe.Where; import com.datadog.debugger.sink.DebuggerSink; import com.datadog.debugger.sink.ProbeStatusSink; -import com.datadog.debugger.sink.Sink; import com.datadog.debugger.sink.Snapshot; import datadog.trace.api.Config; import datadog.trace.api.GlobalTracer; @@ -66,11 +65,15 @@ enum ExceptionKind { HANDLED } - static class TestSnapshotListener implements Sink { + static class TestSnapshotListener extends DebuggerSink { boolean skipped; DebuggerContext.SkipCause cause; List snapshots = new ArrayList<>(); + public TestSnapshotListener(Config config, ProbeStatusSink probeStatusSink) { + super(config, probeStatusSink); + } + @Override public void skipSnapshot(String probeId, DebuggerContext.SkipCause cause) { skipped = true; @@ -304,8 +307,6 @@ public void classBeingRedefinedNull() { @Test public void classGenerationFailed() { Config config = createConfig(); - TestSnapshotListener listener = new TestSnapshotListener(); - DebuggerAgentHelper.injectSink(listener); final String CLASS_NAME = DebuggerAgent.class.getTypeName(); final String METHOD_NAME = "run"; MockProbe mockProbe = MockProbe.builder(PROBE_ID).where(CLASS_NAME, METHOD_NAME).build(); @@ -321,12 +322,11 @@ public void classGenerationFailed() { .build(); AtomicReference lastResult = new AtomicReference<>(null); ProbeStatusSink probeStatusSink = mock(ProbeStatusSink.class); + TestSnapshotListener listener = new TestSnapshotListener(config, probeStatusSink); DebuggerTransformer debuggerTransformer = new DebuggerTransformer( - config, - configuration, - ((definition, result) -> lastResult.set(result)), - new DebuggerSink(config, probeStatusSink)); + config, configuration, ((definition, result) -> lastResult.set(result)), listener); + DebuggerAgentHelper.injectSink(listener); byte[] newClassBuffer = debuggerTransformer.transform( ClassLoader.getSystemClassLoader(), @@ -353,6 +353,7 @@ private Config createConfig() { Config config = mock(Config.class); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); when(config.getDebuggerUploadBatchSize()).thenReturn(100); return config; } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java index 8b5172d6579..1a512da61d3 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogProbesInstrumentationTest.java @@ -11,6 +11,7 @@ import com.datadog.debugger.el.DSL; import com.datadog.debugger.el.ProbeCondition; import com.datadog.debugger.probe.LogProbe; +import com.datadog.debugger.sink.ProbeStatusSink; import com.datadog.debugger.sink.Snapshot; import datadog.trace.api.Config; import datadog.trace.bootstrap.debugger.DebuggerContext; @@ -37,6 +38,8 @@ public class LogProbesInstrumentationTest { private static final ProbeId LOG_ID1 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f8", 0); private static final ProbeId LOG_ID2 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f9", 0); private static final String SERVICE_NAME = "service-name"; + private static final String STR_8K = + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; private Instrumentation instr = ByteBuddyAgent.install(); private ClassFileTransformer currentTransformer; @@ -62,6 +65,19 @@ public void methodPlainLog() throws IOException, URISyntaxException { assertEquals("this is log line", snapshot.getMessage()); } + @Test + public void methodLargePlainLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe(STR_8K + "123", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "1").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + @Test public void methodTemplateArgLog() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; @@ -76,6 +92,49 @@ public void methodTemplateArgLog() throws IOException, URISyntaxException { assertEquals("this is log line with arg=1", snapshot.getMessage()); } + @Test + public void methodTemplateArgLogLarge() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe(STR_8K + "{arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "1").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + + @Test + public void methodTemplateLargeArgLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe( + "this is log line with arg={arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", STR_8K).get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + final String expectedStr = "this is log line with arg="; + assertEquals( + expectedStr + STR_8K.substring(0, STR_8K.length() - expectedStr.length()) + "...", + snapshot.getMessage()); + } + + @Test + public void methodTemplateTooLargeArgLog() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot01"; + DebuggerTransformerTest.TestSnapshotListener listener = + installSingleProbe("{arg}", CLASS_NAME, "main", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", STR_8K + "123").get(); + Assertions.assertEquals(3, result); + Snapshot snapshot = assertOneSnapshot(listener); + assertCapturesNull(snapshot); + assertEquals(STR_8K + "...", snapshot.getMessage()); + } + @Test public void methodTemplateArgLogEvaluateAtExit() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot01"; @@ -488,11 +547,12 @@ private DebuggerTransformerTest.TestSnapshotListener installProbes( when(config.isDebuggerClassFileDumpEnabled()).thenReturn(true); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); when(config.getDebuggerUploadBatchSize()).thenReturn(100); currentTransformer = new DebuggerTransformer(config, configuration); instr.addTransformer(currentTransformer); DebuggerTransformerTest.TestSnapshotListener listener = - new DebuggerTransformerTest.TestSnapshotListener(); + new DebuggerTransformerTest.TestSnapshotListener(config, mock(ProbeStatusSink.class)); DebuggerAgentHelper.injectSink(listener); DebuggerContext.init( (id, callingClass) -> diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java index 8a29495bfe1..0fbb89f2a2e 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java @@ -366,6 +366,63 @@ public void methodFieldRefValueDistributionDoubleMetric() throws IOException, UR Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); } + @Test + public void methodSyntheticReturnGaugeMetric() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "f", "()") + .valueScript(new ValueScript(DSL.ref("@return"), "@return")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "f").get(); + Assertions.assertEquals(42, result); + Assertions.assertTrue(listener.gauges.containsKey(METRIC_NAME)); + Assertions.assertEquals(42, listener.gauges.get(METRIC_NAME).longValue()); + Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); + } + + @Test + public void methodSyntheticReturnInvalidType() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + final String INHERITED_CLASS_NAME = CLASS_NAME + "$Inherited"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(INHERITED_CLASS_NAME, "", "()") + .valueScript(new ValueScript(DSL.ref("@return"), "@return")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "").get(); + Assertions.assertEquals(42, result); + Assertions.assertFalse(listener.gauges.containsKey(METRIC_NAME)); + verify(probeStatusSink).addError(eq(METRIC_ID), eq("Cannot resolve symbol @return")); + } + + @Test + public void methodSyntheticDurationGaugeMetric() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot06"; + String METRIC_NAME = "syn_gauge"; + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "f", "()") + .valueScript(new ValueScript(DSL.ref("@duration"), "@duration")) + .evaluateAt(MethodLocation.EXIT) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "f").get(); + Assertions.assertEquals(42, result); + Assertions.assertTrue(listener.doubleGauges.containsKey(METRIC_NAME)); + Assertions.assertTrue(listener.doubleGauges.get(METRIC_NAME).doubleValue() > 0); + Assertions.assertArrayEquals(new String[] {METRIC_PROBEID_TAG}, listener.lastTags); + } + @Test public void lineArgumentRefValueCountMetric() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot03"; @@ -1179,6 +1236,7 @@ private MetricForwarderListener installMetricProbes(Configuration configuration) when(config.isDebuggerClassFileDumpEnabled()).thenReturn(true); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); probeStatusSink = mock(ProbeStatusSink.class); currentTransformer = new DebuggerTransformer( diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ProbeInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ProbeInstrumentationTest.java index fa6ff412955..2f4b879924e 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ProbeInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ProbeInstrumentationTest.java @@ -1,8 +1,9 @@ package com.datadog.debugger.agent; +import com.datadog.debugger.sink.DebuggerSink; import com.datadog.debugger.sink.ProbeStatusSink; -import com.datadog.debugger.sink.Sink; import com.datadog.debugger.sink.Snapshot; +import datadog.trace.api.Config; import datadog.trace.bootstrap.debugger.DebuggerContext; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -26,10 +27,14 @@ public void after() { } } - protected static class MockSink implements Sink { + protected static class MockSink extends DebuggerSink { private final List snapshots = new ArrayList<>(); + public MockSink(Config config, ProbeStatusSink probeStatusSink) { + super(config, probeStatusSink); + } + @Override public void addSnapshot(Snapshot snapshot) { snapshots.add(snapshot); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java index 2eb09c842c0..2049abe0e8b 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanDecorationProbeInstrumentationTest.java @@ -1,6 +1,8 @@ package com.datadog.debugger.agent; import static com.datadog.debugger.el.DSL.eq; +import static com.datadog.debugger.el.DSL.getMember; +import static com.datadog.debugger.el.DSL.gt; import static com.datadog.debugger.el.DSL.ref; import static com.datadog.debugger.el.DSL.value; import static com.datadog.debugger.probe.SpanDecorationProbe.TargetSpan.ACTIVE; @@ -11,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static utils.InstrumentationTestHelper.compileAndLoadClass; @@ -180,11 +183,61 @@ public void methodActiveSpanInvalidCondition() throws IOException, URISyntaxExce assertEquals("Cannot find symbol: noarg", snapshot.getEvaluationErrors().get(0).getMessage()); } + @Test + public void methodActiveSpanSynthReturn() throws IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; + SpanDecorationProbe.Decoration decoration = + createDecoration(gt(ref("@return"), value(0)), "@return > '0", "tag1", "{@return}"); + installSingleSpanDecoration( + CLASS_NAME, ACTIVE, decoration, "process", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "5").get(); + assertEquals(84, result); + MutableSpan span = traceInterceptor.getFirstSpan(); + assertEquals("84", span.getTags().get("tag1")); + } + + @Test + public void methodActiveSpanSynthDuration() throws IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; + SpanDecorationProbe.Decoration decoration = + createDecoration(gt(ref("@duration"), value(0)), "@return > 0", "tag1", "{@duration}"); + installSingleSpanDecoration( + CLASS_NAME, ACTIVE, decoration, "process", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + int result = Reflect.on(testClass).call("main", "5").get(); + assertEquals(84, result); + MutableSpan span = traceInterceptor.getFirstSpan(); + assertTrue(Double.parseDouble((String) span.getTags().get("tag1")) > 0); + } + + @Test + public void methodActiveSpanSynthException() throws IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; + SpanDecorationProbe.Decoration decoration = + createDecoration( + eq(getMember(ref("@exception"), "detailMessage"), value("oops")), + "@exception.detailMessage == 'oops'", + "tag1", + "{@exception.detailMessage}"); + installSingleSpanDecoration( + CLASS_NAME, ACTIVE, decoration, "processWithException", "int (java.lang.String)"); + Class testClass = compileAndLoadClass(CLASS_NAME); + try { + Reflect.on(testClass).call("main", "exception").get(); + Assertions.fail("should not reach this code"); + } catch (RuntimeException ex) { + assertEquals("oops", ex.getCause().getCause().getMessage()); + } + MutableSpan span = traceInterceptor.getFirstSpan(); + assertEquals("oops", span.getTags().get("tag1")); + } + @Test public void lineActiveSpanSimpleTag() throws IOException, URISyntaxException { final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; SpanDecorationProbe.Decoration decoration = createDecoration("tag1", "{arg}"); - installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 38); + installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 41); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "1").get(); assertEquals(84, result); @@ -216,7 +269,7 @@ public void lineActiveSpanCondition() throws IOException, URISyntaxException { final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; SpanDecorationProbe.Decoration decoration = createDecoration(eq(ref("arg"), value("5")), "arg == '5'", "tag1", "{arg}"); - installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 38); + installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 41); Class testClass = compileAndLoadClass(CLASS_NAME); for (int i = 0; i < 10; i++) { int result = Reflect.on(testClass).call("main", String.valueOf(i)).get(); @@ -232,7 +285,7 @@ public void lineActiveSpanInvalidCondition() throws IOException, URISyntaxExcept final String CLASS_NAME = "com.datadog.debugger.CapturedSnapshot20"; SpanDecorationProbe.Decoration decoration = createDecoration(eq(ref("noarg"), value("5")), "arg == '5'", "tag1", "{arg}"); - installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 38); + installSingleSpanDecoration(CLASS_NAME, ACTIVE, decoration, "CapturedSnapshot20.java", 41); Class testClass = compileAndLoadClass(CLASS_NAME); int result = Reflect.on(testClass).call("main", "5").get(); assertEquals(84, result); @@ -584,12 +637,13 @@ private void installSpanDecorationProbes(String expectedClassName, Configuration when(config.isDebuggerClassFileDumpEnabled()).thenReturn(true); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); probeStatusSink = mock(ProbeStatusSink.class); currentTransformer = new DebuggerTransformer( config, configuration, null, new DebuggerSink(config, probeStatusSink)); instr.addTransformer(currentTransformer); - mockSink = new MockSink(); + mockSink = new MockSink(config, probeStatusSink); DebuggerAgentHelper.injectSink(mockSink); DebuggerContext.init( (id, callingClass) -> resolver(id, callingClass, expectedClassName, configuration), null); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanProbeInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanProbeInstrumentationTest.java index 4823ff4dec1..08c0de0d287 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanProbeInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SpanProbeInstrumentationTest.java @@ -145,12 +145,13 @@ private MockTracer installSpanProbes(Configuration configuration) { when(config.isDebuggerVerifyByteCode()).thenReturn(true); when(config.getFinalDebuggerSnapshotUrl()) .thenReturn("http://localhost:8126/debugger/v1/input"); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); probeStatusSink = mock(ProbeStatusSink.class); currentTransformer = new DebuggerTransformer( config, configuration, null, new DebuggerSink(config, probeStatusSink)); instr.addTransformer(currentTransformer); - mockSink = new MockSink(); + mockSink = new MockSink(config, probeStatusSink); DebuggerAgentHelper.injectSink(mockSink); DebuggerContext.init(null, null); DebuggerContext.initClassFilter(new DenyListHelper(null)); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java similarity index 85% rename from dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java rename to dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java index 0089376522b..83df1db244a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/LogMessageTemplateBuilderTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/StringTemplateBuilderTest.java @@ -11,6 +11,7 @@ import com.datadog.debugger.probe.LogProbe; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.EvaluationError; +import datadog.trace.bootstrap.debugger.Limits; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; @@ -23,12 +24,13 @@ import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; -class LogMessageTemplateBuilderTest { +class StringTemplateBuilderTest { + private static final Limits LIMITS = new Limits(1, 3, 255, 5); @Test public void emptyProbe() { LogProbe probe = LogProbe.builder().build(); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertNull(message); } @@ -36,7 +38,7 @@ public void emptyProbe() { @Test public void emptyTemplate() { LogProbe probe = createLogProbe(""); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("", message); } @@ -44,7 +46,7 @@ public void emptyTemplate() { @Test public void onlyStringTemplate() { LogProbe probe = createLogProbe("this is a simple string"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("this is a simple string", message); } @@ -52,7 +54,7 @@ public void onlyStringTemplate() { @Test public void undefinedArgTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); String message = summaryBuilder.evaluate(new CapturedContext(), new LogProbe.LogStatus(probe)); assertEquals("{Cannot find symbol: arg}", message); } @@ -60,7 +62,7 @@ public void undefinedArgTemplate() { @Test public void argTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -78,7 +80,7 @@ public void booleanArgTemplate() { new ValueScript( DSL.bool(DSL.contains(DSL.ref("arg"), new StringValue("o"))), "{arg}"))); LogProbe probe = LogProbe.builder().template("{contains(arg, 'o')}", segments).build(); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -91,14 +93,14 @@ public void booleanArgTemplate() { @Test public void argMultipleInFlightTemplate() { LogProbe probe = createLogProbe("{arg}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { CapturedContext.CapturedValue.of("arg", String.class.getTypeName(), "foo") }); String message = summaryBuilder.evaluate(capturedContext, new LogProbe.LogStatus(probe)); - LogMessageTemplateBuilder summaryBuilder2 = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder2 = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext2 = new CapturedContext(); capturedContext2.addArguments( new CapturedContext.CapturedValue[] { @@ -112,7 +114,7 @@ public void argMultipleInFlightTemplate() { @Test public void argNullTemplate() { LogProbe probe = createLogProbe("{nullObject}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -125,7 +127,7 @@ public void argNullTemplate() { @Test public void argArrayTemplate() { LogProbe probe = createLogProbe("{primArray} {strArray}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -145,7 +147,7 @@ public void argArrayTemplate() { @Test public void argCollectionTemplate() { LogProbe probe = createLogProbe("{strList} {strSet}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -171,7 +173,7 @@ public void argCollectionTemplate() { @Test public void argMapTemplate() { LogProbe probe = createLogProbe("{strMap}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); Map map = new LinkedHashMap<>(); for (int i = 0; i < 10; i++) { @@ -199,7 +201,7 @@ static class Level1 { @Test public void argComplexObjectTemplate() { LogProbe probe = createLogProbe("{obj}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -212,7 +214,7 @@ public void argComplexObjectTemplate() { @Test public void argComplexObjectArrayTemplate() { LogProbe probe = createLogProbe("{array}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { @@ -228,7 +230,7 @@ public void argComplexObjectArrayTemplate() { @DisabledIf("datadog.trace.api.Platform#isJ9") public void argInaccessibleFieldTemplate() { LogProbe probe = createLogProbe("{obj}"); - LogMessageTemplateBuilder summaryBuilder = new LogMessageTemplateBuilder(probe.getSegments()); + StringTemplateBuilder summaryBuilder = new StringTemplateBuilder(probe.getSegments(), LIMITS); CapturedContext capturedContext = new CapturedContext(); capturedContext.addArguments( new CapturedContext.CapturedValue[] { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/instrumentation/TypesTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/instrumentation/TypesTest.java index 0469c8137be..ff5338af532 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/instrumentation/TypesTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/instrumentation/TypesTest.java @@ -71,6 +71,14 @@ void testDescriptorFromSignatureNoArgsVoid() { assertEquals(expectedDesc, desc); } + @Test + void testDescriptorFromSignatureInnerClassArgs() { + String expectedDesc = "(Ljava/util/Map$Entry;Ljava/util/Map$Entry;)V"; + String signature = "void(java.util.Map$Entry, java.util.Map$Entry)"; + String desc = Types.descriptorFromSignature(signature); + assertEquals(expectedDesc, desc); + } + @Test void testDescriptorFromSignatureInvalid() { assertThrows(IllegalArgumentException.class, () -> Types.descriptorFromSignature("()")); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/WhereTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/WhereTest.java index 8c88b6b9f57..9d9fdd98e4a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/WhereTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/WhereTest.java @@ -1,5 +1,7 @@ package com.datadog.debugger.probe; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.datadog.debugger.util.MoshiHelper; import com.squareup.moshi.JsonAdapter; import java.io.IOException; @@ -12,7 +14,7 @@ public void simpleLineRange() { Where where = new Where( "java.lang.Object", "toString()", "java.lang.String ()", new String[] {"5-7"}, null); - Assertions.assertTrue(where.isSignatureMatching("java.lang.String ()")); + assertTrue(where.isSignatureMatching("java.lang.String ()")); String[] lines = where.getLines(); Assertions.assertNotNull(lines); Assertions.assertEquals(1, lines.length); @@ -28,7 +30,7 @@ public void multiLines() { "java.lang.String ()", new String[] {"12-25", "42-45"}, null); - Assertions.assertTrue(where.isSignatureMatching("java.lang.String ()")); + assertTrue(where.isSignatureMatching("java.lang.String ()")); String[] lines = where.getLines(); Assertions.assertNotNull(lines); Assertions.assertEquals(2, lines.length); @@ -41,7 +43,7 @@ public void singleLine() { Where where = new Where( "java.lang.Object", "toString()", "java.lang.String ()", new String[] {"12"}, null); - Assertions.assertTrue(where.isSignatureMatching("java.lang.String ()")); + assertTrue(where.isSignatureMatching("java.lang.String ()")); String[] lines = where.getLines(); Assertions.assertNotNull(lines); Assertions.assertEquals(1, lines.length); @@ -57,7 +59,7 @@ public void noLines() { "java.lang.String ()", (Where.SourceLine[]) null, null); - Assertions.assertTrue(where.isSignatureMatching("java.lang.String ()")); + assertTrue(where.isSignatureMatching("java.lang.String ()")); String[] lines = where.getLines(); Assertions.assertNull(lines); } @@ -78,18 +80,30 @@ public void linesRoundTrip() throws IOException { @Test public void methodMatching() { Where where = new Where("String", "substring", "(int,int)", new String[0], null); - Assertions.assertTrue(where.isMethodMatching("substring", "(II)Ljava/lang/String;")); + assertTrue(where.isMethodMatching("substring", "(II)Ljava/lang/String;")); where = new Where("String", "replaceAll", "(String,String)", new String[0], null); - Assertions.assertTrue( + assertTrue( where.isMethodMatching( "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")); where = new Where("HashMap", "", "(Map)", new String[0], null); - Assertions.assertTrue(where.isMethodMatching("", "(Ljava/util/Map;)V")); + assertTrue(where.isMethodMatching("", "(Ljava/util/Map;)V")); where = new Where("ArrayList", "removeIf", "(Predicate)", new String[0], null); - Assertions.assertTrue(where.isMethodMatching("removeIf", "(Ljava/util/function/Predicate;)Z")); + assertTrue(where.isMethodMatching("removeIf", "(Ljava/util/function/Predicate;)Z")); where = new Where("String", "concat", "", new String[0], null); - Assertions.assertTrue(where.isMethodMatching("concat", "String (String)")); + assertTrue(where.isMethodMatching("concat", "String (String)")); where = new Where("String", "concat", " \t", new String[0], null); - Assertions.assertTrue(where.isMethodMatching("concat", "String (String)")); + assertTrue(where.isMethodMatching("concat", "String (String)")); + where = + new Where( + "Inner", + "innerMethod", + "(com.datadog.debugger.probe.Outer$Inner)", + new String[0], + null); + assertTrue( + where.isMethodMatching("innerMethod", "(Lcom/datadog/debugger/probe/Outer$Inner;)V")); + where = new Where("Inner", "innerMethod", "(Outer$Inner)", new String[0], null); + assertTrue( + where.isMethodMatching("innerMethod", "(Lcom/datadog/debugger/probe/Outer$Inner;)V")); } } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/DebuggerSinkTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/DebuggerSinkTest.java index a0df890b0c9..61a40fa136f 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/DebuggerSinkTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/DebuggerSinkTest.java @@ -71,6 +71,7 @@ void setUp() { when(config.getEnv()).thenReturn("test"); when(config.getVersion()).thenReturn("foo"); when(config.getDebuggerUploadBatchSize()).thenReturn(1); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); EXPECTED_SNAPSHOT_TAGS = "^env:test,version:foo,debugger_version:\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?~[0-9a-f]+,agent_version:null,host_name:" diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/SymbolSinkTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/SymbolSinkTest.java new file mode 100644 index 00000000000..d158780d92e --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/SymbolSinkTest.java @@ -0,0 +1,53 @@ +package com.datadog.debugger.sink; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.datadog.debugger.symbol.Scope; +import com.datadog.debugger.symbol.ScopeType; +import com.datadog.debugger.uploader.BatchUploader; +import datadog.trace.api.Config; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class SymbolSinkTest { + + @Test + public void testFlush() { + SymbolUploaderMock symbolUploaderMock = new SymbolUploaderMock(); + Config config = mock(Config.class); + when(config.getServiceName()).thenReturn("service1"); + SymbolSink symbolSink = new SymbolSink(config, symbolUploaderMock); + symbolSink.addScope(Scope.builder(ScopeType.JAR, null, 0, 0).build()); + symbolSink.flush(); + assertEquals(2, symbolUploaderMock.multiPartContents.size()); + BatchUploader.MultiPartContent eventContent = symbolUploaderMock.multiPartContents.get(0); + assertEquals("event", eventContent.getPartName()); + assertEquals("event.json", eventContent.getFileName()); + String strEventContent = new String(eventContent.getContent()); + assertTrue(strEventContent.contains("\"ddsource\": \"dd_debugger\"")); + assertTrue(strEventContent.contains("\"service\": \"service1\"")); + BatchUploader.MultiPartContent symbolContent = symbolUploaderMock.multiPartContents.get(1); + assertEquals("file", symbolContent.getPartName()); + assertEquals("file.json", symbolContent.getFileName()); + assertEquals( + "{\"language\":\"JAVA\",\"scopes\":[{\"end_line\":0,\"scope_type\":\"JAR\",\"start_line\":0}],\"service\":\"service1\"}", + new String(symbolContent.getContent())); + } + + static class SymbolUploaderMock extends BatchUploader { + final List multiPartContents = new ArrayList<>(); + + public SymbolUploaderMock() { + super(Config.get(), "http://localhost"); + } + + @Override + public void uploadAsMultipart(String tags, MultiPartContent... parts) { + multiPartContents.addAll(Arrays.asList(parts)); + } + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/JarScannerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/JarScannerTest.java new file mode 100644 index 00000000000..2e31dbfca76 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/JarScannerTest.java @@ -0,0 +1,34 @@ +package com.datadog.debugger.symbol; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import org.junit.jupiter.api.Test; + +class JarScannerTest { + @Test + public void extractJarPathFromJar() + throws ClassNotFoundException, URISyntaxException, MalformedURLException { + final String CLASS_NAME = "com.datadog.debugger.symbol.SymbolExtraction01"; + URL jarFileUrl = getClass().getResource("/debugger-symbol.jar"); + URL jarUrl = new URL("jar:file:" + jarFileUrl.getFile() + "!/"); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {jarUrl}, null); + Class testClass = urlClassLoader.loadClass(CLASS_NAME); + assertEquals(jarFileUrl.getFile(), JarScanner.extractJarPath(testClass).toString()); + assertEquals( + jarFileUrl.getFile(), + JarScanner.extractJarPath(testClass.getProtectionDomain()).toString()); + } + + @Test + public void extractJarPathFromFile() throws ClassNotFoundException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.symbol.SymbolExtraction01"; + URL jarFileUrl = getClass().getResource("/debugger-symbol.jar"); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {jarFileUrl}, null); + Class testClass = urlClassLoader.loadClass(CLASS_NAME); + assertEquals(jarFileUrl.getFile(), JarScanner.extractJarPath(testClass).toString()); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java new file mode 100644 index 00000000000..a298a3774c7 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java @@ -0,0 +1,160 @@ +package com.datadog.debugger.symbol; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.datadog.debugger.agent.AllowListHelper; +import com.datadog.debugger.sink.SymbolSink; +import datadog.remoteconfig.state.ParsedConfigKey; +import datadog.trace.api.Config; +import datadog.trace.util.Strings; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.instrument.Instrumentation; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class SymDBEnablementTest { + private static final String CONFIG_KEY = + "datadog/2/LIVE_DEBUGGING_SYMBOL_DB/symDb/d5df9e11566aee56b2c9f8a557680ac53fd3a021141a71275345a90e77f2f2ed"; + private static final byte[] UPlOAD_SYMBOL_TRUE = "{\"upload_symbols\": true}".getBytes(); + private static final byte[] UPlOAD_SYMBOL_FALSE = "{\"upload_symbols\": false}".getBytes(); + private Instrumentation instr = mock(Instrumentation.class); + private Config config; + private SymbolSink symbolSink; + + @BeforeEach + public void before() { + when(instr.getAllLoadedClasses()).thenReturn(new Class[0]); + when(instr.isModifiableClass(any())).thenReturn(true); + config = mock(Config.class); + when(config.getDebuggerSymbolIncludes()).thenReturn("com.datadog.debugger"); + when(config.isDebuggerSymbolEnabled()).thenReturn(true); + symbolSink = mock(SymbolSink.class); + } + + @Test + public void enableDisableSymDBThroughRC() throws Exception { + SymDBEnablement symDBEnablement = + new SymDBEnablement(instr, config, new SymbolAggregator(symbolSink, 1)); + symDBEnablement.accept(ParsedConfigKey.parse(CONFIG_KEY), UPlOAD_SYMBOL_TRUE, null); + waitForUpload(symDBEnablement); + verify(instr).addTransformer(any(SymbolExtractionTransformer.class)); + symDBEnablement.accept(ParsedConfigKey.parse(CONFIG_KEY), UPlOAD_SYMBOL_FALSE, null); + verify(instr).removeTransformer(any(SymbolExtractionTransformer.class)); + } + + @Test + public void removeSymDBConfig() throws Exception { + SymDBEnablement symDBEnablement = + new SymDBEnablement(instr, config, new SymbolAggregator(symbolSink, 1)); + symDBEnablement.accept(ParsedConfigKey.parse(CONFIG_KEY), UPlOAD_SYMBOL_TRUE, null); + waitForUpload(symDBEnablement); + symDBEnablement.remove(ParsedConfigKey.parse(CONFIG_KEY), null); + verify(instr).removeTransformer(any(SymbolExtractionTransformer.class)); + } + + @Test + public void noIncludesFilterOutDatadogClass() { + when(config.getDebuggerSymbolIncludes()).thenReturn("com.datadog.debugger."); + SymDBEnablement symDBEnablement = + new SymDBEnablement(instr, config, new SymbolAggregator(symbolSink, 1)); + symDBEnablement.startSymbolExtraction(); + ArgumentCaptor captor = + ArgumentCaptor.forClass(SymbolExtractionTransformer.class); + verify(instr).addTransformer(captor.capture()); + SymbolExtractionTransformer transformer = captor.getValue(); + AllowListHelper allowListHelper = transformer.getAllowListHelper(); + assertTrue(allowListHelper.isAllowed("com.datadog.debugger.test.TestClass")); + assertFalse(allowListHelper.isAllowed("org.springframework.test.TestClass")); + assertFalse(allowListHelper.isAllowed("com.datadog.test.TestClass")); + } + + @Test + public void parseLoadedClass() throws ClassNotFoundException, IOException, URISyntaxException { + final String CLASS_NAME = "com.datadog.debugger.symbol.SymbolExtraction01"; + URL jarFileUrl = getClass().getResource("/debugger-symbol.jar"); + URL jarUrl = new URL("jar:file:" + jarFileUrl.getFile() + "!/"); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {jarUrl}, null); + Class testClass = urlClassLoader.loadClass(CLASS_NAME); + when(instr.getAllLoadedClasses()).thenReturn(new Class[] {testClass}); + when(config.getDebuggerSymbolIncludes()) + .thenReturn("com.datadog.debugger.,org.springframework.samples."); + SymbolAggregator symbolAggregator = mock(SymbolAggregator.class); + SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator); + symDBEnablement.startSymbolExtraction(); + verify(instr).addTransformer(any(SymbolExtractionTransformer.class)); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(symbolAggregator, times(2)) + .parseClass(captor.capture(), any(), eq(jarFileUrl.getFile())); + assertEquals( + "com/datadog/debugger/symbol/SymbolExtraction01.class", captor.getAllValues().get(0)); + assertEquals( + "BOOT-INF/classes/org/springframework/samples/petclinic/vet/VetController.class", + captor.getAllValues().get(1)); + } + + @Test + public void noDuplicateSymbolExtraction() { + final String CLASS_NAME_PATH = "com/datadog/debugger/symbol/SymbolExtraction01"; + SymbolSink mockSymbolSink = mock(SymbolSink.class); + SymbolAggregator symbolAggregator = new SymbolAggregator(mockSymbolSink, 1); + SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator); + doAnswer( + invocation -> { + SymbolExtractionTransformer transformer = invocation.getArgument(0); + JarFile jarFile = + new JarFile(getClass().getResource("/debugger-symbol.jar").getFile()); + JarEntry jarEntry = jarFile.getJarEntry(CLASS_NAME_PATH + ".class"); + InputStream inputStream = jarFile.getInputStream(jarEntry); + byte[] buffer = new byte[4096]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + int readBytes; + baos.reset(); + while ((readBytes = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, readBytes); + } + transformer.transform( + getClass().getClassLoader(), CLASS_NAME_PATH, null, null, baos.toByteArray()); + return null; + }) + .when(instr) + .addTransformer(any(SymbolExtractionTransformer.class), eq(true)); + when(instr.getAllLoadedClasses()) + .thenAnswer( + invocation -> { + URL jarFileUrl = getClass().getResource("/debugger-symbol.jar"); + URL jarUrl = new URL("jar:file:" + jarFileUrl.getFile() + "!/"); + URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {jarUrl}, null); + Class testClass = urlClassLoader.loadClass(Strings.getClassName(CLASS_NAME_PATH)); + return new Class[] {testClass}; + }); + symDBEnablement.startSymbolExtraction(); + verify(mockSymbolSink, times(1)).addScope(any()); + } + + private void waitForUpload(SymDBEnablement symDBEnablement) throws InterruptedException { + int count = 0; + while (symDBEnablement.getLastUploadTimestamp() == 0) { + Thread.sleep(1); + if (count++ > 30 * 1000) { + throw new RuntimeException("Timeout waiting for symDBEnablement.getLastUploadTimestamp()"); + } + } + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java new file mode 100644 index 00000000000..23eb98dccb7 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java @@ -0,0 +1,914 @@ +package com.datadog.debugger.symbol; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; +import static utils.InstrumentationTestHelper.compileAndLoadClass; + +import com.datadog.debugger.agent.AllowListHelper; +import com.datadog.debugger.agent.Configuration; +import com.datadog.debugger.sink.SymbolSink; +import datadog.trace.api.Config; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import net.bytebuddy.agent.ByteBuddyAgent; +import org.joor.Reflect; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; +import org.mockito.Mockito; + +class SymbolExtractionTransformerTest { + private static final String SYMBOL_PACKAGE = "com.datadog.debugger.symbol."; + private static final String SYMBOL_PACKAGE_DIR = SYMBOL_PACKAGE.replace('.', '/'); + + private Instrumentation instr = ByteBuddyAgent.install(); + private Config config; + + @BeforeEach + public void setUp() { + config = Mockito.mock(Config.class); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); + } + + @Test + public void noIncludesFilterOutDatadogClass() throws IOException, URISyntaxException { + config = Mockito.mock(Config.class); + when(config.getFinalDebuggerSymDBUrl()).thenReturn("http://localhost:8126/symdb/v1/input"); + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction01"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = + new SymbolExtractionTransformer( + new AllowListHelper(null), new SymbolAggregator(symbolSinkMock, 1)); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + assertFalse( + symbolSinkMock.jarScopes.stream() + .flatMap(scope -> scope.getScopes().stream()) + .anyMatch(scope -> scope.getName().equals(CLASS_NAME))); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction01() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction01"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction01.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 2, 20, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 2, 2, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 4, 20, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 4); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 4, 20, SOURCE_FILE, 1, 2); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 4); + assertSymbol( + mainMethodLocalScope.getSymbols().get(1), + SymbolType.LOCAL, + "var3", + Integer.TYPE.getTypeName(), + 19); + Scope ifLine5Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(ifLine5Scope, ScopeType.LOCAL, null, 6, 17, SOURCE_FILE, 1, 1); + assertSymbol( + ifLine5Scope.getSymbols().get(0), SymbolType.LOCAL, "var2", Integer.TYPE.getTypeName(), 6); + Scope forLine7Scope = ifLine5Scope.getScopes().get(0); + assertScope(forLine7Scope, ScopeType.LOCAL, null, 7, 15, SOURCE_FILE, 1, 1); + assertSymbol( + forLine7Scope.getSymbols().get(0), SymbolType.LOCAL, "i", Integer.TYPE.getTypeName(), 7); + Scope forBodyLine7Scope = forLine7Scope.getScopes().get(0); + assertScope(forBodyLine7Scope, ScopeType.LOCAL, null, 8, 15, SOURCE_FILE, 1, 3); + assertSymbol( + forBodyLine7Scope.getSymbols().get(0), + SymbolType.LOCAL, + "foo", + Integer.TYPE.getTypeName(), + 8); + assertSymbol( + forBodyLine7Scope.getSymbols().get(1), + SymbolType.LOCAL, + "bar", + Integer.TYPE.getTypeName(), + 9); + assertSymbol( + forBodyLine7Scope.getSymbols().get(2), + SymbolType.LOCAL, + "j", + Integer.TYPE.getTypeName(), + 11); + Scope whileLine12 = forBodyLine7Scope.getScopes().get(0); + assertScope(whileLine12, ScopeType.LOCAL, null, 13, 14, SOURCE_FILE, 0, 1); + assertSymbol( + whileLine12.getSymbols().get(0), SymbolType.LOCAL, "var4", Integer.TYPE.getTypeName(), 13); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction02() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction02"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction02.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 6, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 6, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 6, SOURCE_FILE, 0, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + String.class.getTypeName(), + 5); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction03() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction03"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction03.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 4, 28, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 4, 4, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 6, 28, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 6); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 6, 28, SOURCE_FILE, 2, 2); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + String.class.getTypeName(), + 6); + assertSymbol( + mainMethodLocalScope.getSymbols().get(1), + SymbolType.LOCAL, + "var5", + String.class.getTypeName(), + 27); + Scope elseLine10Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(elseLine10Scope, ScopeType.LOCAL, null, 12, 24, SOURCE_FILE, 1, 4); + assertSymbol( + elseLine10Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var31", + String.class.getTypeName(), + 12); + assertSymbol( + elseLine10Scope.getSymbols().get(1), + SymbolType.LOCAL, + "var32", + String.class.getTypeName(), + 13); + assertSymbol( + elseLine10Scope.getSymbols().get(2), + SymbolType.LOCAL, + "var30", + String.class.getTypeName(), + 15); + assertSymbol( + elseLine10Scope.getSymbols().get(3), + SymbolType.LOCAL, + "var3", + String.class.getTypeName(), + 17); + Scope ifLine19Scope = elseLine10Scope.getScopes().get(0); + assertScope(ifLine19Scope, ScopeType.LOCAL, null, 20, 21, SOURCE_FILE, 0, 1); + assertSymbol( + ifLine19Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var4", + String.class.getTypeName(), + 20); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction04() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction04"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction04.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 18, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 18, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 18, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + String.class.getTypeName(), + 5); + Scope forLine6Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(forLine6Scope, ScopeType.LOCAL, null, 6, 15, SOURCE_FILE, 1, 1); + assertSymbol( + forLine6Scope.getSymbols().get(0), SymbolType.LOCAL, "i", Integer.TYPE.getTypeName(), 6); + Scope forBodyLine6Scope = forLine6Scope.getScopes().get(0); + assertScope(forBodyLine6Scope, ScopeType.LOCAL, null, 7, 15, SOURCE_FILE, 1, 2); + assertSymbol( + forBodyLine6Scope.getSymbols().get(0), + SymbolType.LOCAL, + "j", + Integer.TYPE.getTypeName(), + 8); + assertSymbol( + forBodyLine6Scope.getSymbols().get(1), + SymbolType.LOCAL, + "var2", + String.class.getTypeName(), + 7); + Scope forBodyLine8Scope = forBodyLine6Scope.getScopes().get(0); + assertScope(forBodyLine8Scope, ScopeType.LOCAL, null, 9, 15, SOURCE_FILE, 1, 2); + assertSymbol( + forBodyLine8Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var3", + String.class.getTypeName(), + 9); + assertSymbol( + forBodyLine8Scope.getSymbols().get(1), + SymbolType.LOCAL, + "var5", + String.class.getTypeName(), + 14); + Scope forLine10Scope = forBodyLine8Scope.getScopes().get(0); + assertScope(forLine10Scope, ScopeType.LOCAL, null, 10, 12, SOURCE_FILE, 1, 1); + assertSymbol( + forLine10Scope.getSymbols().get(0), SymbolType.LOCAL, "k", Integer.TYPE.getTypeName(), 10); + Scope forBodyLine10Scope = forLine10Scope.getScopes().get(0); + assertScope(forBodyLine10Scope, ScopeType.LOCAL, null, 11, 12, SOURCE_FILE, 0, 1); + assertSymbol( + forBodyLine10Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var4", + String.class.getTypeName(), + 11); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction05() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction05"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction05.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 15, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 15, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 15, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "i", + Integer.TYPE.getTypeName(), + 5); + Scope whileLine6Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(whileLine6Scope, ScopeType.LOCAL, null, 7, 13, SOURCE_FILE, 1, 2); + assertSymbol( + whileLine6Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 7); + assertSymbol( + whileLine6Scope.getSymbols().get(1), SymbolType.LOCAL, "j", Integer.TYPE.getTypeName(), 8); + Scope whileLine9Scope = whileLine6Scope.getScopes().get(0); + assertScope(whileLine9Scope, ScopeType.LOCAL, null, 10, 11, SOURCE_FILE, 0, 1); + assertSymbol( + whileLine9Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var2", + Integer.TYPE.getTypeName(), + 10); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction06() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction06"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction06.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 13, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 13, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 13, SOURCE_FILE, 2, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 5); + Scope catchLine9Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(catchLine9Scope, ScopeType.LOCAL, null, 9, 11, SOURCE_FILE, 0, 2); + assertSymbol( + catchLine9Scope.getSymbols().get(0), + SymbolType.LOCAL, + "var3", + Integer.TYPE.getTypeName(), + 10); + assertSymbol( + catchLine9Scope.getSymbols().get(1), + SymbolType.LOCAL, + "rte", + RuntimeException.class.getTypeName(), + 9); + Scope tryLine6Scope = mainMethodLocalScope.getScopes().get(1); + assertScope(tryLine6Scope, ScopeType.LOCAL, null, 7, 8, SOURCE_FILE, 0, 1); + assertSymbol( + tryLine6Scope.getSymbols().get(0), SymbolType.LOCAL, "var2", Integer.TYPE.getTypeName(), 7); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction07() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction07"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction07.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 10, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 10, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 10, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "i", + Integer.TYPE.getTypeName(), + 5); + Scope doLine6Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(doLine6Scope, ScopeType.LOCAL, null, 7, 8, SOURCE_FILE, 0, 1); + assertSymbol( + doLine6Scope.getSymbols().get(0), SymbolType.LOCAL, "j", Integer.TYPE.getTypeName(), 7); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction08() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction08"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction08.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 11, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 11, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 11, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 5); + Scope line6Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(line6Scope, ScopeType.LOCAL, null, 7, 9, SOURCE_FILE, 0, 2); + assertSymbol( + line6Scope.getSymbols().get(0), SymbolType.LOCAL, "var2", Integer.TYPE.getTypeName(), 7); + assertSymbol( + line6Scope.getSymbols().get(1), SymbolType.LOCAL, "var3", Integer.TYPE.getTypeName(), 8); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction09() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction09"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction09.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 5, 23, SOURCE_FILE, 6, 2); + assertSymbol( + classScope.getSymbols().get(0), + SymbolType.STATIC_FIELD, + "staticIntField", + Integer.TYPE.getTypeName(), + 0); + assertSymbol( + classScope.getSymbols().get(1), + SymbolType.FIELD, + "intField", + Integer.TYPE.getTypeName(), + 0); + assertScope( + classScope.getScopes().get(0), ScopeType.METHOD, "", 5, 17, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 8, 14, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 8); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 8, 14, SOURCE_FILE, 0, 3); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "outside", + Integer.TYPE.getTypeName(), + 8); + assertSymbol( + mainMethodLocalScope.getSymbols().get(1), + SymbolType.LOCAL, + "outside2", + Integer.TYPE.getTypeName(), + 9); + assertSymbol( + mainMethodLocalScope.getSymbols().get(2), + SymbolType.LOCAL, + "lambda", + Supplier.class.getTypeName(), + 10); + Scope processMethodScope = classScope.getScopes().get(2); + assertScope(processMethodScope, ScopeType.METHOD, "process", 19, 23, SOURCE_FILE, 1, 0); + Scope processMethodLocalScope = processMethodScope.getScopes().get(0); + assertScope(processMethodLocalScope, ScopeType.LOCAL, null, 19, 23, SOURCE_FILE, 0, 1); + assertSymbol( + processMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "supplier", + Supplier.class.getTypeName(), + 19); + Scope supplierClosureScope = classScope.getScopes().get(3); + assertScope( + supplierClosureScope, ScopeType.CLOSURE, "lambda$process$1", 20, 21, SOURCE_FILE, 1, 0); + Scope supplierClosureLocalScope = supplierClosureScope.getScopes().get(0); + assertScope(supplierClosureLocalScope, ScopeType.LOCAL, null, 20, 21, SOURCE_FILE, 0, 1); + assertSymbol( + supplierClosureLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 20); + Scope lambdaClosureScope = classScope.getScopes().get(4); + assertScope(lambdaClosureScope, ScopeType.CLOSURE, "lambda$main$0", 11, 12, SOURCE_FILE, 1, 1); + assertSymbol( + lambdaClosureScope.getSymbols().get(0), + SymbolType.ARG, + "outside", + Integer.TYPE.getTypeName(), + 11); + Scope lambdaMethodLocalScope = lambdaClosureScope.getScopes().get(0); + assertScope(lambdaMethodLocalScope, ScopeType.LOCAL, null, 11, 12, SOURCE_FILE, 0, 1); + assertSymbol( + lambdaMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 11); + Scope clinitMethodScope = classScope.getScopes().get(5); + assertScope(clinitMethodScope, ScopeType.METHOD, "", 6, 6, SOURCE_FILE, 0, 0); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction10() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction10"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction10.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock, 2); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + assertEquals(2, symbolSinkMock.jarScopes.get(0).getScopes().size()); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 6, SOURCE_FILE, 2, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 3, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 5, 6, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", String.class.getTypeName(), 5); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 5, 6, SOURCE_FILE, 0, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "winner", + "com.datadog.debugger.symbol.SymbolExtraction10$Inner", + 5); + Scope innerClassScope = symbolSinkMock.jarScopes.get(0).getScopes().get(1); + assertScope(innerClassScope, ScopeType.CLASS, CLASS_NAME + "$Inner", 9, 13, SOURCE_FILE, 2, 1); + assertSymbol( + innerClassScope.getSymbols().get(0), + SymbolType.FIELD, + "field1", + Integer.TYPE.getTypeName(), + 0); + assertScope( + innerClassScope.getScopes().get(0), ScopeType.METHOD, "", 9, 10, SOURCE_FILE, 0, 0); + Scope addToMethod = innerClassScope.getScopes().get(1); + assertScope(addToMethod, ScopeType.METHOD, "addTo", 12, 13, SOURCE_FILE, 1, 1); + assertSymbol( + addToMethod.getSymbols().get(0), SymbolType.ARG, "arg", Integer.TYPE.getTypeName(), 12); + Scope addToMethodLocalScope = addToMethod.getScopes().get(0); + assertScope(addToMethodLocalScope, ScopeType.LOCAL, null, 12, 13, SOURCE_FILE, 0, 1); + assertSymbol( + addToMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 12); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction11() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction11"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction11.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", 1).get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 3, 11, SOURCE_FILE, 2, 1); + assertSymbol( + classScope.getSymbols().get(0), SymbolType.FIELD, "field1", Integer.TYPE.getTypeName(), 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 3, 4, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 6, 11, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", Integer.TYPE.getTypeName(), 6); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 6, 11, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "var1", + Integer.TYPE.getTypeName(), + 6); + Scope ifLine7Scope = mainMethodLocalScope.getScopes().get(0); + assertScope(ifLine7Scope, ScopeType.LOCAL, null, 8, 9, SOURCE_FILE, 0, 1); + assertSymbol( + ifLine7Scope.getSymbols().get(0), SymbolType.LOCAL, "var2", Integer.TYPE.getTypeName(), 8); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction12() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction12"; + final String SOURCE_FILE = SYMBOL_PACKAGE_DIR + "SymbolExtraction12.java"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", 1).get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertScope(classScope, ScopeType.CLASS, CLASS_NAME, 6, 20, SOURCE_FILE, 7, 0); + assertScope(classScope.getScopes().get(0), ScopeType.METHOD, "", 6, 6, SOURCE_FILE, 0, 0); + Scope mainMethodScope = classScope.getScopes().get(1); + assertScope(mainMethodScope, ScopeType.METHOD, "main", 8, 13, SOURCE_FILE, 1, 1); + assertSymbol( + mainMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", Integer.TYPE.getTypeName(), 8); + Scope mainMethodLocalScope = mainMethodScope.getScopes().get(0); + assertScope(mainMethodLocalScope, ScopeType.LOCAL, null, 8, 13, SOURCE_FILE, 0, 2); + assertSymbol( + mainMethodLocalScope.getSymbols().get(0), + SymbolType.LOCAL, + "list", + List.class.getTypeName(), + 8); + assertSymbol( + mainMethodLocalScope.getSymbols().get(1), + SymbolType.LOCAL, + "sum", + Integer.TYPE.getTypeName(), + 12); + Scope fooMethodScope = classScope.getScopes().get(2); + assertScope(fooMethodScope, ScopeType.METHOD, "foo", 17, 20, SOURCE_FILE, 0, 1); + assertSymbol( + fooMethodScope.getSymbols().get(0), SymbolType.ARG, "arg", Integer.TYPE.getTypeName(), 17); + Scope lambdaFoo3MethodScope = classScope.getScopes().get(3); + assertScope( + lambdaFoo3MethodScope, ScopeType.CLOSURE, "lambda$foo$3", 19, 19, SOURCE_FILE, 0, 1); + assertSymbol( + lambdaFoo3MethodScope.getSymbols().get(0), + SymbolType.ARG, + "x", + Integer.TYPE.getTypeName(), + 19); + Scope lambdaFoo2MethodScope = classScope.getScopes().get(4); + assertScope( + lambdaFoo2MethodScope, ScopeType.CLOSURE, "lambda$foo$2", 19, 19, SOURCE_FILE, 0, 1); + assertSymbol( + lambdaFoo2MethodScope.getSymbols().get(0), + SymbolType.ARG, + "x", + Integer.class.getTypeName(), + 19); + Scope lambdaMain1MethodScope = classScope.getScopes().get(5); + assertScope( + lambdaMain1MethodScope, ScopeType.CLOSURE, "lambda$main$1", 11, 11, SOURCE_FILE, 0, 1); + assertSymbol( + lambdaMain1MethodScope.getSymbols().get(0), + SymbolType.ARG, + "x", + Integer.TYPE.getTypeName(), + 11); + Scope lambdaMain0MethodScope = classScope.getScopes().get(6); + assertScope( + lambdaMain0MethodScope, ScopeType.CLOSURE, "lambda$main$0", 11, 11, SOURCE_FILE, 0, 1); + assertSymbol( + lambdaMain0MethodScope.getSymbols().get(0), + SymbolType.ARG, + "x", + Integer.class.getTypeName(), + 11); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction13() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction13"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertLangSpecifics( + classScope.getLanguageSpecifics(), + asList("public"), + asList( + "@com.datadog.debugger.symbol.MyAnnotation", "@com.datadog.debugger.symbol.MyMarker"), + Object.class.getTypeName(), + null, + null); + Scope mainMethodScope = classScope.getScopes().get(1); + assertLangSpecifics( + mainMethodScope.getLanguageSpecifics(), + asList("public", "static"), + asList("@com.datadog.debugger.symbol.MyAnnotation"), + null, + null, + Integer.TYPE.getTypeName()); + assertEquals(3, classScope.getSymbols().size()); + Symbol intField = classScope.getSymbols().get(0); + assertLangSpecifics( + intField.getLanguageSpecifics(), + asList("private"), + asList("@com.datadog.debugger.symbol.MyAnnotation"), + null, + null, + null); + Scope myAnnotationClassScope = symbolSinkMock.jarScopes.get(1).getScopes().get(0); + assertLangSpecifics( + myAnnotationClassScope.getLanguageSpecifics(), + asList("interface", "abstract", "annotation"), + asList("@java.lang.annotation.Target", "@java.lang.annotation.Retention"), + Object.class.getTypeName(), + asList("java.lang.annotation.Annotation"), + null); + Symbol strField = classScope.getSymbols().get(1); + assertLangSpecifics( + strField.getLanguageSpecifics(), + asList("public", "static", "volatile"), + null, + null, + null, + null); + Symbol doubleField = classScope.getSymbols().get(2); + assertLangSpecifics( + doubleField.getLanguageSpecifics(), + asList("protected", "final", "transient"), + null, + null, + null, + null); + } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + public void symbolExtraction14() throws IOException, URISyntaxException { + final String CLASS_NAME = SYMBOL_PACKAGE + "SymbolExtraction14"; + SymbolSinkMock symbolSinkMock = new SymbolSinkMock(config); + SymbolExtractionTransformer transformer = createTransformer(symbolSinkMock); + instr.addTransformer(transformer); + Class testClass = compileAndLoadClass(CLASS_NAME); + Reflect.on(testClass).call("main", "1").get(); + Scope classScope = symbolSinkMock.jarScopes.get(0).getScopes().get(0); + assertLangSpecifics( + classScope.getLanguageSpecifics(), + asList("public", "abstract"), + null, + Object.class.getTypeName(), + asList("com.datadog.debugger.symbol.I1", "com.datadog.debugger.symbol.I2"), + null); + assertEquals(4, classScope.getScopes().size()); + Scope m1MethodScope = classScope.getScopes().get(2); + assertLangSpecifics( + m1MethodScope.getLanguageSpecifics(), + asList("protected", "abstract"), + null, + null, + null, + Void.TYPE.getTypeName()); + Scope m2MethodScope = classScope.getScopes().get(3); + assertLangSpecifics( + m2MethodScope.getLanguageSpecifics(), + asList("private", "final", "synchronized", "(varargs)", "strictfp"), + null, + null, + null, + String.class.getTypeName()); + Scope i1ClassScope = symbolSinkMock.jarScopes.get(1).getScopes().get(0); + assertLangSpecifics( + i1ClassScope.getLanguageSpecifics(), + asList("interface", "abstract"), + null, + Object.class.getTypeName(), + null, + null); + Scope m3MethodScope = i1ClassScope.getScopes().get(0); + assertLangSpecifics( + m3MethodScope.getLanguageSpecifics(), + asList("public", "default"), + null, + null, + null, + Void.TYPE.getTypeName()); + Scope myEnumClassScope = symbolSinkMock.jarScopes.get(3).getScopes().get(0); + assertLangSpecifics( + myEnumClassScope.getLanguageSpecifics(), + asList("final", "enum"), + null, + Enum.class.getTypeName(), + null, + null); + assertEquals(4, myEnumClassScope.getSymbols().size()); + Symbol oneField = myEnumClassScope.getSymbols().get(0); + assertLangSpecifics( + oneField.getLanguageSpecifics(), + asList("public", "static", "final", "enum"), + null, + null, + null, + null); + Symbol valuesField = myEnumClassScope.getSymbols().get(3); + assertLangSpecifics( + valuesField.getLanguageSpecifics(), + asList("private", "static", "final", "synthetic"), + null, + null, + null, + null); + } + + private void assertLangSpecifics( + LanguageSpecifics languageSpecifics, + List expectedModifiers, + List expectedAnnotations, + String expectedSuperClass, + List expectedInterfaces, + String expectedReturnType) { + if (expectedModifiers == null) { + assertNull(languageSpecifics.getAccessModifiers()); + } else { + assertEquals(expectedModifiers, languageSpecifics.getAccessModifiers()); + } + if (expectedAnnotations == null) { + assertNull(languageSpecifics.getAnnotations()); + } else { + assertEquals(expectedAnnotations, languageSpecifics.getAnnotations()); + } + if (expectedSuperClass == null) { + assertNull(languageSpecifics.getSuperClass()); + } else { + assertEquals(expectedSuperClass, languageSpecifics.getSuperClass()); + } + if (expectedInterfaces == null) { + assertNull(languageSpecifics.getInterfaces()); + } else { + assertEquals(expectedInterfaces, languageSpecifics.getInterfaces()); + } + if (expectedReturnType == null) { + assertNull(languageSpecifics.getReturnType()); + } else { + assertEquals(expectedReturnType, languageSpecifics.getReturnType()); + } + } + + private static void assertScope( + Scope scope, + ScopeType scopeType, + String name, + int startLine, + int endLine, + String sourceFile, + int nbScopes, + int nbSymbols) { + assertEquals(scopeType, scope.getScopeType()); + assertEquals(name, scope.getName()); + assertEquals(startLine, scope.getStartLine()); + assertEquals(endLine, scope.getEndLine()); + assertEquals(sourceFile, scope.getSourceFile()); + assertEquals(nbScopes, scope.getScopes().size()); + assertEquals(nbSymbols, scope.getSymbols().size()); + } + + private void assertSymbol( + Symbol symbol, SymbolType symbolType, String name, String type, int line) { + assertEquals(symbolType, symbol.getSymbolType()); + assertEquals(name, symbol.getName()); + assertEquals(type, symbol.getType()); + assertEquals(line, symbol.getLine()); + } + + private SymbolExtractionTransformer createTransformer(SymbolSink symbolSink) { + return createTransformer(symbolSink, 1); + } + + private SymbolExtractionTransformer createTransformer( + SymbolSink symbolSink, int symbolFlushThreshold) { + AllowListHelper allowListHelper = + new AllowListHelper( + new Configuration.FilterList(singletonList(SYMBOL_PACKAGE), emptyList())); + return new SymbolExtractionTransformer( + allowListHelper, new SymbolAggregator(symbolSink, symbolFlushThreshold)); + } + + static class SymbolSinkMock extends SymbolSink { + final List jarScopes = new ArrayList<>(); + + public SymbolSinkMock(Config config) { + super(config); + } + + @Override + public boolean addScope(Scope jarScope) { + return jarScopes.add(jarScope); + } + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/uploader/BatchUploaderTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/uploader/BatchUploaderTest.java index e018598f313..99a9e7db46e 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/uploader/BatchUploaderTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/uploader/BatchUploaderTest.java @@ -55,10 +55,9 @@ public void setup() throws IOException { server.start(); url = server.url(URL_PATH); - when(config.getFinalDebuggerSnapshotUrl()).thenReturn(server.url(URL_PATH).toString()); when(config.getDebuggerUploadTimeout()).thenReturn((int) REQUEST_TIMEOUT.getSeconds()); - uploader = new BatchUploader(config, ratelimitedLogger); + uploader = new BatchUploader(config, url.toString(), ratelimitedLogger); } @AfterEach @@ -73,9 +72,7 @@ public void tearDown() throws IOException { @Test void testOkHttpClientForcesCleartextConnspecWhenNotUsingTLS() { - when(config.getFinalDebuggerSnapshotUrl()).thenReturn("http://example.com"); - - uploader = new BatchUploader(config); + uploader = new BatchUploader(config, "http://example.com"); final List connectionSpecs = uploader.getClient().connectionSpecs(); assertEquals(connectionSpecs.size(), 1); @@ -84,9 +81,7 @@ void testOkHttpClientForcesCleartextConnspecWhenNotUsingTLS() { @Test void testOkHttpClientUsesDefaultConnspecsOverTLS() { - when(config.getFinalDebuggerSnapshotUrl()).thenReturn("https://example.com"); - - uploader = new BatchUploader(config); + uploader = new BatchUploader(config, "https://example.com"); final List connectionSpecs = uploader.getClient().connectionSpecs(); assertEquals(connectionSpecs.size(), 2); @@ -163,7 +158,7 @@ public void testTooManyRequests() throws IOException, InterruptedException { // We need to make sure that initial requests that fill up the queue hang to the duration of the // test. So we specify insanely large timeout here. when(config.getDebuggerUploadTimeout()).thenReturn((int) FOREVER_REQUEST_TIMEOUT.getSeconds()); - uploader = new BatchUploader(config); + uploader = new BatchUploader(config, url.toString()); // We have to block all parallel requests to make sure queue is kept full for (int i = 0; i < BatchUploader.MAX_RUNNING_REQUESTS; i++) { @@ -203,15 +198,15 @@ public void testShutdown() throws IOException, InterruptedException { @Test public void testEmptyUrl() { - when(config.getFinalDebuggerSnapshotUrl()).thenReturn(""); - Assertions.assertThrows(IllegalArgumentException.class, () -> new BatchUploader(config)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new BatchUploader(config, "")); } @Test public void testNoContainerId() throws InterruptedException { // we don't explicitly specify a container ID server.enqueue(new MockResponse().setResponseCode(200)); - BatchUploader uploaderWithNoContainerId = new BatchUploader(config, ratelimitedLogger, null); + BatchUploader uploaderWithNoContainerId = + new BatchUploader(config, url.toString(), ratelimitedLogger, null); uploaderWithNoContainerId.upload(SNAPSHOT_BUFFER); uploaderWithNoContainerId.shutdown(); @@ -225,7 +220,7 @@ public void testContainerIdHeader() throws InterruptedException { server.enqueue(new MockResponse().setResponseCode(200)); BatchUploader uploaderWithContainerId = - new BatchUploader(config, ratelimitedLogger, "testContainerId"); + new BatchUploader(config, url.toString(), ratelimitedLogger, "testContainerId"); uploaderWithContainerId.upload(SNAPSHOT_BUFFER); uploaderWithContainerId.shutdown(); @@ -238,7 +233,7 @@ public void testApiKey() throws InterruptedException { server.enqueue(new MockResponse().setResponseCode(200)); when(config.getApiKey()).thenReturn(API_KEY_VALUE); - BatchUploader uploaderWithApiKey = new BatchUploader(config, ratelimitedLogger); + BatchUploader uploaderWithApiKey = new BatchUploader(config, url.toString(), ratelimitedLogger); uploaderWithApiKey.upload(SNAPSHOT_BUFFER); uploaderWithApiKey.shutdown(); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java new file mode 100644 index 00000000000..038053dd524 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotPrunerTest.java @@ -0,0 +1,232 @@ +package com.datadog.debugger.util; + +import static com.datadog.debugger.sink.SnapshotSink.MAX_SNAPSHOT_SIZE; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class SnapshotPrunerTest { + @Test + public void noPruning() { + assertEquals("[]", SnapshotPruner.prune("[]", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{}", SnapshotPruner.prune("{}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("[{},{},{}]", SnapshotPruner.prune("[{},{},{}]", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":[[],[],[]]}", SnapshotPruner.prune("{\"foo\":[[],[],[]]}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":\"bar\"}", SnapshotPruner.prune("{\"foo\":\"bar\"}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":1001}", SnapshotPruner.prune("{\"foo\":1001}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":3.14}", SnapshotPruner.prune("{\"foo\":3.14}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals("{\"foo\":true}", SnapshotPruner.prune("{\"foo\":true}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo\":{\"name\":\"value\"}}", + SnapshotPruner.prune("{\"foo\":{\"name\":\"value\"}}", MAX_SNAPSHOT_SIZE, 0)); + assertEquals( + "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", + SnapshotPruner.prune( + "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", MAX_SNAPSHOT_SIZE, 0)); + } + + @Test + public void basic() { + assertEquals("aaaa{\"pruned\":true}bbbb", SnapshotPruner.prune("aaaa{}bbbb", 8, 0)); + assertEquals( + "aa{\"pruned\":true}bb", + SnapshotPruner.prune("aa{\"notCapturedReason\": \"depth\"}bb", 8, 0)); + assertEquals( + "aa{\"pruned\":true}bb", + SnapshotPruner.prune("aa{\"notCapturedReason\": \"collectionSize\"}bb", 8, 0)); + } + + @Test + public void priorityPruning() { + final String INPUT = + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"notCapturedReason\": \"depth\"\n" + + " },\n" + + " {\n" + + " \"type\": \"deep\",\n" + + " \"subobject\": {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"subobject\"\n" + + " }\n" + + " }\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}"; + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\"pruned\":true},\n" + + " {\n" + + " \"type\": \"deep\",\n" + + " \"subobject\": {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"subobject\"\n" + + " }\n" + + " }\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 400, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\n" + + " \"type\": \"list\",\n" + + " \"notCapturedReason\": \"collectionSize\"\n" + + " },\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 300, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\n" + + " \"type\": \"complex\",\n" + + " \"value\": \"sfsfsdfklsdfslkfjsdfkjsdklfjsdflksdjfsdlfjsdklfsjdfklsjfksfjslkdfjskdlf\"\n" + + " }\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 250, 0)); + assertEquals( + "{\n" + + " \"elements\":[\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " {\"pruned\":true}\n" + + " ]\n" + + "}", + SnapshotPruner.prune(INPUT, 200, 0)); + } + + @Test + public void sizeReduction() { + final String INPUT = + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"},\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"}\n" + + " ]}\n" + + " }"; + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"},\n" + + " {\"type\": \"Custom\", \"notCapturedReason\": \"depth\"}\n" + + " ]}\n" + + " }", + SnapshotPruner.prune(INPUT, 800, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " ]}\n" + + " }", + SnapshotPruner.prune(INPUT, 440, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 350, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"type\": \"str\", \"value\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\"},\n" + + " {\"pruned\":true}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 270, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"type\": \"list\", \"size\":2, \"elements\": [\n" + + " {\"pruned\":true},\n" + + " {\"pruned\":true}\n" + + " ]},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 240, 0)); + assertEquals( + "{\n" + + " \"keep\": {\"pruned\":true},\n" + + " \"prune\": {\"pruned\":true}\n" + + " }", + SnapshotPruner.prune(INPUT, 120, 0)); + assertEquals("{\"pruned\":true}", SnapshotPruner.prune(INPUT, 20, 0)); + } + + @Test + public void sliceSmallSnapshot() throws Exception { + final int MIN_LEVEL = 6; + String inputSmallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot.json").trim(); + assertEquals( + inputSmallSnapshot, + SnapshotPruner.prune(inputSmallSnapshot, inputSmallSnapshot.length(), MIN_LEVEL)); + String smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned0.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1500, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned1.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1250, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned2.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1100, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned3.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 1000, MIN_LEVEL)); + smallSnapshot = + utils.TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_pruned4.json") + .trim(); + assertEquals(smallSnapshot, SnapshotPruner.prune(inputSmallSnapshot, 500, MIN_LEVEL)); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java deleted file mode 100644 index f6148407097..00000000000 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/SnapshotSlicerTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.datadog.debugger.util; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import utils.TestHelper; - -class SnapshotSlicerTest { - - @Test - public void noSlice() throws Exception { - Assertions.assertEquals("[]", SnapshotSlicer.slice(16, "[]")); - Assertions.assertEquals("{}", SnapshotSlicer.slice(16, "{}")); - Assertions.assertEquals("[{},{},{}]", SnapshotSlicer.slice(16, "[{},{},{}]")); - Assertions.assertEquals( - "{\"foo\":[[],[],[]]}", SnapshotSlicer.slice(16, "{\"foo\":[[],[],[]]}")); - Assertions.assertEquals("{\"foo\":\"bar\"}", SnapshotSlicer.slice(16, "{\"foo\":\"bar\"}")); - Assertions.assertEquals("{\"foo\":1001}", SnapshotSlicer.slice(16, "{\"foo\":1001}")); - Assertions.assertEquals("{\"foo\":3.14}", SnapshotSlicer.slice(16, "{\"foo\":3.14}")); - Assertions.assertEquals("{\"foo\":true}", SnapshotSlicer.slice(16, "{\"foo\":true}")); - Assertions.assertEquals( - "{\"foo\":{\"name\":\"value\"}}", - SnapshotSlicer.slice(16, "{\"foo\":{\"name\":\"value\"}}")); - Assertions.assertEquals( - "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}", - SnapshotSlicer.slice(16, "{\"foo1\":{\"foo2\":{\"foo3\":{\"foo4\":{\"foo5\":{}}}}}}")); - } - - @Test - public void sliceSmallSnapshot() throws Exception { - String inputSmallSnapshot = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot.json").trim(); - Assertions.assertEquals(inputSmallSnapshot, SnapshotSlicer.slice(5, inputSmallSnapshot)); - String smallSnapshot4 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_4.json").trim(); - Assertions.assertEquals(smallSnapshot4, SnapshotSlicer.slice(4, inputSmallSnapshot)); - String smallSnapshot3 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_3.json").trim(); - Assertions.assertEquals(smallSnapshot3, SnapshotSlicer.slice(3, inputSmallSnapshot)); - String smallSnapshot2 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_2.json").trim(); - Assertions.assertEquals(smallSnapshot2, SnapshotSlicer.slice(2, inputSmallSnapshot)); - String smallSnapshot1 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_1.json").trim(); - Assertions.assertEquals(smallSnapshot1, SnapshotSlicer.slice(1, inputSmallSnapshot)); - String smallSnapshot0 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/smallSnapshot_0.json").trim(); - Assertions.assertEquals(smallSnapshot0, SnapshotSlicer.slice(0, inputSmallSnapshot)); - } - - @Test - public void sliceLargeSnapshot() throws Exception { - String inputLargeSnapshot = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot.json").trim(); - String largeSnapshot2 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_2.json").trim(); - Assertions.assertEquals(largeSnapshot2, SnapshotSlicer.slice(2, inputLargeSnapshot)); - String largeSnapshot1 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_1.json").trim(); - Assertions.assertEquals(largeSnapshot1, SnapshotSlicer.slice(1, inputLargeSnapshot)); - String largeSnapshot0 = - TestHelper.getFixtureContent("/com/datadog/debugger/util/largeSnapshot_0.json").trim(); - Assertions.assertEquals(largeSnapshot0, SnapshotSlicer.slice(0, inputLargeSnapshot)); - } -} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java index dd6ebad74c7..739e66b5cff 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ValueScriptHelperTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import datadog.trace.bootstrap.debugger.CapturedContext; +import datadog.trace.bootstrap.debugger.Limits; import datadog.trace.bootstrap.debugger.ProbeId; import datadog.trace.bootstrap.debugger.ProbeImplementation; import datadog.trace.bootstrap.debugger.ProbeLocation; @@ -17,12 +18,13 @@ class ValueScriptHelperTest { new ProbeLocation("java.lang.String", "indexOf", "String.java", Arrays.asList("12-15", "23")); private static final ProbeImplementation DUMMY_PROBE = new ProbeImplementation.NoopProbeImplementation(PROBE_ID, PROBE_LOCATION); + private static final Limits LIMITS = new Limits(1, 3, 255, 5); @Test public void nullValue() { CapturedContext.Status status = new CapturedContext.Status(DUMMY_PROBE); StringBuilder sb = new StringBuilder(); - ValueScriptHelper.serializeValue(sb, "", null, status); + ValueScriptHelper.serializeValue(sb, "", null, status, LIMITS); assertEquals("null", sb.toString()); } @@ -30,7 +32,7 @@ public void nullValue() { public void basicString() { CapturedContext.Status status = new CapturedContext.Status(DUMMY_PROBE); StringBuilder sb = new StringBuilder(); - ValueScriptHelper.serializeValue(sb, "", "foo", status); + ValueScriptHelper.serializeValue(sb, "", "foo", status, LIMITS); assertEquals("foo", sb.toString()); } } diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java index 8b04dcffe2e..5909e5644f2 100644 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot19.java @@ -17,6 +17,8 @@ static class Base { private static int intValue = 24; protected static double doubleValue = 3.14; private static Object obj1; + private static long[] longValues = new long[] {1, 2, 3, 4}; + private static String[] strValues = new String[] {"foo", "bar"}; public Base(Object obj1) { this.obj1 = obj1; diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot20.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot20.java index 07e5fa48828..8ccf68139d0 100644 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot20.java +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/CapturedSnapshot20.java @@ -27,6 +27,9 @@ public static int main(String arg) { AgentTracer.TracerAPI tracerAPI = AgentTracer.get(); AgentSpan span = tracerAPI.buildSpan("process").start(); try (AgentScope scope = tracerAPI.activateSpan(span, ScopeSource.MANUAL)) { + if (arg.equals("exception")) { + return new CapturedSnapshot20().processWithException(arg); + } return new CapturedSnapshot20().process(arg); } finally { span.finish(); @@ -37,4 +40,9 @@ private int process(String arg) { int intLocal = intField + 42; return intLocal; } + + private int processWithException(String arg) { + int intLocal = intField + 42; + throw new RuntimeException("oops"); + } } diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction01.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction01.java new file mode 100644 index 00000000000..aea27733512 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction01.java @@ -0,0 +1,22 @@ +package com.datadog.debugger.symbol; +public class SymbolExtraction01 { + public static int main(String arg) { + int var1 = 1; + if (Integer.parseInt(arg) == 2) { + int var2 = 2; + for (int i = 0; i <= 9; i++) { + int foo = 13; + int bar = 13; + System.out.println(i + foo + bar); + int j = 0; + while (j < 10) { + int var4 = 1; + j++; + } + } + return var2; + } + int var3 = 3; + return var1 + var3; + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction02.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction02.java new file mode 100644 index 00000000000..2b0396c23a5 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction02.java @@ -0,0 +1,8 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction02 { + public static int main(String arg) { + String var1 = "var1"; + return var1.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction03.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction03.java new file mode 100644 index 00000000000..bc2be93c149 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction03.java @@ -0,0 +1,30 @@ + +package com.datadog.debugger.symbol; + +public class SymbolExtraction03 { + public static int main(String arg) { + String var1 = "var1"; + if (arg.equals("foo")) { + String var2 = "var2"; + System.out.println(var2); + } else { + System.out.println(var1); + String var31 = "var31"; + String var32 = "var32"; + System.out.println(var1); + String var30 = "var30"; + System.out.println(var1); + String var3 = "var3"; + System.out.println(var3); + if (arg.equals(var3)) { + String var4 = "var4"; + System.out.println(var4); + } + if (arg.equals(var1)) { + return var3.length(); + } + } + String var5 = "var5"; + return var1.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction04.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction04.java new file mode 100644 index 00000000000..f667317cb92 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction04.java @@ -0,0 +1,20 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction04 { + public static int main(String arg) { + String var1 = "var1"; + for (int i = 0; i < 10; i++) { + String var2 = "var2"; + for (int j = 0; j < 10; j++) { + String var3 = "var3"; + for (int k = 0; k < 10; k++) { + String var4 = "var4"; + System.out.println("var4 = " + var4); + } + String var5 = "var5"; + System.out.println("var5 = " + var5); + } + } + return var1.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction05.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction05.java new file mode 100644 index 00000000000..327adb10431 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction05.java @@ -0,0 +1,17 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction05 { + public static int main(String arg) { + int i = 0; + while (i < 10) { + int var1 = 10; + int j = 0; + while (j < 10) { + int var2 = 1; + j++; + } + i++; + } + return arg.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction06.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction06.java new file mode 100644 index 00000000000..4eeb3173c81 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction06.java @@ -0,0 +1,15 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction06 { + public static int main(String arg) { + int var1 = 1; + try { + int var2 = 2; + throw new RuntimeException("" + var1); + } catch (RuntimeException rte) { + int var3 = 3; + System.out.println("rte = " + rte.getMessage() + var3); + } + return arg.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction07.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction07.java new file mode 100644 index 00000000000..6b3b6f04af1 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction07.java @@ -0,0 +1,12 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction07 { + public static int main(String arg) { + int i = 10; + do { + int j = i + 12; + i--; + } while (i > 0); + return arg.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction08.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction08.java new file mode 100644 index 00000000000..a06a4561a64 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction08.java @@ -0,0 +1,13 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction08 { + public static int main(String arg) { + int var1 = 1; + { + int var2 = 2; + int var3 = 3; + int var4 = var2 + var3; // var4 is not in the LocalVariableTable because last statement of the scope + } + return arg.length(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction09.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction09.java new file mode 100644 index 00000000000..fac032d858c --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction09.java @@ -0,0 +1,25 @@ +package com.datadog.debugger.symbol; + +import java.util.function.Supplier; + +public class SymbolExtraction09 { + static int staticIntField = 42; + public static int main(String arg) { + int outside = 12; + int outside2 = 1337; + Supplier lambda = () -> { + int var1 = 1; + return var1 + outside + staticIntField; + }; + return lambda.get(); + } + + int intField = 42; + public int process() { + Supplier supplier = () -> { + int var1 = 1; + return var1 + intField; + }; + return supplier.get(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction10.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction10.java new file mode 100644 index 00000000000..b1d6f28bea7 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction10.java @@ -0,0 +1,16 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction10 { + public static int main(String arg) { + Inner winner = new Inner(); + return winner.addTo(12); + } + + static class Inner { + private final int field1 = 1; + public int addTo(int arg) { + int var1 = 2; + return var1 + arg; + } + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction11.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction11.java new file mode 100644 index 00000000000..b4ba9ccd520 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction11.java @@ -0,0 +1,13 @@ +package com.datadog.debugger.symbol; + +public class SymbolExtraction11 { + private final int field1 = 1; + public static int main(int arg) { + int var1 = 1; + if (arg == 42) { + int var2 = 2; + return var2; + } + return var1 + arg; + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction12.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction12.java new file mode 100644 index 00000000000..330fbb1ae81 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction12.java @@ -0,0 +1,22 @@ +package com.datadog.debugger.symbol; + +import java.util.Arrays; +import java.util.List; + +public class SymbolExtraction12 { + public static int main(int arg) { + List list = Arrays.asList(1, 2, 3); + int sum = list + .stream() + .mapToInt(x -> x + 1).map(x -> x - 12) + .sum(); + return sum; + } + + public static int foo(int arg) { + return Arrays.asList(1, 2, 3, 4) + .stream() + .mapToInt(x -> x + 1).map(x -> x - 12) + .sum(); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java new file mode 100644 index 00000000000..6cd268066ef --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction13.java @@ -0,0 +1,39 @@ +package com.datadog.debugger.symbol; + +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@MyAnnotation("class") +@MyMarker +public class SymbolExtraction13 { + + @MyAnnotation("field") + private int intField; + public static volatile String strField; + protected final transient double doubleField = 3.14; + + @MyAnnotation("method") + public static int main(String arg) { + System.out.println(MyAnnotation.class); + return 42; + } + + private static class InnerClass { + + } +} + +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface MyAnnotation { + String value() default ""; +} + +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@interface MyMarker { +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java new file mode 100644 index 00000000000..417867c3659 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/symbol/SymbolExtraction14.java @@ -0,0 +1,36 @@ +package com.datadog.debugger.symbol; + +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public abstract class SymbolExtraction14 extends Object implements I1, I2{ + + public static int main(String arg) { + System.out.println(MyEnum.ONE); + return 42; + } + + protected abstract void m1(); + private strictfp synchronized final String m2(String... strVarArgs) { + return null; + } + +} + +interface I1 { + default void m3(){} +} + +interface I2 { + +} + +enum MyEnum { + ONE, + TWO, + THREE +} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json deleted file mode 100644 index f15769e4a1c..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$$Lambda$1112/1706225206"},"keepAliveTime":{"type":"long","value":"0"},"largestPoolSize":{"type":"int","value":"1"},"__datadogContext$3":{"type":"java.lang.Boolean","value":"true"},"allowCoreThreadTimeOut":{"type":"boolean","value":"false"},"corePoolSize":{"type":"int","value":"1"},"ctl":{"type":"java.util.concurrent.atomic.AtomicInteger","value":"-536870911"},"completedTaskCount":{"type":"long","value":"0"},"workQueue":{"notCapturedReason":"depth","type":"java.util.concurrent.LinkedBlockingQueue"},"maximumPoolSize":{"type":"int","value":"1"},"workers":{"notCapturedReason":"depth","type":"java.util.HashSet"},"mainLock":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.ReentrantLock"}}}}},"logger":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.Logger"},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"notCapturedReason":"depth","type":"java.util.concurrent.CopyOnWriteArrayList"},"loggerContext":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.LoggerContext"},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet.VetController"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"isNull":true,"type":"java.util.List"},"loggerContext":{"notCapturedReason":"fieldCount","type":"ch.qos.logback.classic.LoggerContext","fields":{"loggerCache":{"notCapturedReason":"depth","type":"java.util.concurrent.ConcurrentHashMap"},"frameworkPackages":{"notCapturedReason":"depth","type":"java.util.ArrayList"},"resetCount":{"type":"int","value":"2"},"noAppenderWarning":{"type":"int","value":"0"},"configurationLock":{"notCapturedReason":"depth","type":"ch.qos.logback.core.spi.LogbackLock"},"birthTime":{"type":"long","value":"1686734324162"},"objectMap":{"notCapturedReason":"depth","type":"java.util.HashMap"},"propertyMap":{"notCapturedReason":"depth","type":"java.util.HashMap"},"lifeCycleManager":{"notCapturedReason":"depth","type":"ch.qos.logback.core.LifeCycleManager"},"loggerContextListenerList":{"notCapturedReason":"depth","type":"java.util.ArrayList"},"size":{"type":"int","value":"995"},"maxCallerDataDepth":{"type":"int","value":"8"},"packagingDataEnabled":{"type":"boolean","value":"true"},"root":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.Logger"},"name":{"type":"java.lang.String","value":"default"},"sm":{"notCapturedReason":"depth","type":"ch.qos.logback.core.BasicStatusManager"},"scheduledExecutorService":{"isNull":true,"type":"java.util.concurrent.ScheduledExecutorService"},"loggerContextRemoteView":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.spi.LoggerContextVO"},"turboFilterList":{"notCapturedReason":"depth","type":"ch.qos.logback.classic.spi.TurboFilterList"},"scheduledFutures":{"notCapturedReason":"depth","type":"java.util.ArrayList"}}},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","fields":{"m0":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"8"},"modifiers":{"type":"int","value":"257"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"hashCode"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m1":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"6"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"equals"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m2":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"7"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"toString"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"class java.lang.Object"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class java.lang.String"}}},"m3":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"notCapturedReason":"depth","type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"notCapturedReason":"depth","type":"java.util.LinkedHashMap"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()Ljava/util/Collection;"},"annotations":{"notCapturedReason":"depth","type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"findAll"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.samples.petclinic.vet.VetRepository"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"interface java.util.Collection"}}},"m4":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"indexOf"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m7":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"3"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getTargetSource"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"interface org.springframework.aop.TargetSource"}}},"m8":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()[Ljava/lang/Class<*>;"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"5"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getProxiedInterfaces"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class [Ljava.lang.Class;"}}},"m9":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"(Ljava/lang/Class<*>;)Z"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"6"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isInterfaceProxied"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m21":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"18"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"removeAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m10":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"7"},"modifiers":{"type":"int","value":"1"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getAdvisorCount"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"int"}}},"m24":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"21"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m13":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"10"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"setTargetSource"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m12":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"9"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isProxyTargetClass"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m23":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"20"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m15":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"12"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"isExposeProxy"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m25":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"22"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"removeAdvice"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"boolean"}}},"m14":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"11"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"setExposeProxy"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m27":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"type":"java.lang.String","value":"()Ljava/lang/Class<*>;"},"annotations":{"notCapturedReason":"depth","type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"0"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"notCapturedReason":"depth","type":"sun.reflect.DelegatingMethodAccessorImpl"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"getTargetClass"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.TargetClassAware"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"class java.lang.Class"}}},"m19":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"16"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"isNull":true,"type":"sun.reflect.MethodAccessor"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}},"m18":{"type":"java.lang.reflect.Method","fields":{"genericInfo":{"isNull":true,"type":"sun.reflect.generics.repository.MethodRepository"},"hasRealParameterData":{"type":"boolean","value":"false"},"declaredAnnotations":{"isNull":true,"type":"java.util.Map"},"parameterTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"signature":{"isNull":true,"type":"java.lang.String"},"annotations":{"isNull":true,"type":"byte[]"},"securityCheckCache":{"isNull":true,"type":"java.lang.Object"},"slot":{"type":"int","value":"15"},"modifiers":{"type":"int","value":"1025"},"methodAccessor":{"notCapturedReason":"depth","type":"sun.reflect.DelegatingMethodAccessorImpl"},"exceptionTypes":{"notCapturedReason":"depth","type":"java.lang.Class[]"},"annotationDefault":{"isNull":true,"type":"byte[]"},"root":{"notCapturedReason":"depth","type":"java.lang.reflect.Method"},"name":{"type":"java.lang.String","value":"addAdvisor"},"parameterAnnotations":{"isNull":true,"type":"byte[]"},"override":{"type":"boolean","value":"false"},"clazz":{"type":"java.lang.Class","value":"interface org.springframework.aop.framework.Advised"},"parameters":{"isNull":true,"type":"java.lang.reflect.Parameter[]"},"returnType":{"type":"java.lang.Class","value":"void"}}}}},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","fields":{"value":{"isNull":true,"type":"java.lang.Object"}}}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","fields":{"vets":{"size":"12000","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.Vet"}],"notCapturedReason":"collectionSize","type":"java.util.ArrayList"}}}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json deleted file mode 100644 index 46b82a86e8f..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_0.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","notCapturedReason":"depth"},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json deleted file mode 100644 index fb652cd0dc6..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_1.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","notCapturedReason":"depth"},"logger":{"type":"ch.qos.logback.classic.Logger","notCapturedReason":"depth"},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","notCapturedReason":"depth"},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","notCapturedReason":"depth"}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json deleted file mode 100644 index f18a6613a91..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/largeSnapshot_2.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke","lineNumber":62},{"fileName":"DelegatingMethodAccessorImpl.java","function":"sun.reflect.DelegatingMethodAccessorImpl.invoke","lineNumber":43},{"fileName":"Method.java","function":"java.lang.reflect.Method.invoke","lineNumber":498},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.doInvoke","lineNumber":197},{"fileName":"InvocableHandlerMethod.java","function":"org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest","lineNumber":141},{"fileName":"ServletInvocableHandlerMethod.java","function":"org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle","lineNumber":106},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod","lineNumber":894},{"fileName":"RequestMappingHandlerAdapter.java","function":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal","lineNumber":808},{"fileName":"AbstractHandlerMethodAdapter.java","function":"org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle","lineNumber":87},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doDispatch","lineNumber":1060},{"fileName":"DispatcherServlet.java","function":"org.springframework.web.servlet.DispatcherServlet.doService","lineNumber":962},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.processRequest","lineNumber":1006},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.doGet","lineNumber":898},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":626},{"fileName":"FrameworkServlet.java","function":"org.springframework.web.servlet.FrameworkServlet.service","lineNumber":883},{"fileName":"HttpServlet.java","function":"javax.servlet.http.HttpServlet.service","lineNumber":733},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":227},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WsFilter.java","function":"org.apache.tomcat.websocket.server.WsFilter.doFilter","lineNumber":53},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ResourceUrlEncodingFilter.java","function":"org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilter","lineNumber":67},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"RequestContextFilter.java","function":"org.springframework.web.filter.RequestContextFilter.doFilterInternal","lineNumber":100},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"FormContentFilter.java","function":"org.springframework.web.filter.FormContentFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"HandlerMappingResourceNameFilter.java","function":"datadog.trace.instrumentation.springweb.HandlerMappingResourceNameFilter.doFilterInternal","lineNumber":50},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"WebMvcMetricsFilter.java","function":"org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal","lineNumber":93},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"CharacterEncodingFilter.java","function":"org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal","lineNumber":201},{"fileName":"OncePerRequestFilter.java","function":"org.springframework.web.filter.OncePerRequestFilter.doFilter","lineNumber":119},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"ServletRequestPathFilter.java","function":"org.springframework.web.filter.ServletRequestPathFilter.doFilter","lineNumber":55},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate","lineNumber":358},{"fileName":"DelegatingFilterProxy.java","function":"org.springframework.web.filter.DelegatingFilterProxy.doFilter","lineNumber":271},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.internalDoFilter","lineNumber":189},{"fileName":"ApplicationFilterChain.java","function":"org.apache.catalina.core.ApplicationFilterChain.doFilter","lineNumber":162},{"fileName":"StandardWrapperValve.java","function":"org.apache.catalina.core.StandardWrapperValve.invoke","lineNumber":202},{"fileName":"StandardContextValve.java","function":"org.apache.catalina.core.StandardContextValve.invoke","lineNumber":97},{"fileName":"AuthenticatorBase.java","function":"org.apache.catalina.authenticator.AuthenticatorBase.invoke","lineNumber":542},{"fileName":"StandardHostValve.java","function":"org.apache.catalina.core.StandardHostValve.invoke","lineNumber":143},{"fileName":"ErrorReportValve.java","function":"org.apache.catalina.valves.ErrorReportValve.invoke","lineNumber":92},{"fileName":"StandardEngineValve.java","function":"org.apache.catalina.core.StandardEngineValve.invoke","lineNumber":78},{"fileName":"CoyoteAdapter.java","function":"org.apache.catalina.connector.CoyoteAdapter.service","lineNumber":357},{"fileName":"Http11Processor.java","function":"org.apache.coyote.http11.Http11Processor.service","lineNumber":374},{"fileName":"AbstractProcessorLight.java","function":"org.apache.coyote.AbstractProcessorLight.process","lineNumber":65},{"fileName":"AbstractProtocol.java","function":"org.apache.coyote.AbstractProtocol$ConnectionHandler.process","lineNumber":893},{"fileName":"NioEndpoint.java","function":"org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun","lineNumber":1707},{"fileName":"SocketProcessorBase.java","function":"org.apache.tomcat.util.net.SocketProcessorBase.run","lineNumber":49},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor.runWorker","lineNumber":1149},{"fileName":"ThreadPoolExecutor.java","function":"java.util.concurrent.ThreadPoolExecutor$Worker.run","lineNumber":624},{"fileName":"TaskThread.java","function":"org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run","lineNumber":61},{"fileName":"Thread.java","function":"java.lang.Thread.run","lineNumber":750}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","notCapturedReason":"depth"}}},"logger":{"type":"ch.qos.logback.classic.Logger","fields":{"parent":{"type":"ch.qos.logback.classic.Logger","notCapturedReason":"depth"},"level":{"isNull":true,"type":"ch.qos.logback.classic.Level"},"name":{"type":"java.lang.String","value":"org.springframework.samples.petclinic.vet.VetController"},"aai":{"isNull":true,"type":"ch.qos.logback.core.spi.AppenderAttachableImpl"},"childrenList":{"isNull":true,"type":"java.util.List"},"loggerContext":{"notCapturedReason":"fieldCount","type":"ch.qos.logback.classic.LoggerContext","notCapturedReason":"depth"},"effectiveLevelInt":{"type":"int","value":"20000"},"additive":{"type":"boolean","value":"true"}}},"vets":{"notCapturedReason":"fieldCount","type":"com.sun.proxy.$Proxy165","fields":{"m0":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m1":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m2":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m3":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m4":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m7":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m8":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m9":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m21":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m10":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m24":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m13":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m12":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m23":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m15":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m25":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m14":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m27":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m19":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"},"m18":{"type":"java.lang.reflect.Method","notCapturedReason":"depth"}}},"garbage":{"size":"10","elements":[{"size":"7515","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"4412","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"3665","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"635","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6794","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"7280","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"6563","elements":[{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"85","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"type":"java.lang.Object[]"},{"size":"4819","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"},{"size":"2521","elements":[{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"java.lang.Object"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$MiddleObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"},{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$LargeObject"}],"notCapturedReason":"collectionSize","type":"java.lang.Object[]"}],"type":"java.util.ArrayList"},"counter":{"type":"int","value":"0"},"timeout":{"type":"java.time.Duration","value":"PT30S"},"syntheticLiveSet":{"type":"java.util.concurrent.atomic.AtomicReference","notCapturedReason":"depth"}}},"model":{"entries":[[{"type":"java.lang.String","value":"vets"},{"type":"org.springframework.samples.petclinic.vet.Vets","notCapturedReason":"depth"}]],"size":"1","type":"org.springframework.validation.support.BindingAwareModelMap"},"uuid":{"isNull":true,"type":"java.lang.String"}},"locals":{"@return":{"type":"java.lang.String","value":"vets/vetList"}}}},"language":"java","id":"75332018-9ffa-41f2-9aa1-5a3804fabe9c","probe":{"location":{"method":"showVetList","type":"org.springframework.samples.petclinic.vet.VetController"},"id":"2147bca7-2880-4ce5-a856-6b5f161b5f79","version":4},"timestamp":1686734349534}},"logger":{"thread_id":376,"method":"showVetList","thread_name":"http-nio-8080-exec-1","name":"org.springframework.samples.petclinic.vet.VetController","version":2},"timestamp":1686734349534}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json index 560f0b48f7a..4c7303c2131 100644 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot.json @@ -1 +1 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"fields":{"level":4,"field41":{"fields":{"level":5,"field51":"foo51","field52":"foo52"}},"field42":"foo42"}},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"notCapturedReason":"depth","type":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"notCapturedReason":"depth","type":"org.springframework.samples.petclinic.vet.VetController$$Lambda$1112/1706225206"}}}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json deleted file mode 100644 index b14e715d2ed..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_0.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"notCapturedReason":"depth"},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json deleted file mode 100644 index 20c31bf7885..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_1.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"notCapturedReason":"depth"},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json deleted file mode 100644 index 023aca34040..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_2.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"notCapturedReason":"depth"},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json deleted file mode 100644 index 716489fdef1..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_3.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"notCapturedReason":"depth"},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json deleted file mode 100644 index f6f29a64bf5..00000000000 --- a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_4.json +++ /dev/null @@ -1 +0,0 @@ -{"level":0,"this":{"fields":{"level":1,"field11":{"fields":{"level":2,"field21":{"fields":{"level":3,"field31":{"fields":{"level":4,"field41":{"notCapturedReason":"depth"},"field42":"foo42"}},"field32":"foo32"}},"field22":"foo22"}},"field12":"foo12"}},"field02":"foo02"} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json new file mode 100644 index 00000000000..f47e198fc02 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned0.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"type":"java.util.concurrent.ThreadPoolExecutor","fields":{"termination":{"pruned":true},"acc":{"isNull":true,"type":"java.security.AccessControlContext"},"handler":{"notCapturedReason":"depth","type":"java.util.concurrent.ThreadPoolExecutor$AbortPolicy"},"threadFactory":{"pruned":true}}}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json new file mode 100644 index 00000000000..d4def329595 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned1.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"type":"long","value":"1686734349511"},"executor":{"type":"java.util.concurrent.Executors$FinalizableDelegatedExecutorService","fields":{"e":{"pruned":true}}}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json new file mode 100644 index 00000000000..5198d062c7d --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned2.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"result":{"type":"int","value":"0"},"garbageStart":{"pruned":true},"executor":{"pruned":true}}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json new file mode 100644 index 00000000000..d8268f4591f --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned3.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"arguments":{"this":{"type":"org.springframework.samples.petclinic.vet.VetController","fields":{"pruned":true}}}}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json new file mode 100644 index 00000000000..495103ba4a6 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/util/smallSnapshot_pruned4.json @@ -0,0 +1 @@ +{"id":"AgAAAYi5M6TejOuSNQAAAAAAAAAYAAAAAEFZaTVNNmtLQUFDRkJYcG5fLTNHaUFBQQAAACQAAAAAMDE4OGI5MzMtZTBkNC00MzdhLWEwNmEtZjk3ZGFhOWY5YWQ3","content":{"timestamp":"2023-06-14T09:19:09.534Z","tags":["agent_version:7.45.0","env:","host_name:comp-kv33mv2fv3","source:dd_debugger","debugger_version:1.16.0-snapshot_587bb56c3c","version:","host:comp-kv33mv2fv3","default_env:none","datadog.submission_auth:private_api_key"],"service":"petclinic-benchmark","message":"Executed VetController.showVetList, it took 465ms","attributes":{"duration":465830375,"service":"petclinic-benchmark","debugger":{"snapshot":{"stack":[{"fileName":"VetController.java","function":"org.springframework.samples.petclinic.vet.VetController.showVetList","lineNumber":174},{"fileName":"NativeMethodAccessorImpl.java","function":"sun.reflect.NativeMethodAccessorImpl.invoke0","lineNumber":-2}],"captures":{"return":{"pruned":true}}}}}}} diff --git a/dd-java-agent/agent-debugger/src/test/resources/debugger-symbol.jar b/dd-java-agent/agent-debugger/src/test/resources/debugger-symbol.jar new file mode 100644 index 00000000000..2113a1358ad Binary files /dev/null and b/dd-java-agent/agent-debugger/src/test/resources/debugger-symbol.jar differ diff --git a/dd-java-agent/agent-iast/build.gradle b/dd-java-agent/agent-iast/build.gradle index 3a42e0871f4..a6474456aa6 100644 --- a/dd-java-agent/agent-iast/build.gradle +++ b/dd-java-agent/agent-iast/build.gradle @@ -1,13 +1,29 @@ +import net.ltgt.gradle.errorprone.CheckSeverity + plugins { id 'com.github.johnrengelman.shadow' id 'me.champeau.jmh' id 'java-test-fixtures' id 'com.google.protobuf' version '0.8.18' + id 'net.ltgt.errorprone' version '3.1.0' } apply from: "$rootDir/gradle/java.gradle" apply from: "$rootDir/gradle/version.gradle" +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} + +tasks.withType(AbstractCompile).configureEach { + // ensure no APIs beyond JDK8 are used + options.release = 8 +} + // First version with Mac M1 support def grpcVersion = '1.42.2' protobuf { @@ -49,6 +65,10 @@ dependencies { jmh project(':dd-java-agent:agent-builder') jmh project(':dd-java-agent:instrumentation:iast-instrumenter') jmh project(':dd-java-agent:instrumentation:java-lang') + + compileOnly('org.jetbrains:annotations:24.0.0') + errorprone('com.uber.nullaway:nullaway:0.10.15') + errorprone('com.google.errorprone:error_prone_core:2.23.0') } shadowJar { @@ -80,7 +100,6 @@ ext { tasks.withType(Test).configureEach { jvmArgs += ['-Ddd.iast.enabled=true'] } -def rootDir = project.rootDir spotless { java { target 'src/**/*.java' @@ -104,3 +123,16 @@ sourceSets { } } } + +tasks.withType(JavaCompile).configureEach { + if (name == 'compileJava') { + options.errorprone { + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.datadog.iast") + disableAllWarnings = true // only errors for now + } + } else { + // disable null away for test and jmh + options.errorprone.enabled = false + } +} diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderAppendBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderAppendBenchmark.java index babe608c0f3..e4bb2ee9f46 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderAppendBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderAppendBenchmark.java @@ -1,6 +1,6 @@ package com.datadog.iast.propagation; -import static com.datadog.iast.model.Range.NOT_MARKED; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderBatchBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderBatchBenchmark.java index 1d5f61a43bd..8bb5d4adb20 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderBatchBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderBatchBenchmark.java @@ -1,5 +1,6 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import static java.util.concurrent.TimeUnit.MICROSECONDS; import com.datadog.iast.IastRequestContext; @@ -34,8 +35,7 @@ protected StringBuilderBatchBenchmark.Context initializeContext() { final String value; if (current < limit) { value = - tainted( - context, UUID.randomUUID().toString(), new Range(3, 6, source(), Range.NOT_MARKED)); + tainted(context, UUID.randomUUID().toString(), new Range(3, 6, source(), NOT_MARKED)); } else { value = notTainted(UUID.randomUUID().toString()); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderInitBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderInitBenchmark.java index a3c832355dc..ec11b927ff2 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderInitBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderInitBenchmark.java @@ -1,5 +1,7 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; + import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; import datadog.trace.instrumentation.java.lang.StringBuilderCallSite; @@ -14,7 +16,7 @@ protected Context initializeContext() { final IastRequestContext context = new IastRequestContext(); final String notTainted = notTainted("I am not a tainted string"); final String tainted = - tainted(context, "I am a tainted string", new Range(3, 6, source(), Range.NOT_MARKED)); + tainted(context, "I am a tainted string", new Range(3, 6, source(), NOT_MARKED)); return new Context(context, notTainted, tainted); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderToStringBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderToStringBenchmark.java index 707e9b04611..1a8468610a4 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderToStringBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringBuilderToStringBenchmark.java @@ -1,5 +1,7 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; + import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; import datadog.trace.instrumentation.java.lang.StringBuilderCallSite; @@ -18,7 +20,7 @@ protected Context initializeContext() { tainted( context, new StringBuilder("I am a tainted string builder"), - new Range(5, 7, source(), Range.NOT_MARKED)); + new Range(5, 7, source(), NOT_MARKED)); return new Context(context, notTaintedBuilder, taintedBuilder); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatBenchmark.java index 4dded1b999e..0858263ff00 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatBenchmark.java @@ -1,5 +1,7 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; + import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; import datadog.trace.instrumentation.java.lang.StringCallSite; @@ -13,7 +15,7 @@ protected StringConcatBenchmark.Context initializeContext() { final IastRequestContext context = new IastRequestContext(); final String notTainted = notTainted("I am not a tainted string"); final String tainted = - tainted(context, "I am a tainted string", new Range(3, 5, source(), Range.NOT_MARKED)); + tainted(context, "I am a tainted string", new Range(3, 5, source(), NOT_MARKED)); return new StringConcatBenchmark.Context(context, notTainted, tainted); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBatchBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBatchBenchmark.java index 42e1741f361..59f0353ec51 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBatchBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBatchBenchmark.java @@ -1,5 +1,6 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import static java.util.concurrent.TimeUnit.MICROSECONDS; import com.datadog.iast.IastRequestContext; @@ -54,7 +55,7 @@ protected StringConcatFactoryBatchBenchmark.Context initializeContext() { double current = i / (double) stringCount; final String value; if (current < limit) { - value = tainted(context, "Yep, tainted", new Range(3, 5, source(), Range.NOT_MARKED)); + value = tainted(context, "Yep, tainted", new Range(3, 5, source(), NOT_MARKED)); } else { value = notTainted("Nop, tainted"); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBenchmark.java index 6f99576fa56..9008bea3ad4 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringConcatFactoryBenchmark.java @@ -1,5 +1,7 @@ package com.datadog.iast.propagation; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; + import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; import datadog.trace.api.iast.InstrumentationBridge; @@ -13,8 +15,7 @@ public class StringConcatFactoryBenchmark protected StringConcatFactoryBenchmark.Context initializeContext() { final IastRequestContext context = new IastRequestContext(); final String notTainted = notTainted("Nop, tainted"); - final String tainted = - tainted(context, "Yep, tainted", new Range(3, 5, source(), Range.NOT_MARKED)); + final String tainted = tainted(context, "Yep, tainted", new Range(3, 5, source(), NOT_MARKED)); return new StringConcatFactoryBenchmark.Context(context, notTainted, tainted); } diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringJoinBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringJoinBenchmark.java index 5b7b4d87a69..3f8bb4b6cfd 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringJoinBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringJoinBenchmark.java @@ -1,6 +1,6 @@ package com.datadog.iast.propagation; -import static com.datadog.iast.model.Range.NOT_MARKED; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; diff --git a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringSubsequenceBenchmark.java b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringSubsequenceBenchmark.java index 78e8f030be8..c8ba76a2add 100644 --- a/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringSubsequenceBenchmark.java +++ b/dd-java-agent/agent-iast/src/jmh/java/com/datadog/iast/propagation/StringSubsequenceBenchmark.java @@ -1,6 +1,6 @@ package com.datadog.iast.propagation; -import static com.datadog.iast.model.Range.NOT_MARKED; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Dependencies.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Dependencies.java new file mode 100644 index 00000000000..e4df7602205 --- /dev/null +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Dependencies.java @@ -0,0 +1,41 @@ +package com.datadog.iast; + +import com.datadog.iast.overhead.OverheadController; +import datadog.trace.api.Config; +import datadog.trace.util.stacktrace.StackWalker; +import javax.annotation.Nonnull; + +public class Dependencies { + + private final Config config; + private final Reporter reporter; + private final OverheadController overheadController; + private final StackWalker stackWalker; + + public Dependencies( + @Nonnull final Config config, + @Nonnull final Reporter reporter, + @Nonnull final OverheadController overheadController, + @Nonnull final StackWalker stackWalker) { + this.config = config; + this.reporter = reporter; + this.overheadController = overheadController; + this.stackWalker = stackWalker; + } + + public Config getConfig() { + return config; + } + + public Reporter getReporter() { + return reporter; + } + + public OverheadController getOverheadController() { + return overheadController; + } + + public StackWalker getStackWalker() { + return stackWalker; + } +} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java index 7091ffc23af..21df4f14862 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/GrpcRequestMessageHandler.java @@ -2,10 +2,12 @@ import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; -import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; +import datadog.trace.api.iast.telemetry.IastMetric; +import datadog.trace.api.iast.telemetry.IastMetricCollector; import java.util.function.BiFunction; import javax.annotation.Nonnull; @@ -29,9 +31,13 @@ public class GrpcRequestMessageHandler implements BiFunction apply(final RequestContext ctx, final Object o) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null && o != null) { - final IastRequestContext iastCtx = ctx.getData(RequestContextSlot.IAST); - module.taintDeeply( - iastCtx, SourceTypes.GRPC_BODY, o, GrpcRequestMessageHandler::isProtobufArtifact); + final IastContext iastCtx = IastContext.Provider.get(ctx); + final byte source = SourceTypes.GRPC_BODY; + final int tainted = + module.taintDeeply(iastCtx, o, source, GrpcRequestMessageHandler::isProtobufArtifact); + if (tainted > 0) { + IastMetricCollector.add(IastMetric.EXECUTED_SOURCE, source, tainted, iastCtx); + } } return Flow.ResultFlow.empty(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HasDependencies.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HasDependencies.java deleted file mode 100644 index 9e1d7cf758f..00000000000 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HasDependencies.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.datadog.iast; - -import com.datadog.iast.overhead.OverheadController; -import datadog.trace.api.Config; -import datadog.trace.util.stacktrace.StackWalker; -import javax.annotation.Nonnull; - -public interface HasDependencies { - - void registerDependencies(@Nonnull Dependencies dependencies); - - class Dependencies { - private final Config config; - private final Reporter reporter; - private final OverheadController overheadController; - private final StackWalker stackWalker; - - public Dependencies( - @Nonnull final Config config, - @Nonnull final Reporter reporter, - @Nonnull final OverheadController overheadController, - @Nonnull final StackWalker stackWalker) { - this.config = config; - this.reporter = reporter; - this.overheadController = overheadController; - this.stackWalker = stackWalker; - } - - public Config getConfig() { - return config; - } - - public Reporter getReporter() { - return reporter; - } - - public OverheadController getOverheadController() { - return overheadController; - } - - public StackWalker getStackWalker() { - return stackWalker; - } - } -} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java index a5472fb3b00..15817bde263 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java @@ -4,25 +4,25 @@ import com.datadog.iast.overhead.OverheadContext; import com.datadog.iast.taint.TaintedObjects; import datadog.trace.api.gateway.RequestContext; -import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.telemetry.IastMetricCollector; import datadog.trace.api.iast.telemetry.IastMetricCollector.HasMetricCollector; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class IastRequestContext implements HasMetricCollector { +public class IastRequestContext implements IastContext, HasMetricCollector { private final VulnerabilityBatch vulnerabilityBatch; private final AtomicBoolean spanDataIsSet; private final TaintedObjects taintedObjects; private final OverheadContext overheadContext; - private final IastMetricCollector collector; - private volatile String strictTransportSecurity; - private volatile String xContentTypeOptions; - private volatile String xForwardedProto; - private volatile String contentType; + @Nullable private final IastMetricCollector collector; + @Nullable private volatile String strictTransportSecurity; + @Nullable private volatile String xContentTypeOptions; + @Nullable private volatile String xForwardedProto; + @Nullable private volatile String contentType; public IastRequestContext() { this(TaintedObjects.acquire(), null); @@ -33,7 +33,7 @@ public IastRequestContext(final TaintedObjects taintedObjects) { } public IastRequestContext( - final TaintedObjects taintedObjects, final IastMetricCollector collector) { + final TaintedObjects taintedObjects, @Nullable final IastMetricCollector collector) { this.vulnerabilityBatch = new VulnerabilityBatch(); this.spanDataIsSet = new AtomicBoolean(false); this.overheadContext = new OverheadContext(); @@ -45,6 +45,7 @@ public VulnerabilityBatch getVulnerabilityBatch() { return vulnerabilityBatch; } + @Nullable public String getStrictTransportSecurity() { return strictTransportSecurity; } @@ -53,6 +54,7 @@ public void setStrictTransportSecurity(final String strictTransportSecurity) { this.strictTransportSecurity = strictTransportSecurity; } + @Nullable public String getxContentTypeOptions() { return xContentTypeOptions; } @@ -61,6 +63,7 @@ public void setxContentTypeOptions(final String xContentTypeOptions) { this.xContentTypeOptions = xContentTypeOptions; } + @Nullable public String getxForwardedProto() { return xForwardedProto; } @@ -69,6 +72,7 @@ public void setxForwardedProto(final String xForwardedProto) { this.xForwardedProto = xForwardedProto; } + @Nullable public String getContentType() { return contentType; } @@ -85,6 +89,7 @@ public OverheadContext getOverheadContext() { return overheadContext; } + @Nonnull public TaintedObjects getTaintedObjects() { return taintedObjects; } @@ -97,22 +102,21 @@ public IastMetricCollector getMetricCollector() { @Nullable public static IastRequestContext get() { - return get(AgentTracer.activeSpan()); + return asRequestContext(IastContext.Provider.get()); } @Nullable public static IastRequestContext get(final AgentSpan span) { - if (span == null) { - return null; - } - return get(span.getRequestContext()); + return asRequestContext(IastContext.Provider.get(span)); } @Nullable public static IastRequestContext get(final RequestContext reqCtx) { - if (reqCtx == null) { - return null; - } - return reqCtx.getData(RequestContextSlot.IAST); + return asRequestContext(IastContext.Provider.get(reqCtx)); + } + + @Nullable + private static IastRequestContext asRequestContext(final IastContext ctx) { + return ctx instanceof IastRequestContext ? (IastRequestContext) ctx : null; } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java index b5c3e7131f4..5da60ff4d30 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java @@ -1,6 +1,5 @@ package com.datadog.iast; -import com.datadog.iast.HasDependencies.Dependencies; import com.datadog.iast.overhead.OverheadController; import com.datadog.iast.propagation.FastCodecModule; import com.datadog.iast.propagation.PropagationModuleImpl; @@ -15,6 +14,7 @@ import com.datadog.iast.sink.PathTraversalModuleImpl; import com.datadog.iast.sink.SqlInjectionModuleImpl; import com.datadog.iast.sink.SsrfModuleImpl; +import com.datadog.iast.sink.StacktraceLeakModuleImpl; import com.datadog.iast.sink.TrustBoundaryViolationModuleImpl; import com.datadog.iast.sink.UnvalidatedRedirectModuleImpl; import com.datadog.iast.sink.WeakCipherModuleImpl; @@ -23,7 +23,6 @@ import com.datadog.iast.sink.XContentTypeModuleImpl; import com.datadog.iast.sink.XPathInjectionModuleImpl; import com.datadog.iast.sink.XssModuleImpl; -import com.datadog.iast.source.WebModuleImpl; import com.datadog.iast.telemetry.TelemetryRequestEndedHandler; import com.datadog.iast.telemetry.TelemetryRequestStartedHandler; import datadog.trace.api.Config; @@ -41,9 +40,9 @@ import datadog.trace.util.AgentTaskScheduler; import datadog.trace.util.stacktrace.StackWalkerFactory; import java.util.function.BiFunction; -import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +55,8 @@ public static void start(final SubscriptionService ss) { start(ss, null); } - public static void start(final SubscriptionService ss, OverheadController overheadController) { + public static void start( + final SubscriptionService ss, @Nullable OverheadController overheadController) { final Config config = Config.get(); if (config.getIastActivation() != ProductActivation.FULLY_ENABLED) { LOGGER.debug("IAST is disabled"); @@ -71,7 +71,7 @@ public static void start(final SubscriptionService ss, OverheadController overhe final Dependencies dependencies = new Dependencies(config, reporter, overheadController, StackWalkerFactory.INSTANCE); final boolean addTelemetry = config.getIastTelemetryVerbosity() != Verbosity.OFF; - iastModules().forEach(registerModule(dependencies)); + iastModules(dependencies).forEach(InstrumentationBridge::registerIastModule); registerRequestStartedCallback(ss, addTelemetry, dependencies); registerRequestEndedCallback(ss, addTelemetry, dependencies); registerHeadersCallback(ss); @@ -79,39 +79,30 @@ public static void start(final SubscriptionService ss, OverheadController overhe LOGGER.debug("IAST started"); } - private static Consumer registerModule(final Dependencies dependencies) { - return module -> { - if (module instanceof HasDependencies) { - ((HasDependencies) module).registerDependencies(dependencies); - } - InstrumentationBridge.registerIastModule(module); - }; - } - - private static Stream iastModules() { + private static Stream iastModules(final Dependencies dependencies) { return Stream.of( - new WebModuleImpl(), new StringModuleImpl(), new FastCodecModule(), - new SqlInjectionModuleImpl(), - new PathTraversalModuleImpl(), - new CommandInjectionModuleImpl(), - new WeakCipherModuleImpl(), - new WeakHashModuleImpl(), - new LdapInjectionModuleImpl(), + new SqlInjectionModuleImpl(dependencies), + new PathTraversalModuleImpl(dependencies), + new CommandInjectionModuleImpl(dependencies), + new WeakCipherModuleImpl(dependencies), + new WeakHashModuleImpl(dependencies), + new LdapInjectionModuleImpl(dependencies), new PropagationModuleImpl(), - new HttpResponseHeaderModuleImpl(), - new HstsMissingHeaderModuleImpl(), + new HttpResponseHeaderModuleImpl(dependencies), + new HstsMissingHeaderModuleImpl(dependencies), new InsecureCookieModuleImpl(), new NoHttpOnlyCookieModuleImpl(), - new XContentTypeModuleImpl(), + new XContentTypeModuleImpl(dependencies), new NoSameSiteCookieModuleImpl(), - new SsrfModuleImpl(), - new UnvalidatedRedirectModuleImpl(), - new WeakRandomnessModuleImpl(), - new XPathInjectionModuleImpl(), - new TrustBoundaryViolationModuleImpl(), - new XssModuleImpl()); + new SsrfModuleImpl(dependencies), + new UnvalidatedRedirectModuleImpl(dependencies), + new WeakRandomnessModuleImpl(dependencies), + new XPathInjectionModuleImpl(dependencies), + new TrustBoundaryViolationModuleImpl(dependencies), + new XssModuleImpl(dependencies), + new StacktraceLeakModuleImpl(dependencies)); } private static void registerRequestStartedCallback( diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Reporter.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Reporter.java index e51cf258b38..bb4f3436a27 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Reporter.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/Reporter.java @@ -30,7 +30,7 @@ public class Reporter { this(Config.get(), null); } - public Reporter(final Config config, final AgentTaskScheduler taskScheduler) { + public Reporter(final Config config, @Nullable final AgentTaskScheduler taskScheduler) { this( config.isIastDeduplicationEnabled() ? new HashBasedDeduplication(taskScheduler) @@ -108,11 +108,11 @@ protected static class HashBasedDeduplication implements Predicate hashes; - public HashBasedDeduplication(final AgentTaskScheduler taskScheduler) { + public HashBasedDeduplication(@Nullable final AgentTaskScheduler taskScheduler) { this(DEFAULT_MAX_SIZE, taskScheduler); } - HashBasedDeduplication(final int size, final AgentTaskScheduler taskScheduler) { + HashBasedDeduplication(final int size, @Nullable final AgentTaskScheduler taskScheduler) { maxSize = size; hashes = ConcurrentHashMap.newKeySet(size); if (taskScheduler != null) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java index 25611c79643..fc2c9a18975 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java @@ -3,7 +3,6 @@ import static com.datadog.iast.IastTag.ANALYZED; import static com.datadog.iast.IastTag.SKIPPED; -import com.datadog.iast.HasDependencies.Dependencies; import com.datadog.iast.overhead.OverheadController; import com.datadog.iast.taint.TaintedObjects; import datadog.trace.api.gateway.Flow; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestStartedHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestStartedHandler.java index 9c680717743..4d5c851f52c 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestStartedHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestStartedHandler.java @@ -1,6 +1,5 @@ package com.datadog.iast; -import com.datadog.iast.HasDependencies.Dependencies; import com.datadog.iast.overhead.OverheadController; import datadog.trace.api.gateway.Flow; import java.util.function.Supplier; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Evidence.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Evidence.java index 65b2f78e374..c4dc17d27e5 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Evidence.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Evidence.java @@ -17,6 +17,8 @@ public final class Evidence { private final transient @Nonnull Context context = new Evidence.Context(4); /** For deserialization in tests via moshi */ + @Deprecated + @SuppressWarnings({"NullAway", "DataFlowIssue", "unused"}) private Evidence() { this(null, null); } @@ -25,15 +27,17 @@ public Evidence(final String value) { this(value, null); } - public Evidence(final String value, final Range[] ranges) { + public Evidence(@Nonnull final String value, @Nullable final Range[] ranges) { this.value = value; this.ranges = ranges; } + @Nonnull public String getValue() { return value; } + @Nullable public Range[] getRanges() { return ranges; } @@ -76,6 +80,7 @@ public boolean put(final String key, final Object value) { return true; } + @Nullable @SuppressWarnings("unchecked") public E get(@Nonnull final String key) { return (E) context.get(key); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Location.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Location.java index 2522699121b..1dbe22b545d 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Location.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Location.java @@ -1,25 +1,26 @@ package com.datadog.iast.model; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import javax.annotation.Nullable; public final class Location { - private final String path; + @Nullable private final String path; private final int line; - private final String method; + @Nullable private final String method; - private Long spanId; + @Nullable private Long spanId; - private transient String serviceName; + @Nullable private transient String serviceName; private Location( - final Long spanId, - final String path, + @Nullable final Long spanId, + @Nullable final String path, final int line, - final String method, - final String serviceName) { + @Nullable final String method, + @Nullable final String serviceName) { this.spanId = spanId; this.path = path; this.line = line; @@ -27,7 +28,8 @@ private Location( this.serviceName = serviceName; } - public static Location forSpanAndStack(final AgentSpan span, final StackTraceElement stack) { + public static Location forSpanAndStack( + @Nullable final AgentSpan span, final StackTraceElement stack) { return new Location( spanId(span), stack.getClassName(), @@ -54,6 +56,7 @@ public long getSpanId() { return spanId == null ? 0 : spanId; } + @Nullable public String getPath() { return path; } @@ -62,26 +65,30 @@ public int getLine() { return line; } + @Nullable public String getMethod() { return method; } + @Nullable public String getServiceName() { return serviceName; } - public void updateSpan(final AgentSpan span) { + public void updateSpan(@Nullable final AgentSpan span) { if (span != null) { this.spanId = span.getSpanId(); this.serviceName = span.getServiceName(); } } - private static Long spanId(AgentSpan span) { + @Nullable + private static Long spanId(@Nullable AgentSpan span) { return span != null ? span.getSpanId() : null; } - private static String serviceName(AgentSpan span) { + @Nullable + private static String serviceName(@Nullable AgentSpan span) { return span != null ? span.getServiceName() : null; } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Range.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Range.java index 97caac40bd3..a8e1d43b28a 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Range.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Range.java @@ -1,5 +1,7 @@ package com.datadog.iast.model; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; + import com.datadog.iast.model.json.SourceIndex; import com.datadog.iast.util.Ranged; import java.util.Objects; @@ -9,8 +11,6 @@ public final class Range implements Ranged { - public static final int NOT_MARKED = 0; - private final @Nonnegative int start; private final @Nonnegative int length; private final @Nonnull @SourceIndex Source source; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Source.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Source.java index 6dc5956adc7..31acd483a7a 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Source.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Source.java @@ -5,13 +5,19 @@ import datadog.trace.api.iast.Taintable; import java.util.Objects; import java.util.StringJoiner; +import javax.annotation.Nullable; public final class Source implements Taintable.Source { private final @SourceTypeString byte origin; - private final String name; - private final String value; + @Nullable private final String name; + @Nullable private final String value; - public Source(final byte origin, final String name, final String value) { + public Source( + final byte origin, @Nullable final CharSequence name, @Nullable final CharSequence value) { + this(origin, name == null ? null : name.toString(), value == null ? null : value.toString()); + } + + public Source(final byte origin, @Nullable final String name, @Nullable final String value) { this.origin = origin; this.name = name; this.value = value; @@ -23,11 +29,13 @@ public byte getOrigin() { } @Override + @Nullable public String getName() { return name; } @Override + @Nullable public String getValue() { return value; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Vulnerability.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Vulnerability.java index 1e119321ff1..97ecf815775 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Vulnerability.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/Vulnerability.java @@ -15,21 +15,26 @@ public final class Vulnerability { private long hash; public Vulnerability( - final VulnerabilityType type, final Location location, final Evidence evidence) { + @Nonnull final VulnerabilityType type, + @Nonnull final Location location, + @Nullable final Evidence evidence) { this.type = type; this.location = location; this.evidence = evidence; this.hash = type.calculateHash(this); } + @Nonnull public VulnerabilityType getType() { return type; } + @Nonnull public Location getLocation() { return location; } + @Nullable public Evidence getEvidence() { return evidence; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityBatch.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityBatch.java index 17f59fbdd7c..fa5193b0a00 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityBatch.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityBatch.java @@ -3,12 +3,13 @@ import com.datadog.iast.model.json.VulnerabilityEncoding; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; /** Collects vulnerabilities and serializes to JSON lazily on {@link #toString()}. */ public final class VulnerabilityBatch { - private List vulnerabilities; - private volatile String json; + @Nullable private List vulnerabilities; + @Nullable private volatile String json; public void add(final Vulnerability v) { synchronized (this) { @@ -21,6 +22,7 @@ public void add(final Vulnerability v) { } /** Internal list of vulnerabilities. Not thread-safe. */ + @Nullable public List getVulnerabilities() { return vulnerabilities; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java index deb7888adaf..62fdc6db56e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java @@ -1,10 +1,11 @@ package com.datadog.iast.model; -import static com.datadog.iast.model.Range.NOT_MARKED; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import datadog.trace.api.iast.VulnerabilityMarks; import datadog.trace.api.iast.VulnerabilityTypes; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.zip.CRC32; import javax.annotation.Nonnull; @@ -64,6 +65,9 @@ public interface VulnerabilityType { InjectionType XSS = new InjectionTypeImpl(VulnerabilityTypes.XSS_STRING, VulnerabilityMarks.XSS_MARK, ' '); + VulnerabilityType STACKTRACE_LEAK = + new VulnerabilityTypeImpl(VulnerabilityTypes.STACKTRACE_LEAK_STRING, NOT_MARKED); + String name(); /** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */ @@ -99,17 +103,24 @@ public int mark() { @Override public long calculateHash(@Nonnull final Vulnerability vulnerability) { CRC32 crc = new CRC32(); - crc.update(name().getBytes()); + update(crc, name()); final Location location = vulnerability.getLocation(); if (location != null) { crc.update(location.getLine()); - crc.update(location.getPath().getBytes()); + if (location.getPath() != null) { + update(crc, location.getPath()); + } if (location.getLine() <= -1 && location.getMethod() != null) { - crc.update(location.getMethod().getBytes()); + update(crc, location.getMethod()); } } return crc.getValue(); } + + protected void update(final CRC32 crc, final String value) { + final byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + crc.update(bytes, 0, bytes.length); + } } class InjectionTypeImpl extends VulnerabilityTypeImpl implements InjectionType { @@ -135,10 +146,10 @@ public HeaderVulnerabilityType(@Nonnull String name, int vulnerabilityMark) { @Override public long calculateHash(@Nonnull final Vulnerability vulnerability) { CRC32 crc = new CRC32(); - crc.update(name().getBytes()); + update(crc, name()); String serviceName = vulnerability.getLocation().getServiceName(); if (serviceName != null) { - crc.update(serviceName.getBytes()); + update(crc, serviceName); } return crc.getValue(); } @@ -152,10 +163,10 @@ public CookieVulnerabilityType(@Nonnull String name, int vulnerabilityMark) { @Override public long calculateHash(@Nonnull final Vulnerability vulnerability) { CRC32 crc = new CRC32(); - crc.update(name().getBytes()); + update(crc, name()); final Evidence evidence = vulnerability.getEvidence(); if (evidence != null) { - crc.update(evidence.getValue().getBytes()); + update(crc, evidence.getValue()); } return crc.getValue(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/AdapterFactory.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/AdapterFactory.java index 469c3b9227d..b62e74388ae 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/AdapterFactory.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/AdapterFactory.java @@ -37,7 +37,7 @@ static class Context { final List sources; final Map sourceIndexMap; final Map sourceContext; - Vulnerability vulnerability; + @Nullable Vulnerability vulnerability; public Context() { sources = new ArrayList<>(); @@ -183,7 +183,7 @@ public static class RedactionContext { private final Source source; private final boolean sensitive; private boolean sensitiveRanges; - private String redactedValue; + @Nullable private String redactedValue; public RedactionContext(final Source source) { this.source = source; @@ -206,6 +206,7 @@ public boolean shouldRedact() { return sensitive || sensitiveRanges; } + @Nullable public String getRedactedValue() { return redactedValue; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/EvidenceAdapter.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/EvidenceAdapter.java index f90f7d68a67..9c78f11ed81 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/EvidenceAdapter.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/EvidenceAdapter.java @@ -74,8 +74,12 @@ private String substring(final String value, final Ranged range) { private class DefaultEvidenceAdapter extends FormattingAdapter { @Override - public void toJson(@Nonnull final JsonWriter writer, final @Nonnull Evidence evidence) + public void toJson(@Nonnull final JsonWriter writer, final @Nullable Evidence evidence) throws IOException { + if (evidence == null) { + writer.nullValue(); + return; + } writer.beginObject(); if (evidence.getRanges() == null || evidence.getRanges().length == 0) { writer.name("value"); @@ -132,8 +136,12 @@ private void writeValuePart( private class RedactedEvidenceAdapter extends FormattingAdapter { @Override - public void toJson(@Nonnull final JsonWriter writer, @Nonnull final Evidence evidence) + public void toJson(@Nonnull final JsonWriter writer, @Nullable final Evidence evidence) throws IOException { + if (evidence == null) { + writer.nullValue(); + return; + } final Context ctx = Context.get(); final Vulnerability vulnerability = ctx.vulnerability; if (vulnerability == null) { @@ -163,7 +171,10 @@ private void toRedactedJson( writer.beginArray(); for (final Iterator it = new ValuePartIterator(ctx, value, tainted, sensitive); it.hasNext(); ) { - it.next().write(ctx, writer); + final ValuePart next = it.next(); + if (next != null) { + next.write(ctx, writer); + } } writer.endArray(); } @@ -205,6 +216,7 @@ public boolean hasNext() { return !next.isEmpty() || index < value.length(); } + @Nullable @Override public ValuePart next() { if (!hasNext()) { @@ -237,6 +249,7 @@ public ValuePart next() { return next.poll(); } + @Nullable private Ranged handleTaintedValue( @Nonnull final Range nextTainted, @Nullable Ranged nextSensitive) { final RedactionContext redactionCtx = ctx.getRedaction(nextTainted.getSource()); @@ -290,6 +303,7 @@ private void handleSensitiveValue(@Nonnull Ranged nextSensitive) { * Removes the tainted range from the sensitive one and returns whatever is before and enqueues * the rest */ + @Nullable private Ranged removeTaintedRange(final Ranged sensitive, final Range tainted) { final List disjointRanges = sensitive.remove(tainted); Ranged result = null; @@ -303,6 +317,7 @@ private Ranged removeTaintedRange(final Ranged sensitive, final Range tainted) { return result; } + @Nullable private ValuePart nextStringValuePart(final int end) { if (index < end) { final String chunk = value.substring(index, end); @@ -325,9 +340,10 @@ interface ValuePart { } static class StringValuePart implements ValuePart { - private final String value; - private StringValuePart(final String value) { + @Nullable private final String value; + + private StringValuePart(@Nullable final String value) { this.value = value; } @@ -443,11 +459,13 @@ private void addValuePart( valueParts.add(new TaintedValuePart(adapter, source, chunk, false)); } else { final int length = chunk.length(); - final int matching = source.getValue().indexOf(chunk); + final String sourceValue = source.getValue(); + final String redactedValue = ctx.getRedactedValue(); + final int matching = (sourceValue == null) ? -1 : sourceValue.indexOf(chunk); final String pattern; - if (matching >= 0) { + if (matching >= 0 && redactedValue != null) { // if matches append the matching part from the redacted value - pattern = ctx.getRedactedValue().substring(matching, matching + length); + pattern = redactedValue.substring(matching, matching + length); } else { // otherwise redact the string pattern = SensitiveHandler.get().redactString(chunk); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/SourceAdapter.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/SourceAdapter.java index 9da5177c6ef..7f5ab7edc1f 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/SourceAdapter.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/SourceAdapter.java @@ -66,7 +66,8 @@ public void toJson(@Nonnull final JsonWriter writer, final @Nonnull Source sourc } } - private void toRedactedJson(final JsonWriter writer, final Source source, final String value) + private void toRedactedJson( + final JsonWriter writer, final Source source, @Nullable final String value) throws IOException { writer.beginObject(); writer.name("origin"); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/TruncatedVulnerabilities.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/TruncatedVulnerabilities.java index 2212f1b9963..fe767384ef2 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/TruncatedVulnerabilities.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/TruncatedVulnerabilities.java @@ -2,15 +2,17 @@ import com.datadog.iast.model.Vulnerability; import java.util.List; +import javax.annotation.Nullable; public class TruncatedVulnerabilities { - private final List vulnerabilities; + @Nullable private final List vulnerabilities; - public TruncatedVulnerabilities(final List vulnerabilities) { + public TruncatedVulnerabilities(@Nullable final List vulnerabilities) { this.vulnerabilities = vulnerabilities; } + @Nullable public List getVulnerabilities() { return vulnerabilities; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/VulnerabilityEncoding.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/VulnerabilityEncoding.java index 455e1334b70..59c6c1c5b62 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/VulnerabilityEncoding.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/VulnerabilityEncoding.java @@ -3,9 +3,12 @@ import com.datadog.iast.model.VulnerabilityBatch; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class VulnerabilityEncoding { + private static final Logger log = LoggerFactory.getLogger(VulnerabilityEncoding.class); private static final int MAX_SPAN_TAG_SIZE = 25000; static final Moshi MOSHI = @@ -18,10 +21,15 @@ public class VulnerabilityEncoding { MOSHI.adapter(TruncatedVulnerabilities.class); public static String toJson(final VulnerabilityBatch value) { - String json = BATCH_ADAPTER.toJson(value); - return json.getBytes().length > MAX_SPAN_TAG_SIZE - ? getExceededTagSizeJson(new TruncatedVulnerabilities(value.getVulnerabilities())) - : json; + try { + String json = BATCH_ADAPTER.toJson(value); + return json.getBytes().length > MAX_SPAN_TAG_SIZE + ? getExceededTagSizeJson(new TruncatedVulnerabilities(value.getVulnerabilities())) + : json; + } catch (Exception ex) { + log.debug("Vulnerability serialization error", ex); + return "{\"vulnerabilities\":[]}"; + } } static String getExceededTagSizeJson(final TruncatedVulnerabilities truncatedVulnerabilities) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operation.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operation.java index f0d75012887..c21cec9d9c7 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operation.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operation.java @@ -1,7 +1,9 @@ package com.datadog.iast.overhead; +import javax.annotation.Nullable; + public interface Operation { - boolean hasQuota(final OverheadContext context); + boolean hasQuota(@Nullable final OverheadContext context); - boolean consumeQuota(final OverheadContext context); + boolean consumeQuota(@Nullable final OverheadContext context); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operations.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operations.java index c9eab7b7237..e7bb35f1550 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operations.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/Operations.java @@ -1,5 +1,7 @@ package com.datadog.iast.overhead; +import javax.annotation.Nullable; + public class Operations { private Operations() {} @@ -7,7 +9,7 @@ private Operations() {} public static final Operation REPORT_VULNERABILITY = new Operation() { @Override - public boolean hasQuota(final OverheadContext context) { + public boolean hasQuota(@Nullable final OverheadContext context) { if (context == null) { return false; } @@ -15,7 +17,7 @@ public boolean hasQuota(final OverheadContext context) { } @Override - public boolean consumeQuota(final OverheadContext context) { + public boolean consumeQuota(@Nullable final OverheadContext context) { if (context == null) { return false; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java index ee03973a11e..44f62063030 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java @@ -13,6 +13,7 @@ import datadog.trace.util.AgentTaskScheduler; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +25,9 @@ public interface OverheadController { int releaseRequest(); - boolean hasQuota(final Operation operation, final AgentSpan span); + boolean hasQuota(final Operation operation, @Nullable final AgentSpan span); - boolean consumeQuota(final Operation operation, final AgentSpan span); + boolean consumeQuota(final Operation operation, @Nullable final AgentSpan span); static OverheadController build(final Config config, final AgentTaskScheduler scheduler) { final OverheadControllerImpl result = new OverheadControllerImpl(config, scheduler); @@ -68,7 +69,7 @@ public int releaseRequest() { } @Override - public boolean hasQuota(final Operation operation, final AgentSpan span) { + public boolean hasQuota(final Operation operation, @Nullable final AgentSpan span) { final boolean result = delegate.hasQuota(operation, span); if (LOGGER.isDebugEnabled()) { LOGGER.debug( @@ -82,7 +83,7 @@ public boolean hasQuota(final Operation operation, final AgentSpan span) { } @Override - public boolean consumeQuota(final Operation operation, final AgentSpan span) { + public boolean consumeQuota(final Operation operation, @Nullable final AgentSpan span) { final boolean result = delegate.consumeQuota(operation, span); if (LOGGER.isDebugEnabled()) { LOGGER.debug( @@ -103,7 +104,7 @@ public void reset() { } } - private int getAvailableQuote(final AgentSpan span) { + private int getAvailableQuote(@Nullable final AgentSpan span) { final OverheadContext context = delegate.getContext(span); return context == null ? -1 : context.getAvailableQuota(); } @@ -149,16 +150,17 @@ public int releaseRequest() { } @Override - public boolean hasQuota(final Operation operation, final AgentSpan span) { + public boolean hasQuota(final Operation operation, @Nullable final AgentSpan span) { return operation.hasQuota(getContext(span)); } @Override - public boolean consumeQuota(final Operation operation, final AgentSpan span) { + public boolean consumeQuota(final Operation operation, @Nullable final AgentSpan span) { return operation.consumeQuota(getContext(span)); } - public OverheadContext getContext(final AgentSpan span) { + @Nullable + public OverheadContext getContext(@Nullable final AgentSpan span) { final RequestContext requestContext = span != null ? span.getRequestContext() : null; if (requestContext != null) { IastRequestContext iastRequestContext = requestContext.getData(RequestContextSlot.IAST); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java index cae08236901..dd5ec368a06 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/PropagationModuleImpl.java @@ -1,9 +1,8 @@ package com.datadog.iast.propagation; -import static com.datadog.iast.model.Range.NOT_MARKED; import static com.datadog.iast.taint.Ranges.highestPriorityRange; -import static com.datadog.iast.taint.Tainteds.canBeTainted; import static com.datadog.iast.util.ObjectVisitor.State.CONTINUE; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; @@ -11,349 +10,505 @@ import com.datadog.iast.taint.Ranges; import com.datadog.iast.taint.TaintedObject; import com.datadog.iast.taint.TaintedObjects; +import com.datadog.iast.taint.Tainteds; import com.datadog.iast.util.ObjectVisitor; -import com.datadog.iast.util.ObjectVisitor.State; -import com.datadog.iast.util.ObjectVisitor.Visitor; -import datadog.trace.api.iast.SourceTypes; +import datadog.trace.api.Config; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.Taintable; import datadog.trace.api.iast.propagation.PropagationModule; -import java.util.Collection; -import java.util.List; -import java.util.Map; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.jetbrains.annotations.Contract; public class PropagationModuleImpl implements PropagationModule { + /** Prevent copy of values bigger than this threshold */ + private static final int MAX_VALUE_LENGTH = Config.get().getIastTruncationMaxValueLength(); + @Override - public void taintIfInputIsTainted(@Nullable final Object toTaint, @Nullable final Object input) { - if (toTaint == null || input == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Source source = highestPriorityTaintedSource(taintedObjects, input); - if (source != null) { - taintObject(taintedObjects, toTaint, source); - } + public void taint(@Nullable final Object target, final byte origin) { + taint(target, origin, null); } @Override - public void taintIfInputIsTainted(@Nullable final String toTaint, @Nullable final Object input) { - if (!canBeTainted(toTaint) || input == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Source source = highestPriorityTaintedSource(taintedObjects, input); - if (source != null) { - taintString(taintedObjects, toTaint, source); - } + public void taint( + @Nullable final Object target, final byte origin, @Nullable final CharSequence name) { + taint(target, origin, name, sourceValue(target)); } @Override - public void taintIfInputIsTainted( + public void taint( + @Nullable final Object target, final byte origin, - @Nullable final String name, - @Nullable final String toTaint, - @Nullable final Object input) { - if (!canBeTainted(toTaint) || input == null) { + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + if (!canBeTainted(target)) { return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - if (isTainted(taintedObjects, input)) { - taintString(taintedObjects, toTaint, new Source(origin, name, toTaint)); - } + taint(LazyContext.build(), target, origin, name, value); } @Override - public void taintIfInputIsTainted( - final byte origin, - @Nullable final String name, - @Nullable final Collection toTaintCollection, - @Nullable final Object input) { - if (toTaintCollection == null || toTaintCollection.isEmpty() || input == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - if (isTainted(taintedObjects, input)) { - for (final String toTaint : toTaintCollection) { - if (canBeTainted(toTaint)) { - taintString(taintedObjects, toTaint, new Source(origin, name, toTaint)); - } - } - } + public void taint( + @Nullable final IastContext ctx, @Nullable final Object target, final byte origin) { + taint(ctx, target, origin, null); } @Override - public void taintIfInputIsTainted( + public void taint( + @Nullable final IastContext ctx, + @Nullable final Object target, final byte origin, - @Nullable final Collection toTaintCollection, - @Nullable final Object input) { - if (toTaintCollection == null || toTaintCollection.isEmpty() || input == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - if (isTainted(taintedObjects, input)) { - for (final String toTaint : toTaintCollection) { - if (canBeTainted(toTaint)) { - taintString(taintedObjects, toTaint, new Source(origin, toTaint, toTaint)); - } - } - } + @Nullable final CharSequence name) { + taint(ctx, target, origin, name, sourceValue(target)); } @Override - public void taintIfInputIsTainted( + public void taint( + @Nullable final IastContext ctx, + @Nullable final Object target, final byte origin, - @Nullable final List> toTaintCollection, - @Nullable final Object input) { - if (toTaintCollection == null || toTaintCollection.isEmpty() || input == null) { + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + if (!canBeTainted(target)) { return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - if (isTainted(taintedObjects, input)) { - for (final Map.Entry entry : toTaintCollection) { - final String name = entry.getKey(); - if (canBeTainted(name)) { - taintString( - taintedObjects, name, new Source(SourceTypes.namedSource(origin), name, name)); - } - final String toTaint = entry.getValue(); - if (canBeTainted(toTaint)) { - taintString(taintedObjects, toTaint, new Source(origin, name, toTaint)); - } - } - } + internalTaint(ctx, target, newSource(target, origin, name, value), NOT_MARKED); } @Override - public void taintIfAnyInputIsTainted( - @Nullable final Object toTaint, @Nullable final Object... inputs) { - if (toTaint == null || inputs == null || inputs.length == 0) { + public void taintIfTainted(@Nullable final Object target, @Nullable final Object input) { + taintIfTainted(target, input, false, NOT_MARKED); + } + + @Override + public void taintIfTainted( + @Nullable final Object target, @Nullable final Object input, boolean keepRanges, int mark) { + if (!canBeTainted(target) || !canBeTainted(input)) { return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - for (final Object input : inputs) { - final Source source = highestPriorityTaintedSource(taintedObjects, input); - if (source != null) { - taintObject(taintedObjects, toTaint, source); - return; - } - } + taintIfTainted(LazyContext.build(), target, input, keepRanges, mark); } @Override - public void taint(final byte source, @Nullable final String name, @Nullable final String value) { - if (!canBeTainted(value)) { + public void taintIfTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object input) { + taintIfTainted(ctx, target, input, false, NOT_MARKED); + } + + @Override + public void taintIfTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object input, + boolean keepRanges, + int mark) { + if (!canBeTainted(target) || !canBeTainted(input)) { return; } - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx == null) { - return; + if (keepRanges) { + internalTaint(ctx, target, getRanges(ctx, input), mark); + } else { + internalTaint(ctx, target, highestPrioritySource(ctx, input), mark); } - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - taintedObjects.taintInputString(value, new Source(source, name, value)); } @Override - public void taint( - @Nullable final Object ctx_, - final byte source, - @Nullable final String name, - @Nullable final String value) { - if (ctx_ == null || !canBeTainted(value)) { - return; - } - final IastRequestContext ctx = (IastRequestContext) ctx_; - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - taintedObjects.taintInputString(value, new Source(source, name, value)); + public void taintIfTainted( + @Nullable final Object target, @Nullable final Object input, final byte origin) { + taintIfTainted(target, input, origin, null); } @Override - public void taintDeeply(@Nullable final Object ctx_, final byte source, @Nonnull final Object o) { - taintDeeply(ctx_, source, o, ObjectVisitor::inspectClass); + public void taintIfTainted( + @Nullable final Object target, + @Nullable final Object input, + final byte origin, + @Nullable final CharSequence name) { + taintIfTainted(target, input, origin, name, sourceValue(target)); } @Override - public void taintDeeply( - @Nullable final Object ctx_, - final byte source, - @Nonnull final Object o, - @Nonnull final Predicate> classFilter) { - if (ctx_ == null) { + public void taintIfTainted( + @Nullable final Object target, + @Nullable final Object input, + final byte origin, + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + if (!canBeTainted(target) || !canBeTainted(input)) { return; } - final IastRequestContext ctx = (IastRequestContext) ctx_; - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - ObjectVisitor.visit(o, new TaintingVisitor(taintedObjects, source), classFilter); + taintIfTainted(LazyContext.build(), target, input, origin, name, value); } @Override - public void taintObjectIfInputIsTaintedKeepingRanges( - @Nullable final Object toTaint, @Nullable Object input) { - if (toTaint == null || input == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Range[] ranges = getTaintedRanges(taintedObjects, input); - if (ranges != null && ranges.length > 0) { - taintedObjects.taint(toTaint, ranges); - } + public void taintIfTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object input, + final byte origin) { + taintIfTainted(ctx, target, input, origin, null); } @Override - public void taintObject(final byte origin, @Nullable final Object toTaint) { - if (toTaint == null) { - return; - } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(false); - if (taintedObjects == null) { - return; - } - final Source source = new Source(origin, null, null); - taintObject(taintedObjects, toTaint, source); + public void taintIfTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object input, + final byte origin, + @Nullable final CharSequence name) { + taintIfTainted(ctx, target, input, origin, name, sourceValue(target)); } @Override - public void taintObjects(final byte origin, @Nullable final Object[] toTaintArray) { - if (toTaintArray == null || toTaintArray.length == 0) { + public void taintIfTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object input, + final byte origin, + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + if (!canBeTainted(target) || !canBeTainted(input)) { return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Source source = new Source(origin, null, null); - for (final Object toTaint : toTaintArray) { - taintObject(taintedObjects, toTaint, source); + if (isTainted(ctx, input)) { + internalTaint(ctx, target, newSource(target, origin, name, value), NOT_MARKED); } } @Override - public boolean isTainted(@Nullable Object obj) { - if (obj instanceof Taintable) { - return ((Taintable) obj).$DD$isTainted(); - } + public void taintIfAnyTainted(@Nullable final Object target, @Nullable final Object[] inputs) { + taintIfAnyTainted(target, inputs, false, NOT_MARKED); + } - if (obj == null) { - return false; + @Override + public void taintIfAnyTainted( + @Nullable final Object target, + @Nullable final Object[] inputs, + final boolean keepRanges, + final int mark) { + if (!canBeTainted(target) || !canBeTainted(inputs)) { + return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - return taintedObjects.get(obj) != null; + taintIfAnyTainted(LazyContext.build(), target, inputs, keepRanges, mark); } @Override - public void taintObjects( - final byte origin, @Nullable final Collection toTaintCollection) { - if (toTaintCollection == null || toTaintCollection.isEmpty()) { + public void taintIfAnyTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object[] inputs) { + taintIfAnyTainted(ctx, target, inputs, false, NOT_MARKED); + } + + @Override + public void taintIfAnyTainted( + @Nullable final IastContext ctx, + @Nullable final Object target, + @Nullable final Object[] inputs, + final boolean keepRanges, + final int mark) { + if (!canBeTainted(target) || !canBeTainted(inputs)) { return; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Source source = new Source(origin, null, null); - for (final Object toTaint : toTaintCollection) { - taintObject(taintedObjects, toTaint, source); + if (keepRanges) { + final Range[] ranges = getRangesInArray(ctx, inputs); + if (ranges != null) { + internalTaint(ctx, target, ranges, mark); + } + } else { + final Source source = highestPrioritySourceInArray(ctx, inputs); + if (source != null) { + internalTaint(ctx, target, source, mark); + } } } @Override - public void taintObject( - byte origin, @Nullable String name, @Nullable String value, @Nullable Object t) { - if (t == null) { - return; + public int taintDeeply( + @Nullable final Object target, final byte origin, final Predicate> classFilter) { + if (!canBeTainted(target)) { + return 0; + } + return taintDeeply(LazyContext.build(), target, origin, classFilter); + } + + @Override + public int taintDeeply( + @Nullable final IastContext ctx, + @Nullable final Object target, + final byte origin, + final Predicate> classFilter) { + if (!canBeTainted(target)) { + return 0; + } + final TaintedObjects to = getTaintedObjects(ctx); + if (to == null) { + return 0; } - if (t instanceof Taintable) { - ((Taintable) t).$$DD$setSource(new Source(origin, name, value)); + if (target instanceof CharSequence) { + internalTaint(ctx, target, newSource(target, origin, null, sourceValue(target)), NOT_MARKED); + return 1; } else { - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(); - taintObject(taintedObjects, t, new Source(origin, name, value)); + final TaintingVisitor visitor = new TaintingVisitor(to, origin); + ObjectVisitor.visit(target, visitor, classFilter); + return visitor.getCount(); } } + @Nullable @Override - public Taintable.Source firstTaintedSource(@Nullable final Object input) { - if (input == null) { + public Taintable.Source findSource(@Nullable final Object target) { + return target == null ? null : findSource(LazyContext.build(), target); + } + + @Nullable + @Override + public Taintable.Source findSource( + @Nullable final IastContext ctx, @Nullable final Object target) { + if (target == null) { return null; } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - return highestPriorityTaintedSource(taintedObjects, input); + return highestPrioritySource(ctx, target); } @Override - public void taintIfInputIsTaintedWithMarks( - @Nullable final String toTaint, @Nullable final Object input, final int mark) { - if (!canBeTainted(toTaint) || input == null) { - return; + public boolean isTainted(@Nullable final Object target) { + return target != null && isTainted(LazyContext.build(), target); + } + + @Override + public boolean isTainted(@Nullable final IastContext ctx, @Nullable final Object target) { + return target != null && findSource(ctx, target) != null; + } + + /** Ensures that the reference is not kept strongly via the name or value properties */ + private static Source newSource( + @Nonnull final Object reference, + final byte origin, + @Nullable final CharSequence name, + @Nullable final CharSequence value) { + return new Source( + origin, + reference == name ? sourceValue(name) : name, + reference == value ? sourceValue(value) : value); + } + + /** + * This method will prevent the code from creating a strong reference to what should remain weak + */ + @Nullable + private static CharSequence sourceValue(@Nullable final Object target) { + if (target instanceof String) { + final String string = (String) target; + if (MAX_VALUE_LENGTH > string.length()) { + return String.copyValueOf(string.toCharArray()); + } else { + final char[] chars = new char[MAX_VALUE_LENGTH]; + string.getChars(0, MAX_VALUE_LENGTH, chars, 0); + return String.copyValueOf(chars); + } + } else if (target instanceof CharSequence) { + final CharSequence charSequence = (CharSequence) target; + if (MAX_VALUE_LENGTH > charSequence.length()) { + return charSequence.toString(); + } else { + final CharSequence subSequence = charSequence.subSequence(0, MAX_VALUE_LENGTH); + return subSequence.toString(); + } } - final TaintedObjects taintedObjects = TaintedObjects.activeTaintedObjects(true); - final Range[] ranges = getTaintedRanges(taintedObjects, input); - if (ranges != null && ranges.length > 0) { - Range priorityRange = highestPriorityRange(ranges); - taintedObjects.taintInputString( - toTaint, priorityRange.getSource(), priorityRange.getMarks() | mark); + return null; + } + + @Contract("null -> false") + private static boolean canBeTainted(@Nullable final Object target) { + if (target == null) { + return false; } + if (target instanceof CharSequence) { + return Tainteds.canBeTainted((CharSequence) target); + } + return true; } - private static void taintString( - final TaintedObjects taintedObjects, final String toTaint, final Source source) { - taintedObjects.taintInputString(toTaint, source); + @Contract("null -> false") + private static boolean canBeTainted(@Nullable final Object[] target) { + if (target == null || target.length == 0) { + return false; + } + return true; } - private static void taintObject( - final TaintedObjects taintedObjects, final Object toTaint, final Source source) { - if (toTaint instanceof Taintable) { - ((Taintable) toTaint).$$DD$setSource(source); - } else { - taintedObjects.taintInputObject(toTaint, source); + @Nullable + private static TaintedObjects getTaintedObjects(final @Nullable IastContext ctx) { + IastRequestContext iastCtx = null; + if (ctx instanceof IastRequestContext) { + iastCtx = (IastRequestContext) ctx; + } else if (ctx instanceof LazyContext) { + iastCtx = ((LazyContext) ctx).getDelegate(); } + return iastCtx == null ? null : iastCtx.getTaintedObjects(); } - private static boolean isTainted(final TaintedObjects taintedObjects, final Object object) { - return highestPriorityTaintedSource(taintedObjects, object) != null; + @Nullable + private static Range[] getRangesInArray( + final @Nullable IastContext ctx, final @Nonnull Object[] objects) { + for (final Object object : objects) { + final Range[] ranges = getRanges(ctx, object); + if (ranges != null) { + return ranges; + } + } + return null; } - private static Source highestPriorityTaintedSource( - final TaintedObjects taintedObjects, final Object object) { + @Nullable + private static Range[] getRanges(final @Nullable IastContext ctx, final @Nonnull Object object) { + if (object instanceof Taintable) { + final Source source = highestPrioritySource(ctx, object); + if (source == null) { + return null; + } else { + return new Range[] {new Range(0, Integer.MAX_VALUE, source, NOT_MARKED)}; + } + } + final TaintedObjects to = getTaintedObjects(ctx); + if (to == null) { + return null; + } + final TaintedObject tainted = to.get(object); + return tainted == null ? null : tainted.getRanges(); + } + + @Nullable + private static Source highestPrioritySourceInArray( + final @Nullable IastContext ctx, final @Nonnull Object[] objects) { + for (final Object object : objects) { + final Source source = highestPrioritySource(ctx, object); + if (source != null) { + return source; + } + } + return null; + } + + @Nullable + private static Source highestPrioritySource( + final @Nullable IastContext ctx, final @Nonnull Object object) { if (object instanceof Taintable) { return (Source) ((Taintable) object).$$DD$getSource(); } else { - final TaintedObject tainted = taintedObjects.get(object); - final Range[] ranges = tainted == null ? null : tainted.getRanges(); + final Range[] ranges = getRanges(ctx, object); return ranges != null && ranges.length > 0 ? highestPriorityRange(ranges).getSource() : null; } } - private static Range[] getTaintedRanges( - final TaintedObjects taintedObjects, final Object object) { - if (object instanceof Taintable) { - Source source = (Source) ((Taintable) object).$$DD$getSource(); - if (source == null) { - return null; + private static void internalTaint( + @Nullable final IastContext ctx, + @Nonnull final Object value, + @Nullable final Source source, + int mark) { + if (source == null) { + return; + } + if (value instanceof Taintable) { + ((Taintable) value).$$DD$setSource(source); + } else { + final TaintedObjects to = getTaintedObjects(ctx); + if (to == null) { + return; + } + if (value instanceof CharSequence) { + to.taint(value, Ranges.forCharSequence((CharSequence) value, source, mark)); } else { - return Ranges.forObject(source, NOT_MARKED); + to.taint(value, Ranges.forObject(source, mark)); } + } + } + + private static void internalTaint( + @Nullable final IastContext ctx, + @Nonnull final Object value, + @Nullable final Range[] ranges, + final int mark) { + if (ranges == null || ranges.length == 0) { + return; + } + if (value instanceof Taintable) { + ((Taintable) value).$$DD$setSource(ranges[0].getSource()); } else { - final TaintedObject tainted = taintedObjects.get(object); - return tainted == null ? null : tainted.getRanges(); + final TaintedObjects to = getTaintedObjects(ctx); + if (to != null) { + final Range[] markedRanges = markRanges(ranges, mark); + to.taint(value, markedRanges); + } + } + } + + @Nonnull + private static Range[] markRanges(@Nonnull final Range[] ranges, final int mark) { + if (mark == NOT_MARKED) { + return ranges; + } + final Range[] result = new Range[ranges.length]; + for (int i = 0; i < ranges.length; i++) { + final Range range = ranges[i]; + final int newMark = range.getMarks() | mark; + result[i] = new Range(range.getStart(), range.getLength(), range.getSource(), newMark); + } + return result; + } + + private static class LazyContext implements IastContext { + + private boolean fetched; + @Nullable private IastRequestContext delegate; + + @Nullable + private IastRequestContext getDelegate() { + if (!fetched) { + fetched = true; + delegate = IastRequestContext.get(); + } + return delegate; + } + + public static IastContext build() { + return new LazyContext(); } } - private static class TaintingVisitor implements Visitor { + private static class TaintingVisitor implements ObjectVisitor.Visitor { private final TaintedObjects taintedObjects; - private final byte source; + private final byte origin; + private int count; - private TaintingVisitor(final TaintedObjects taintedObjects, final byte source) { + private TaintingVisitor(@Nonnull final TaintedObjects taintedObjects, final byte origin) { this.taintedObjects = taintedObjects; - this.source = source; + this.origin = origin; } @Nonnull @Override - public State visit(@Nonnull final String path, @Nonnull final Object value) { - if (value instanceof String) { - final String stringValue = (String) value; - if (canBeTainted(stringValue)) { - taintedObjects.taintInputString(stringValue, new Source(source, path, stringValue)); + public ObjectVisitor.State visit(@Nonnull final String path, @Nonnull final Object value) { + if (value instanceof CharSequence) { + final CharSequence charSequence = (CharSequence) value; + if (canBeTainted(charSequence)) { + final Source source = newSource(value, origin, path, charSequence); + count++; + taintedObjects.taint( + charSequence, Ranges.forCharSequence(charSequence, source, NOT_MARKED)); } } return CONTINUE; } + + public int getCount() { + return count; + } } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/StringModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/StringModuleImpl.java index 7a3f7720112..a93d3c53e8e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/StringModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/propagation/StringModuleImpl.java @@ -39,6 +39,7 @@ public class StringModuleImpl implements StringModule { private static final int NULL_STR_LENGTH = "null".length(); + @SuppressWarnings("NullAway") // NullAway fails with taintedLeft and taintedRight checks @Override public void onStringConcat( @Nonnull final String left, @Nullable final String right, @Nonnull final String result) { @@ -170,7 +171,7 @@ public void onStringConcatFactory( int offset = 0, rangeIndex = 0; for (int item : recipeOffsets) { if (item < 0) { - offset += (-item); + offset += -item; } else { final String argument = args[item]; final Range[] ranges = sourceRanges.get(item); @@ -214,7 +215,7 @@ public void onStringSubSequence( @Override public void onStringJoin( - @Nullable String result, @Nonnull CharSequence delimiter, @Nonnull CharSequence... elements) { + @Nullable String result, @Nonnull CharSequence delimiter, @Nonnull CharSequence[] elements) { if (!canBeTainted(result)) { return; } @@ -262,7 +263,7 @@ public void onStringJoin( @Override @SuppressFBWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") - public void onStringRepeat(String self, int count, String result) { + public void onStringRepeat(@Nonnull String self, int count, @Nonnull String result) { if (!canBeTainted(self) || !canBeTainted(result) || self == result) { return; } @@ -368,7 +369,7 @@ private static int insertRange( return rangeIndex + count; } - private static Range[] getRanges(final TaintedObject taintedObject) { + private static Range[] getRanges(@Nullable final TaintedObject taintedObject) { return taintedObject == null ? EMPTY : taintedObject.getRanges(); } @@ -572,11 +573,11 @@ public void onSplit(@Nonnull String self, @Nonnull String[] result) { * @param finalRanges result with all ranges */ private void addParameterTaintedRanges( - final Range placeholderRange, + @Nullable final Range placeholderRange, final Object param, final String formatted, final int offset, - final Range[] ranges, + @Nullable final Range[] ranges, /* out */ final RangeList finalRanges) { if (ranges != null && ranges.length > 0) { // only shift ranges if they are character sequences of the same length, otherwise taint the @@ -602,6 +603,7 @@ private void addParameterTaintedRanges( * @param finalRanges result with all ranges * @return tainted range of the placeholder or {@code null} if not tainted */ + @Nullable private Range addFormatTaintedRanges( final Ranged placeholderPos, final int offset, diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/AbstractRegexTokenizer.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/AbstractRegexTokenizer.java index 58a1e8aaec3..54c73669be3 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/AbstractRegexTokenizer.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/AbstractRegexTokenizer.java @@ -4,11 +4,12 @@ import java.util.NoSuchElementException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; public abstract class AbstractRegexTokenizer implements SensitiveHandler.Tokenizer { protected final Matcher matcher; - private Ranged current; + @Nullable private Ranged current; protected AbstractRegexTokenizer(final Pattern pattern, final String evidence) { matcher = pattern.matcher(evidence); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SensitiveHandlerImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SensitiveHandlerImpl.java index ffa8dbe3346..d62fdf8c95c 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SensitiveHandlerImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SensitiveHandlerImpl.java @@ -76,7 +76,7 @@ public Tokenizer tokenizeEvidence( return supplier.tokenizerFor(evidence); } - private int computeLength(final String value) { + private int computeLength(@Nullable final String value) { if (value == null || value.isEmpty()) { return 0; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SqlRegexpTokenizer.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SqlRegexpTokenizer.java index 9082979e890..6c87aaf7a87 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SqlRegexpTokenizer.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/SqlRegexpTokenizer.java @@ -120,10 +120,6 @@ public static Dialect fromEvidence(final Evidence evidence) { return ANSI; } - public static Dialect current() { - return ANSI; - } - public Pattern buildPattern() { return pattern.get(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/TaintedRangeBasedTokenizer.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/TaintedRangeBasedTokenizer.java index b0f08bb9afb..b2cff3356b4 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/TaintedRangeBasedTokenizer.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sensitive/TaintedRangeBasedTokenizer.java @@ -2,22 +2,24 @@ import com.datadog.iast.model.Evidence; import com.datadog.iast.model.Range; +import com.datadog.iast.taint.Ranges; import com.datadog.iast.util.Ranged; import java.util.NoSuchElementException; +import javax.annotation.Nullable; public class TaintedRangeBasedTokenizer implements SensitiveHandler.Tokenizer { private final String value; private final Range[] ranges; - private Ranged current; + @Nullable private Ranged current; private int rangesIndex; private int pos; public TaintedRangeBasedTokenizer(final Evidence evidence) { - this.ranges = evidence.getRanges(); + this.ranges = evidence.getRanges() == null ? Ranges.EMPTY : evidence.getRanges(); this.value = evidence.getValue(); rangesIndex = 0; pos = 0; // current value position @@ -37,6 +39,7 @@ public Ranged current() { return current; } + @Nullable private Ranged buildNext() { for (; rangesIndex < ranges.length; rangesIndex++) { Range range = ranges[rangesIndex]; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/CommandInjectionModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/CommandInjectionModuleImpl.java index ea5fa289e6d..24fd88c99c4 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/CommandInjectionModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/CommandInjectionModuleImpl.java @@ -3,6 +3,7 @@ import static com.datadog.iast.taint.Ranges.rangesProviderFor; import static com.datadog.iast.taint.Tainteds.canBeTainted; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.taint.TaintedObjects; @@ -15,6 +16,10 @@ public class CommandInjectionModuleImpl extends SinkModuleBase implements CommandInjectionModule { + public CommandInjectionModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onRuntimeExec(@Nullable final String... cmdArray) { if (!canBeTainted(cmdArray)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HstsMissingHeaderModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HstsMissingHeaderModuleImpl.java index f096f5f46ce..801d7e51052 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HstsMissingHeaderModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HstsMissingHeaderModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Location; import com.datadog.iast.model.Vulnerability; @@ -23,6 +24,10 @@ public class HstsMissingHeaderModuleImpl extends SinkModuleBase implements HstsM private static final Logger LOGGER = LoggerFactory.getLogger(HstsMissingHeaderModuleImpl.class); + public HstsMissingHeaderModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onRequestEnd(final Object iastRequestContextObject, final IGSpanInfo igSpanInfo) { @@ -70,7 +75,7 @@ static boolean isHttps(@Nullable final String urlString, @Nullable final String if (urlString == null) { return false; } - if (urlString.toLowerCase().startsWith("https://")) { + if (urlString.toLowerCase(Locale.ROOT).startsWith("https://")) { return true; } if (forwardedFor == null) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HttpResponseHeaderModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HttpResponseHeaderModuleImpl.java index 3216bff3d60..9eea7be16c5 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HttpResponseHeaderModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/HttpResponseHeaderModuleImpl.java @@ -3,6 +3,7 @@ import static com.datadog.iast.util.HttpHeader.Values.SET_COOKIE; import static java.util.Collections.singletonList; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.Location; @@ -27,6 +28,10 @@ public class HttpResponseHeaderModuleImpl extends SinkModuleBase implements HttpResponseHeaderModule { + public HttpResponseHeaderModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onHeader(@Nonnull final String name, final String value) { final HttpHeader header = HttpHeader.from(name); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/LdapInjectionModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/LdapInjectionModuleImpl.java index 1b0fc2a3cd9..aaa0f999bfb 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/LdapInjectionModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/LdapInjectionModuleImpl.java @@ -3,6 +3,7 @@ import static com.datadog.iast.taint.Ranges.rangesProviderFor; import static com.datadog.iast.taint.Tainteds.canBeTainted; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.taint.TaintedObjects; @@ -14,6 +15,10 @@ public class LdapInjectionModuleImpl extends SinkModuleBase implements LdapInjectionModule { + public LdapInjectionModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @SuppressWarnings("unchecked") @Override public void onDirContextSearch( diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/PathTraversalModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/PathTraversalModuleImpl.java index 2cb6eb4d816..cd5a54862a7 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/PathTraversalModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/PathTraversalModuleImpl.java @@ -4,6 +4,7 @@ import static com.datadog.iast.taint.Tainteds.canBeTainted; import static java.util.Arrays.asList; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.taint.TaintedObjects; @@ -20,6 +21,10 @@ public class PathTraversalModuleImpl extends SinkModuleBase implements PathTraversalModule { + public PathTraversalModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onPathTraversal(final @Nullable String path) { if (!canBeTainted(path)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SinkModuleBase.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SinkModuleBase.java index 57a7de1b0dc..d1a24a5516f 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SinkModuleBase.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SinkModuleBase.java @@ -3,7 +3,7 @@ import static com.datadog.iast.util.ObjectVisitor.State.CONTINUE; import static com.datadog.iast.util.ObjectVisitor.State.EXIT; -import com.datadog.iast.HasDependencies; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.Reporter; import com.datadog.iast.model.Evidence; @@ -29,14 +29,13 @@ /** Base class with utility methods for with sinks */ @SuppressWarnings({"UnusedReturnValue", "SameParameterValue"}) -public abstract class SinkModuleBase implements HasDependencies { +public abstract class SinkModuleBase { - protected OverheadController overheadController; - protected Reporter reporter; - protected StackWalker stackWalker; + protected final OverheadController overheadController; + protected final Reporter reporter; + protected final StackWalker stackWalker; - @Override - public void registerDependencies(@Nonnull final Dependencies dependencies) { + protected SinkModuleBase(@Nonnull final Dependencies dependencies) { overheadController = dependencies.getOverheadController(); reporter = dependencies.getReporter(); stackWalker = dependencies.getStackWalker(); @@ -89,6 +88,9 @@ public void registerDependencies(@Nonnull final Dependencies dependencies) { if (rangeProvider.size() == 1) { // only one item and has ranges final E item = rangeProvider.value(0); + if (item == null) { + return null; // should never happen + } evidence = item.toString(); targetRanges = rangeProvider.ranges(item); } else { @@ -177,10 +179,6 @@ protected StackTraceElement getCurrentStackTrace() { return stackWalker.walk(SinkModuleBase::findValidPackageForVulnerability); } - protected String getServiceName(final AgentSpan span) { - return span != null ? span.getServiceName() : null; - } - static StackTraceElement findValidPackageForVulnerability( @Nonnull final Stream stream) { final StackTraceElement[] first = new StackTraceElement[1]; @@ -198,13 +196,13 @@ static StackTraceElement findValidPackageForVulnerability( private class InjectionVisitor implements Visitor { - private final AgentSpan span; + @Nullable private final AgentSpan span; private final IastRequestContext ctx; private final InjectionType type; - private Evidence evidence; + @Nullable private Evidence evidence; private InjectionVisitor( - final AgentSpan span, final IastRequestContext ctx, final InjectionType type) { + @Nullable final AgentSpan span, final IastRequestContext ctx, final InjectionType type) { this.span = span; this.ctx = ctx; this.type = type; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SqlInjectionModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SqlInjectionModuleImpl.java index 31d42394329..98ad3d757f4 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SqlInjectionModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SqlInjectionModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.VulnerabilityType; @@ -10,6 +11,10 @@ public class SqlInjectionModuleImpl extends SinkModuleBase implements SqlInjectionModule { + public SqlInjectionModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onJdbcQuery(@Nullable final String queryString) { onJdbcQuery(queryString, null); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SsrfModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SsrfModuleImpl.java index b4df8302c94..e397e649c6e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SsrfModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SsrfModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import datadog.trace.api.iast.sink.SsrfModule; @@ -9,6 +10,10 @@ public class SsrfModuleImpl extends SinkModuleBase implements SsrfModule { + public SsrfModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onURLConnection(@Nullable final Object url) { if (url == null) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java new file mode 100644 index 00000000000..2cfb07e5f49 --- /dev/null +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/StacktraceLeakModuleImpl.java @@ -0,0 +1,37 @@ +package com.datadog.iast.sink; + +import com.datadog.iast.Dependencies; +import com.datadog.iast.model.Evidence; +import com.datadog.iast.model.Location; +import com.datadog.iast.model.Vulnerability; +import com.datadog.iast.model.VulnerabilityType; +import datadog.trace.api.iast.sink.StacktraceLeakModule; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import org.jetbrains.annotations.NotNull; + +public class StacktraceLeakModuleImpl extends SinkModuleBase implements StacktraceLeakModule { + + public StacktraceLeakModuleImpl(@NotNull Dependencies dependencies) { + super(dependencies); + } + + @Override + public void onStacktraceLeak( + Throwable throwable, String moduleName, String className, String methodName) { + if (throwable != null) { + final AgentSpan span = AgentTracer.activeSpan(); + + Evidence evidence = + new Evidence( + "ExceptionHandler in " + + moduleName + + " \r\nthrown " + + throwable.getClass().getName()); + Location location = Location.forSpanAndClassAndMethod(span, className, methodName); + + reporter.report( + span, new Vulnerability(VulnerabilityType.STACKTRACE_LEAK, location, evidence)); + } + } +} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/TrustBoundaryViolationModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/TrustBoundaryViolationModuleImpl.java index a584d078a0c..5f3ed0707b2 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/TrustBoundaryViolationModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/TrustBoundaryViolationModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import datadog.trace.api.iast.sink.TrustBoundaryViolationModule; @@ -9,6 +10,11 @@ public class TrustBoundaryViolationModuleImpl extends SinkModuleBase implements TrustBoundaryViolationModule { + + public TrustBoundaryViolationModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onSessionValue(@Nonnull String name, Object value) { final AgentSpan span = AgentTracer.activeSpan(); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/UnvalidatedRedirectModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/UnvalidatedRedirectModuleImpl.java index 45bd6ed2e47..deaf1977898 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/UnvalidatedRedirectModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/UnvalidatedRedirectModuleImpl.java @@ -2,6 +2,7 @@ import static com.datadog.iast.taint.Tainteds.canBeTainted; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.Location; @@ -25,6 +26,10 @@ public class UnvalidatedRedirectModuleImpl extends SinkModuleBase private static final String LOCATION_HEADER = "Location"; private static final String REFERER = "Referer"; + public UnvalidatedRedirectModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onRedirect(final @Nullable String value) { if (!canBeTainted(value)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakCipherModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakCipherModuleImpl.java index f06a52c4f02..0c61867614d 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakCipherModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakCipherModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.overhead.Operations; @@ -14,9 +15,8 @@ public class WeakCipherModuleImpl extends SinkModuleBase implements WeakCipherMo private Config config; - @Override - public void registerDependencies(@Nonnull Dependencies dependencies) { - super.registerDependencies(dependencies); + public WeakCipherModuleImpl(final Dependencies dependencies) { + super(dependencies); config = dependencies.getConfig(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakHashModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakHashModuleImpl.java index 3d91d1d371b..88b9cb8b0a4 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakHashModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakHashModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.overhead.Operations; @@ -14,9 +15,8 @@ public class WeakHashModuleImpl extends SinkModuleBase implements WeakHashModule private Config config; - @Override - public void registerDependencies(@Nonnull Dependencies dependencies) { - super.registerDependencies(dependencies); + public WeakHashModuleImpl(final Dependencies dependencies) { + super(dependencies); config = dependencies.getConfig(); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakRandomnessModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakRandomnessModuleImpl.java index d374927173a..7c13313eb30 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakRandomnessModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/WeakRandomnessModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.VulnerabilityType; import com.datadog.iast.overhead.Operations; @@ -11,6 +12,10 @@ public class WeakRandomnessModuleImpl extends SinkModuleBase implements WeakRandomnessModule { + public WeakRandomnessModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onWeakRandom(@Nonnull final Class instance) { if (isSecuredInstance(instance)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XContentTypeModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XContentTypeModuleImpl.java index 3d610e75792..8cb864e443e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XContentTypeModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XContentTypeModuleImpl.java @@ -1,5 +1,6 @@ package com.datadog.iast.sink; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Location; import com.datadog.iast.model.Vulnerability; @@ -18,6 +19,10 @@ public class XContentTypeModuleImpl extends SinkModuleBase implements XContentTypeModule { private static final Logger LOGGER = LoggerFactory.getLogger(XContentTypeModuleImpl.class); + public XContentTypeModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onRequestEnd(final Object iastRequestContextObject, final IGSpanInfo igSpanInfo) { try { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XPathInjectionModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XPathInjectionModuleImpl.java index 7e501b969a6..31633777c5e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XPathInjectionModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XPathInjectionModuleImpl.java @@ -2,6 +2,7 @@ import static com.datadog.iast.taint.Tainteds.canBeTainted; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.VulnerabilityType; import datadog.trace.api.iast.sink.XPathInjectionModule; @@ -10,6 +11,11 @@ import javax.annotation.Nullable; public class XPathInjectionModuleImpl extends SinkModuleBase implements XPathInjectionModule { + + public XPathInjectionModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onExpression(@Nullable String expression) { if (!canBeTainted(expression)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XssModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XssModuleImpl.java index 0168b73924f..da3d1e18337 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XssModuleImpl.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XssModuleImpl.java @@ -3,6 +3,7 @@ import static com.datadog.iast.taint.Ranges.rangesProviderFor; import static com.datadog.iast.taint.Tainteds.canBeTainted; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Evidence; import com.datadog.iast.model.Location; @@ -21,6 +22,10 @@ public class XssModuleImpl extends SinkModuleBase implements XssModule { + public XssModuleImpl(final Dependencies dependencies) { + super(dependencies); + } + @Override public void onXss(@Nonnull String s) { if (!canBeTainted(s)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/source/WebModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/source/WebModuleImpl.java deleted file mode 100644 index b036a740f06..00000000000 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/source/WebModuleImpl.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.datadog.iast.source; - -import static com.datadog.iast.taint.Tainteds.canBeTainted; - -import com.datadog.iast.IastRequestContext; -import com.datadog.iast.model.Source; -import com.datadog.iast.taint.TaintedObjects; -import datadog.trace.api.iast.SourceTypes; -import datadog.trace.api.iast.source.WebModule; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import javax.annotation.Nullable; - -public class WebModuleImpl implements WebModule { - - @Override - public void onParameterNames(@Nullable final Collection paramNames) { - onNamed(paramNames, SourceTypes.REQUEST_PARAMETER_NAME); - } - - @Override - public void onParameterValues( - @Nullable final String paramName, @Nullable final String[] paramValues) { - onNamed(paramName, paramValues, SourceTypes.REQUEST_PARAMETER_VALUE); - } - - @Override - public void onParameterValues( - @Nullable final String paramName, @Nullable final Collection paramValues) { - onNamed(paramName, paramValues, SourceTypes.REQUEST_PARAMETER_VALUE); - } - - @Override - public void onParameterValues(@Nullable final Map values) { - onNamed(values, SourceTypes.REQUEST_PARAMETER_VALUE); - } - - @Override - public void onHeaderNames(@Nullable final Collection headerNames) { - onNamed(headerNames, SourceTypes.REQUEST_HEADER_NAME); - } - - @Override - public void onHeaderValues( - @Nullable final String headerName, @Nullable final Collection headerValues) { - onNamed(headerName, headerValues, SourceTypes.REQUEST_HEADER_VALUE); - } - - @Override - public void onMultipartNames(@Nullable final Collection headerNames) { - onNamed(headerNames, SourceTypes.REQUEST_MULTIPART_PARAMETER); - } - - @Override - public void onMultipartValues( - @Nullable final String headerName, @Nullable final Collection headerValues) { - onNamed(headerName, headerValues, SourceTypes.REQUEST_MULTIPART_PARAMETER); - } - - @Override - public void onCookieNames(@Nullable Iterable cookieNames) { - onNamed(cookieNames, SourceTypes.REQUEST_COOKIE_NAME); - } - - @Override - public void onGetPathInfo(@Nullable String s) { - onNamed(Collections.singleton(s), SourceTypes.REQUEST_PATH); - } - - @Override - public void onGetRequestURI(@Nullable String s) { - onNamed(Collections.singleton(s), SourceTypes.REQUEST_URI); - } - - private static void onNamed(@Nullable final Iterable names, final byte source) { - if (names == null) { - return; - } - Iterator iterator = names.iterator(); - if (!iterator.hasNext()) { - return; - } - - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx == null) { - return; - } - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - do { - String name = iterator.next(); - if (canBeTainted(name)) { - taintedObjects.taintInputString(name, new Source(source, name, name)); - } - } while (iterator.hasNext()); - } - - private static void onNamed( - @Nullable final String name, @Nullable final Iterable values, final byte source) { - if (values == null) { - return; - } - Iterator iterator = values.iterator(); - if (!iterator.hasNext()) { - return; - } - - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx == null) { - return; - } - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - do { - String value = iterator.next(); - if (canBeTainted(value)) { - taintedObjects.taintInputString(value, new Source(source, name, value)); - } - } while (iterator.hasNext()); - } - - private static void onNamed( - @Nullable final String name, @Nullable final String[] values, final byte source) { - if (values == null || values.length == 0) { - return; - } - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx == null) { - return; - } - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - for (final String value : values) { - if (canBeTainted(value)) { - taintedObjects.taintInputString(value, new Source(source, name, value)); - } - } - } - - private static void onNamed(@Nullable final Map values, final byte source) { - if (values == null || values.isEmpty()) { - return; - } - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx == null) { - return; - } - final TaintedObjects taintedObjects = ctx.getTaintedObjects(); - final byte nameSource = SourceTypes.namedSource(source); - for (final Map.Entry entry : values.entrySet()) { - final String name = entry.getKey(); - if (canBeTainted(name)) { - taintedObjects.taintInputString(name, new Source(nameSource, name, name)); - } - for (final String value : entry.getValue()) { - if (canBeTainted(value)) { - taintedObjects.taintInputString(value, new Source(source, name, value)); - } - } - } - } -} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Ranges.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Ranges.java index aa6543d1b59..992655086c5 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Ranges.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Ranges.java @@ -1,7 +1,7 @@ package com.datadog.iast.taint; -import static com.datadog.iast.model.Range.NOT_MARKED; import static com.datadog.iast.taint.TaintedObject.MAX_RANGE_COUNT; +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED; import com.datadog.iast.model.Range; import com.datadog.iast.model.Source; @@ -41,11 +41,20 @@ public Range[] ranges(final Object value) { private Ranges() {} + public static Range[] forCharSequence( + final @Nonnull CharSequence obj, final @Nonnull Source source) { + return forCharSequence(obj, source, NOT_MARKED); + } + public static Range[] forCharSequence( final @Nonnull CharSequence obj, final @Nonnull Source source, final int mark) { return new Range[] {new Range(0, obj.length(), source, mark)}; } + public static Range[] forObject(final @Nonnull Source source) { + return forObject(source, NOT_MARKED); + } + public static Range[] forObject(final @Nonnull Source source, final int mark) { return new Range[] {new Range(0, Integer.MAX_VALUE, source, mark)}; } @@ -76,7 +85,7 @@ public static void copyShift( public static Range[] mergeRanges( final int offset, @Nonnull final Range[] rangesLeft, @Nonnull final Range[] rangesRight) { - final long nRanges = rangesLeft.length + rangesRight.length; + final long nRanges = rangesLeft.length + (long) rangesRight.length; final Range[] ranges = newArray(nRanges); int remaining = ranges.length; if (rangesLeft.length > 0) { @@ -114,6 +123,7 @@ public static RangesProvider rangesProviderFor( return new ListProvider<>(items, to); } + @Nullable public static Range[] forSubstring(int offset, int length, final @Nonnull Range[] ranges) { int[] includedRangesInterval = getIncludedRangesInterval(offset, length, ranges); @@ -175,6 +185,7 @@ public static int[] getIncludedRangesInterval( return new int[] {start, end}; } + @Nonnull public static Range highestPriorityRange(@Nonnull final Range[] ranges) { /* * This approach is better but not completely correct ideally the highest priority should use the following patterns: @@ -204,14 +215,16 @@ public interface RangesProvider { int size(); + @Nullable E value(final int index); + @Nullable Range[] ranges(final E value); } private abstract static class IterableProvider implements RangesProvider { private final LIST items; - private final Map ranges; + @Nullable private final Map ranges; private final int rangeCount; private IterableProvider(@Nonnull final LIST items, @Nonnull final TaintedObjects to) { @@ -242,11 +255,13 @@ public int rangeCount() { return rangeCount; } + @Nullable @Override public E value(final int index) { return item(items, index); } + @Nullable @Override public Range[] ranges(final E value) { return ranges == null ? null : ranges.get(value); @@ -259,12 +274,13 @@ public int size() { protected abstract int size(@Nonnull final LIST items); + @Nullable protected abstract E item(@Nonnull final LIST items, final int index); } private static class SingleProvider implements RangesProvider { private final E value; - private final TaintedObject tainted; + @Nullable private final TaintedObject tainted; private SingleProvider(@Nonnull final E value, @Nonnull final TaintedObjects to) { this.value = value; @@ -281,11 +297,13 @@ public int size() { return 1; } + @Nullable @Override public E value(int index) { return index == 0 ? value : null; } + @Nullable @Override public Range[] ranges(E value) { return value == this.value && tainted != null ? tainted.getRanges() : null; @@ -303,6 +321,7 @@ protected int size(@Nonnull final E[] items) { return items.length; } + @Nullable @Override protected E item(@Nonnull final E[] items, final int index) { return items[index]; @@ -320,13 +339,15 @@ protected int size(@Nonnull final List items) { return items.size(); } + @Nullable @Override protected E item(@Nonnull final List items, final int index) { return items.get(index); } } - public static Range[] getNotMarkedRanges(final Range[] ranges, final int mark) { + @Nullable + public static Range[] getNotMarkedRanges(@Nullable final Range[] ranges, final int mark) { if (ranges == null) { return null; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedMap.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedMap.java index fa690a22ded..79f91c18a6a 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedMap.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedMap.java @@ -223,7 +223,7 @@ private int remove(final TaintedObject entry) { if (cur == null) { return 0; } - for (TaintedObject prev = cur.next; cur != null; prev = cur, cur = cur.next) { + for (TaintedObject prev = cur.next; cur != null && prev != null; prev = cur, cur = cur.next) { if (cur == entry) { prev.next = cur.next; return 1; @@ -259,7 +259,7 @@ private int index(int h) { private Iterator iterator(final int start, final int stop) { return new Iterator() { int currentIndex = start; - TaintedObject currentSubPos; + @Nullable TaintedObject currentSubPos; @Override public boolean hasNext() { @@ -294,6 +294,8 @@ public TaintedObject next() { }; } + @Nonnull + @Override public Iterator iterator() { return iterator(0, table.length); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObject.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObject.java index d8b60f6b68e..84d3b9c6091 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObject.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObject.java @@ -14,7 +14,7 @@ public class TaintedObject extends WeakReference { public static final int MAX_RANGE_COUNT = Config.get().getIastMaxRangeCount(); final int positiveHashCode; - TaintedObject next; + @Nullable TaintedObject next; private Range[] ranges; public TaintedObject( diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObjects.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObjects.java index a292802992a..ab3b20e0401 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObjects.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/TaintedObjects.java @@ -1,13 +1,10 @@ package com.datadog.iast.taint; -import static com.datadog.iast.model.Range.NOT_MARKED; import static datadog.trace.api.ConfigDefaults.DEFAULT_IAST_MAX_CONCURRENT_REQUESTS; import static java.util.Collections.emptyIterator; -import com.datadog.iast.IastRequestContext; import com.datadog.iast.IastSystem; import com.datadog.iast.model.Range; -import com.datadog.iast.model.Source; import com.datadog.iast.model.json.TaintedObjectEncoding; import datadog.trace.api.Config; import java.util.ArrayList; @@ -16,19 +13,17 @@ import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@SuppressWarnings("UnusedReturnValue") public interface TaintedObjects extends Iterable { - TaintedObject taintInputString(@Nonnull String obj, @Nonnull Source source, int mark); - - TaintedObject taintInputCharSequence(@Nonnull CharSequence obj, @Nonnull Source source, int mark); - - TaintedObject taintInputObject(@Nonnull Object obj, @Nonnull Source source, int mark); - + @Nullable TaintedObject taint(@Nonnull Object obj, @Nonnull Range[] ranges); + @Nullable TaintedObject get(@Nonnull Object obj); void release(); @@ -39,18 +34,6 @@ public interface TaintedObjects extends Iterable { boolean isFlat(); - default TaintedObject taintInputString(@Nonnull String obj, @Nonnull Source source) { - return taintInputString(obj, source, NOT_MARKED); - } - - default TaintedObject taintInputCharSequence(@Nonnull CharSequence obj, @Nonnull Source source) { - return taintInputCharSequence(obj, source, NOT_MARKED); - } - - default TaintedObject taintInputObject(@Nonnull Object obj, @Nonnull Source source) { - return taintInputObject(obj, source, NOT_MARKED); - } - static TaintedObjects acquire() { TaintedObjectsImpl taintedObjects = TaintedObjectsImpl.pool.poll(); if (taintedObjects == null) { @@ -59,22 +42,6 @@ static TaintedObjects acquire() { return IastSystem.DEBUG ? new TaintedObjectsDebugAdapter(taintedObjects) : taintedObjects; } - static TaintedObjects activeTaintedObjects(boolean lazy) { - if (lazy) { - return new LazyTaintedObjects(); - } else { - final IastRequestContext ctx = IastRequestContext.get(); - if (ctx != null) { - return ctx.getTaintedObjects(); - } - return null; - } - } - - static TaintedObjects activeTaintedObjects() { - return activeTaintedObjects(false); - } - class TaintedObjectsImpl implements TaintedObjects { private static final ArrayBlockingQueue pool = @@ -92,35 +59,6 @@ private TaintedObjectsImpl(final @Nonnull TaintedMap map) { this.map = map; } - @Override - public TaintedObject taintInputString( - final @Nonnull String obj, final @Nonnull Source source, final int mark) { - final TaintedObject tainted = - new TaintedObject( - obj, Ranges.forCharSequence(obj, source, mark), map.getReferenceQueue()); - map.put(tainted); - return tainted; - } - - @Override - public TaintedObject taintInputCharSequence( - final @Nonnull CharSequence obj, final @Nonnull Source source, final int mark) { - final TaintedObject tainted = - new TaintedObject( - obj, Ranges.forCharSequence(obj, source, mark), map.getReferenceQueue()); - map.put(tainted); - return tainted; - } - - @Override - public TaintedObject taintInputObject( - @Nonnull Object obj, @Nonnull Source source, final int mark) { - final TaintedObject tainted = - new TaintedObject(obj, Ranges.forObject(source, mark), map.getReferenceQueue()); - map.put(tainted); - return tainted; - } - @Override public TaintedObject taint(final @Nonnull Object obj, final @Nonnull Range[] ranges) { final TaintedObject tainted = new TaintedObject(obj, ranges, map.getReferenceQueue()); @@ -128,6 +66,7 @@ public TaintedObject taint(final @Nonnull Object obj, final @Nonnull Range[] ran return tainted; } + @Nullable @Override public TaintedObject get(final @Nonnull Object obj) { return map.get(obj); @@ -154,13 +93,14 @@ public boolean isFlat() { return map.isFlat(); } + @Nonnull @Override public Iterator iterator() { return map.iterator(); } } - class TaintedObjectsDebugAdapter implements TaintedObjects { + final class TaintedObjectsDebugAdapter implements TaintedObjects { static final Logger LOGGER = LoggerFactory.getLogger(TaintedObjects.class); private final TaintedObjectsImpl delegated; @@ -172,30 +112,7 @@ public TaintedObjectsDebugAdapter(final TaintedObjectsImpl delegated) { LOGGER.debug("new: id={}", id); } - @Override - public TaintedObject taintInputString( - final @Nonnull String obj, final @Nonnull Source source, final int mark) { - final TaintedObject tainted = delegated.taintInputString(obj, source, mark); - logTainted(tainted); - return tainted; - } - - @Override - public TaintedObject taintInputCharSequence( - @Nonnull CharSequence obj, @Nonnull Source source, int mark) { - final TaintedObject tainted = delegated.taintInputCharSequence(obj, source, mark); - logTainted(tainted); - return tainted; - } - - @Override - public TaintedObject taintInputObject( - @Nonnull Object obj, @Nonnull Source source, final int mark) { - final TaintedObject tainted = delegated.taintInputObject(obj, source, mark); - logTainted(tainted); - return tainted; - } - + @Nullable @Override public TaintedObject taint(final @Nonnull Object obj, final @Nonnull Range[] ranges) { final TaintedObject tainted = delegated.taint(obj, ranges); @@ -203,6 +120,7 @@ public TaintedObject taint(final @Nonnull Object obj, final @Nonnull Range[] ran return tainted; } + @Nullable @Override public TaintedObject get(final @Nonnull Object obj) { return delegated.get(obj); @@ -239,6 +157,7 @@ public boolean isFlat() { return delegated.isFlat(); } + @Nonnull @Override public Iterator iterator() { return delegated.iterator(); @@ -255,110 +174,17 @@ private void logTainted(final TaintedObject tainted) { } } - class LazyTaintedObjects implements TaintedObjects { - private boolean fetched = false; - private TaintedObjects taintedObjects; - - @Override - public TaintedObject taintInputString( - @Nonnull final String obj, @Nonnull final Source source, final int mark) { - final TaintedObjects to = getTaintedObjects(); - return to == null ? null : to.taintInputString(obj, source, mark); - } - - @Override - public TaintedObject taintInputCharSequence( - @Nonnull CharSequence obj, @Nonnull Source source, int mark) { - final TaintedObjects to = getTaintedObjects(); - return to == null ? null : to.taintInputCharSequence(obj, source, mark); - } - - @Override - public TaintedObject taintInputObject( - @Nonnull final Object obj, @Nonnull final Source source, final int mark) { - final TaintedObjects to = getTaintedObjects(); - return to == null ? null : to.taintInputObject(obj, source, mark); - } - - @Override - public TaintedObject taint(@Nonnull final Object obj, @Nonnull final Range[] ranges) { - final TaintedObjects to = getTaintedObjects(); - return to == null ? null : to.taint(obj, ranges); - } - - @Override - public TaintedObject get(@Nonnull final Object obj) { - final TaintedObjects to = getTaintedObjects(); - return to == null ? null : to.get(obj); - } - - @Override - public void release() { - final TaintedObjects to = getTaintedObjects(); - if (to != null) { - to.release(); - } - } - - @Override - public Iterator iterator() { - final TaintedObjects to = getTaintedObjects(); - return to != null ? to.iterator() : emptyIterator(); - } - - @Override - public int getEstimatedSize() { - final TaintedObjects to = getTaintedObjects(); - return to != null ? to.getEstimatedSize() : 0; - } - - @Override - public boolean isFlat() { - final TaintedObjects to = getTaintedObjects(); - return to != null && to.isFlat(); - } - - @Override - public int count() { - final TaintedObjects to = getTaintedObjects(); - return to != null ? to.count() : 0; - } - - private TaintedObjects getTaintedObjects() { - if (!fetched) { - fetched = true; - taintedObjects = activeTaintedObjects(); - } - return taintedObjects; - } - } - - enum NoOp implements TaintedObjects { - INSTANCE; - - @Override - public TaintedObject taintInputString( - @Nonnull final String obj, @Nonnull final Source source, final int mark) { - return null; - } - - @Override - public TaintedObject taintInputCharSequence( - @Nonnull CharSequence obj, @Nonnull Source source, int mark) { - return null; - } + final class NoOp implements TaintedObjects { - @Override - public TaintedObject taintInputObject( - @Nonnull final Object obj, @Nonnull final Source source, final int mark) { - return null; - } + public static final TaintedObjects INSTANCE = new NoOp(); + @Nullable @Override public TaintedObject taint(@Nonnull final Object obj, @Nonnull final Range[] ranges) { return null; } + @Nullable @Override public TaintedObject get(@Nonnull final Object obj) { return null; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Tainteds.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Tainteds.java index 142249ec1fe..422f677d553 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Tainteds.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Tainteds.java @@ -1,17 +1,21 @@ package com.datadog.iast.taint; import java.util.Collection; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.jetbrains.annotations.Contract; /** Utilitiles to work with {@link TaintedObject} */ public final class Tainteds { private Tainteds() {} + @Contract("null -> false") public static boolean canBeTainted(@Nullable final CharSequence s) { return s != null && s.length() > 0; } + @Contract("null -> false") public static boolean canBeTainted(@Nullable final E[] e) { if (e == null || e.length == 0) { return false; @@ -24,6 +28,7 @@ public static boolean canBeTainted(@Nullable final E[] return false; } + @Contract("null -> false") public static boolean canBeTainted(@Nullable final Collection e) { if (e == null || e.isEmpty()) { return false; @@ -36,7 +41,9 @@ public static boolean canBeTainted(@Nullable final Coll return false; } - public static TaintedObject getTainted(final TaintedObjects to, final Object value) { + @Nullable + public static TaintedObject getTainted( + @Nonnull final TaintedObjects to, @Nullable final Object value) { return value == null ? null : to.get(value); } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/TelemetryRequestStartedHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/TelemetryRequestStartedHandler.java index 1bf6033ca8a..5e647355b09 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/TelemetryRequestStartedHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/TelemetryRequestStartedHandler.java @@ -1,6 +1,6 @@ package com.datadog.iast.telemetry; -import com.datadog.iast.HasDependencies.Dependencies; +import com.datadog.iast.Dependencies; import com.datadog.iast.IastRequestContext; import com.datadog.iast.RequestStartedHandler; import com.datadog.iast.taint.TaintedObjects; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetry.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetry.java index 951bd9ec15f..1ac059b0b54 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetry.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetry.java @@ -6,13 +6,13 @@ import com.datadog.iast.IastRequestContext; import com.datadog.iast.model.Range; -import com.datadog.iast.model.Source; import com.datadog.iast.taint.TaintedObject; import com.datadog.iast.taint.TaintedObjects; import datadog.trace.api.iast.telemetry.IastMetricCollector; import datadog.trace.api.iast.telemetry.Verbosity; import java.util.Iterator; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class TaintedObjectsWithTelemetry implements TaintedObjects { @@ -31,7 +31,7 @@ public static TaintedObjects build( private final TaintedObjects delegate; private final boolean debug; - private IastRequestContext ctx; + @Nullable private IastRequestContext ctx; protected TaintedObjectsWithTelemetry(final boolean debug, final TaintedObjects delegate) { this.delegate = delegate; @@ -46,26 +46,7 @@ public void initContext(final IastRequestContext ctx) { this.ctx = ctx; } - @Override - public TaintedObject taintInputString( - @Nonnull String obj, @Nonnull Source source, final int mark) { - final TaintedObject result = delegate.taintInputString(obj, source, mark); - if (debug) { - IastMetricCollector.add(EXECUTED_TAINTED, 1, ctx); - } - return result; - } - - @Override - public TaintedObject taintInputCharSequence( - @Nonnull CharSequence obj, @Nonnull Source source, int mark) { - final TaintedObject result = delegate.taintInputCharSequence(obj, source, mark); - if (debug) { - IastMetricCollector.add(EXECUTED_TAINTED, 1, ctx); - } - return result; - } - + @Nullable @Override public TaintedObject taint(@Nonnull Object obj, @Nonnull Range[] ranges) { final TaintedObject result = delegate.taint(obj, ranges); @@ -75,16 +56,7 @@ public TaintedObject taint(@Nonnull Object obj, @Nonnull Range[] ranges) { return result; } - @Override - public TaintedObject taintInputObject( - @Nonnull Object obj, @Nonnull Source source, final int mark) { - final TaintedObject result = delegate.taintInputObject(obj, source, mark); - if (debug) { - IastMetricCollector.add(EXECUTED_TAINTED, 1, ctx); - } - return result; - } - + @Nullable @Override public TaintedObject get(@Nonnull Object obj) { return delegate.get(obj); @@ -102,6 +74,7 @@ public void release() { } } + @Nonnull @Override public Iterator iterator() { return delegate.iterator(); diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/CookieSecurityParser.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/CookieSecurityParser.java index 54dec1ec58a..bc238eac288 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/CookieSecurityParser.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/CookieSecurityParser.java @@ -5,8 +5,10 @@ import datadog.trace.api.iast.util.Cookie; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.NoSuchElementException; import java.util.StringTokenizer; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +46,7 @@ public static List parse(final String cookieString) { } } + @Nullable private static Cookie parseInternal(final String header) { String cookieName; boolean httpOnly = false; @@ -111,7 +114,7 @@ private static List splitMultiCookies(final String header) { } private static int guessCookieVersion(String header) { - header = header.toLowerCase(); + header = header.toLowerCase(Locale.ROOT); if (header.contains("expires=")) { // only netscape cookie using 'expires' return 0; diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java index 36ee2b86501..5d58ea8a617 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java @@ -6,6 +6,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; public class HttpHeader { @@ -19,6 +20,7 @@ public boolean matches(final String name) { return this.name.equalsIgnoreCase(name); } + @Nullable public static HttpHeader from(final String name) { return Values.HEADERS.get(name.toLowerCase(Locale.ROOT)); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/ObjectVisitor.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/ObjectVisitor.java index c9178b749c1..01f5e568061 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/ObjectVisitor.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/ObjectVisitor.java @@ -15,6 +15,7 @@ import java.util.Set; import java.util.function.Predicate; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +26,7 @@ public class ObjectVisitor { private static final int MAX_VISITED_OBJECTS = 1000; private static final int MAX_DEPTH = 10; - private static final Method TRY_SET_ACCESSIBLE; + @Nullable private static final Method TRY_SET_ACCESSIBLE; static { TRY_SET_ACCESSIBLE = fetchTrySetAccessibleMethod(); @@ -210,6 +211,7 @@ private static boolean inspectField(final Field field) { return true; } + @Nullable private static Method fetchTrySetAccessibleMethod() { Method method = null; if (Platform.isJavaVersionAtLeast(9)) { diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/Ranged.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/Ranged.java index 1cde27dc057..645fb58cfdc 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/Ranged.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/Ranged.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; public interface Ranged { @@ -62,6 +63,7 @@ default List remove(final Ranged range) { } /** Computes the intersection of the ranges or {@code null} if they do not intersect */ + @Nullable default Ranged intersection(final Ranged range) { if (this.getStart() == range.getStart() && this.getLength() == range.getLength()) { return this; @@ -84,7 +86,7 @@ default Ranged intersection(final Ranged range) { } } - default boolean isBefore(final Ranged range) { + default boolean isBefore(@Nullable final Ranged range) { if (range == null) { return true; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/RangedDeque.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/RangedDeque.java index faef93afde0..4e66a2bf5a1 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/RangedDeque.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/RangedDeque.java @@ -9,8 +9,10 @@ /** */ public interface RangedDeque { + @Nullable E poll(); + @Nullable E peek(); void addFirst(@Nonnull E item); @@ -31,8 +33,9 @@ abstract class BaseRangedDequeue implements RangedDeque { private final Deque head = new LinkedList<>(); - protected E next; + @Nullable protected E next; + @Nullable @Override public final E poll() { final E result = next; @@ -40,6 +43,7 @@ public final E poll() { return result; } + @Nullable @Override public final E peek() { return next; @@ -58,15 +62,18 @@ public final boolean isEmpty() { return next == null; } + @Nullable protected final E fetchNext() { return head.isEmpty() ? internalPoll() : head.poll(); } + @Nullable protected abstract E internalPoll(); } class EmptyRangedDequeue extends BaseRangedDequeue { + @Nullable @Override protected E internalPoll() { return null; @@ -82,6 +89,7 @@ class TokenizerQueue extends BaseRangedDequeue { next = fetchNext(); } + @Nullable @Override protected Ranged internalPoll() { return tokenizer.next() ? tokenizer.current() : null; @@ -99,6 +107,7 @@ class ArrayQueue extends BaseRangedDequeue { next = fetchNext(); } + @Nullable @Override protected E internalPoll() { return index >= array.length ? null : array[index++]; diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy index d3c31196f87..2d9597e728b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/GrpcRequestMessageHandlerTest.groovy @@ -1,5 +1,6 @@ package com.datadog.iast +import com.datadog.iast.propagation.PropagationModuleImpl import com.datadog.iast.protobuf.Test2 import com.datadog.iast.protobuf.Test3 import com.datadog.iast.util.ObjectVisitor @@ -8,6 +9,8 @@ import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.SourceTypes import datadog.trace.api.iast.propagation.PropagationModule +import datadog.trace.api.iast.telemetry.IastMetric +import datadog.trace.api.iast.telemetry.IastMetricCollector import datadog.trace.test.util.DDSpecification import foo.bar.VisitableClass @@ -22,9 +25,9 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { private RequestContext ctx void setup() { - propagation = Mock(PropagationModule) + propagation = Spy(new PropagationModuleImpl()) InstrumentationBridge.registerIastModule(propagation) - iastCtx = Mock(IastRequestContext) + iastCtx = Spy(new IastRequestContext()) ctx = Mock(RequestContext) { getData(RequestContextSlot.IAST) >> iastCtx } @@ -62,7 +65,7 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { handler.apply(ctx, target) then: - 1 * propagation.taintDeeply(iastCtx, SourceTypes.GRPC_BODY, target, _ as Predicate>) + 1 * propagation.taintDeeply(iastCtx, target, SourceTypes.GRPC_BODY, _ as Predicate>) } void 'the handler only takes into account protobuf v.#protobufVersion related messages'() { @@ -73,7 +76,6 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { return CONTINUE } } - final url = 'https://dd.datad0g.com/' final nonProtobufMessage = new VisitableClass(name: 'test') final filter = GrpcRequestMessageHandler::isProtobufArtifact @@ -102,6 +104,24 @@ class GrpcRequestMessageHandlerTest extends DDSpecification { protobufMessage << [buildProto2Message(), buildProto3Message()] } + void 'test that metrics are properly generated'() { + given: + final collector = Spy(new IastMetricCollector()) + iastCtx.getMetricCollector() >> collector + final handler = new GrpcRequestMessageHandler() + + when: + handler.apply(ctx, message) + + then: + 1 * collector.addMetric(IastMetric.EXECUTED_SOURCE, SourceTypes.GRPC_BODY, 6) + + where: + message | _ + buildProto2Message() | _ + buildProto3Message() | _ + } + private static def buildProto2Message() { final child = Test2.Proto2Child.newBuilder() .setOptional("optional") diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastModuleImplTestBase.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastModuleImplTestBase.groovy index d47ce8e0a14..9ad167ed6c6 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastModuleImplTestBase.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastModuleImplTestBase.groovy @@ -1,6 +1,5 @@ package com.datadog.iast -import com.datadog.iast.HasDependencies.Dependencies import com.datadog.iast.overhead.Operation import com.datadog.iast.overhead.OverheadController import datadog.trace.api.Config @@ -25,6 +24,8 @@ class IastModuleImplTestBase extends DDSpecification { // TODO replace by mock an fix all mock assertions (0 * _ will usually fail) protected StackWalker stackWalker = StackWalkerFactory.INSTANCE + protected Dependencies dependencies = new Dependencies(Config.get(), reporter, overheadController, stackWalker) + void setup() { AgentTracer.forceRegister(tracer) overheadController.acquireRequest() >> true @@ -34,9 +35,4 @@ class IastModuleImplTestBase extends DDSpecification { void cleanup() { AgentTracer.forceRegister(ORIGINAL_TRACER) } - - protected E registerDependencies(final E module) { - module.registerDependencies(new Dependencies(Config.get(), reporter, overheadController, stackWalker)) - return module - } } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestEndedHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestEndedHandlerTest.groovy index b205d9d3eba..b325a373d90 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestEndedHandlerTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestEndedHandlerTest.groovy @@ -1,6 +1,5 @@ package com.datadog.iast -import com.datadog.iast.HasDependencies.Dependencies import com.datadog.iast.overhead.OverheadController import datadog.trace.api.Config import datadog.trace.api.gateway.Flow diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestStartedHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestStartedHandlerTest.groovy index f70e087b146..8a0ca4e2c4a 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestStartedHandlerTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/RequestStartedHandlerTest.groovy @@ -1,6 +1,5 @@ package com.datadog.iast -import com.datadog.iast.HasDependencies.Dependencies import com.datadog.iast.overhead.OverheadController import datadog.trace.api.Config import datadog.trace.api.gateway.Flow diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/RangeTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/RangeTest.groovy index 5a3561fe7f8..c4992278756 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/RangeTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/RangeTest.groovy @@ -35,7 +35,7 @@ class RangeTest extends DDSpecification { def 'shift zero'() { given: final source = new Source(SourceTypes.NONE, null, null) - final orig = new Range(0, 1, source, Range.NOT_MARKED) + final orig = new Range(0, 1, source, VulnerabilityMarks.NOT_MARKED) when: final result = orig.shift(0) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/VulnerabilityTypeTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/VulnerabilityTypeTest.groovy index 51568f75e64..408219c9000 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/VulnerabilityTypeTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/VulnerabilityTypeTest.groovy @@ -27,9 +27,6 @@ class VulnerabilityTypeTest extends DDSpecification { WEAK_CIPHER | getSpanAndClassAndMethodLocation(123) | new Evidence("MD5") | 3265519776 WEAK_CIPHER | getSpanAndClassAndMethodLocation(456) | new Evidence("MD4") | 3265519776 WEAK_CIPHER | getSpanAndClassAndMethodLocation(789) | null | 3265519776 - WEAK_CIPHER | null | new Evidence("MD5") | 1272119222 - WEAK_CIPHER | null | new Evidence("MD4") | 1272119222 - WEAK_CIPHER | null | null | 1272119222 INSECURE_COOKIE | getSpanAndStackLocation(123) | null | 3471934557 INSECURE_COOKIE | getSpanAndStackLocation(123) | new Evidence("cookieName1") | 360083726 INSECURE_COOKIE | getSpanAndStackLocation(123) | new Evidence("cookieName2") | 2357141684 @@ -47,19 +44,19 @@ class VulnerabilityTypeTest extends DDSpecification { HSTS_HEADER_MISSING | getSpanLocation(123, 'serviceName2') | null | 1268102093 } - private Location getSpanAndStackLocation(final long spanId){ + private Location getSpanAndStackLocation(final long spanId) { final span = Mock(AgentSpan) span.getSpanId() >> spanId return Location.forSpanAndStack(span, new StackTraceElement("foo", "foo", "foo", 1)) } - private Location getSpanAndClassAndMethodLocation(final long spanId){ + private Location getSpanAndClassAndMethodLocation(final long spanId) { final span = Mock(AgentSpan) span.getSpanId() >> spanId return Location.forSpanAndClassAndMethod(span, "foo", "foo") } - private Location getSpanLocation(final long spanId, final String serviceName){ + private Location getSpanLocation(final long spanId, final String serviceName) { final span = Mock(AgentSpan) span.getSpanId() >> spanId span.getServiceName() >> serviceName diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/EvidenceEncodingTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/EvidenceEncodingTest.groovy index c946e53f70a..9b3d67e9339 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/EvidenceEncodingTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/EvidenceEncodingTest.groovy @@ -10,6 +10,8 @@ import datadog.trace.test.util.DDSpecification import org.skyscreamer.jsonassert.JSONAssert import spock.lang.Shared +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED + class EvidenceEncodingTest extends DDSpecification { private static final List SOURCES_SUITE = (0..2).collect { new Source((byte) it, "name$it", "value$it") } @@ -65,7 +67,7 @@ class EvidenceEncodingTest extends DDSpecification { } private static Range range(final int start, final int length, final Source source) { - return new Range(start, length, source, Range.NOT_MARKED) + return new Range(start, length, source, NOT_MARKED) } private static Source source(final int index) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/TaintedObjectEncodingTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/TaintedObjectEncodingTest.groovy index 55956358225..dfbc0fb8275 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/TaintedObjectEncodingTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/TaintedObjectEncodingTest.groovy @@ -10,6 +10,8 @@ import org.skyscreamer.jsonassert.JSONAssert import java.lang.ref.ReferenceQueue +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED + class TaintedObjectEncodingTest extends DDSpecification { @Override @@ -87,7 +89,7 @@ class TaintedObjectEncodingTest extends DDSpecification { private TaintedObject taintedObject(final String value, final byte sourceType, final String sourceName, final String sourceValue) { return new TaintedObject( value, - [new Range(0, value.length(), new Source(sourceType, sourceName, sourceValue), Range.NOT_MARKED)] as Range[], + [new Range(0, value.length(), new Source(sourceType, sourceName, sourceValue), NOT_MARKED)] as Range[], Mock(ReferenceQueue)) } } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/VulnerabilityEncodingTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/VulnerabilityEncodingTest.groovy index e56e6afa77b..4ade1c08a8d 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/VulnerabilityEncodingTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/VulnerabilityEncodingTest.groovy @@ -16,7 +16,7 @@ import org.skyscreamer.jsonassert.JSONAssert import java.util.regex.Matcher import java.util.regex.Pattern -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED class VulnerabilityEncodingTest extends DDSpecification { @@ -539,6 +539,25 @@ class VulnerabilityEncodingTest extends DDSpecification { } + void 'exception during serialization is caught'() { + given: + final value = new VulnerabilityBatch() + final type = Mock(VulnerabilityType) { + name() >> { throw new RuntimeException("ERROR") } + } + final vuln = new Vulnerability(type, null, null) + value.add(vuln) + + when: + final result = VulnerabilityEncoding.toJson(value) + + then: + JSONAssert.assertEquals('''{ + "vulnerabilities": [ + ] + }''', result, true) + } + private static String generateLargeString(){ int targetSize = 25 * 1024 StringBuilder sb = new StringBuilder() diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy index c8b45e183ec..e573d5dfb2c 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/propagation/PropagationModuleTest.groovy @@ -4,6 +4,9 @@ import com.datadog.iast.IastModuleImplTestBase import com.datadog.iast.IastRequestContext import com.datadog.iast.model.Range import com.datadog.iast.model.Source +import com.datadog.iast.taint.Ranges +import com.datadog.iast.taint.TaintedObject +import datadog.trace.api.Config import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.SourceTypes @@ -11,25 +14,19 @@ import datadog.trace.api.iast.Taintable import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.propagation.PropagationModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import groovy.transform.CompileDynamic +import org.junit.Assume -import static com.datadog.iast.model.Range.NOT_MARKED -import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat -import static com.datadog.iast.taint.TaintUtils.fromTaintFormat -import static datadog.trace.api.iast.VulnerabilityMarks.SQL_INJECTION_MARK -import static datadog.trace.api.iast.VulnerabilityMarks.XPATH_INJECTION_MARK -import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK +import static com.datadog.iast.taint.Ranges.highestPriorityRange +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED -@CompileDynamic class PropagationModuleTest extends IastModuleImplTestBase { private PropagationModule module - private List objectHolder + private IastRequestContext ctx - def setup() { + void setup() { module = new PropagationModuleImpl() - objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { getData(RequestContextSlot.IAST) >> ctx @@ -40,54 +37,49 @@ class PropagationModuleTest extends IastModuleImplTestBase { tracer.activeSpan() >> span } - void '#method null or empty'() { - when: + void '#method(#args) not taintable'() { + when: 'there is no context by default' module.&"$method".call(args.toArray()) - then: + then: 'no mock calls should happen' + 0 * _ + + when: 'there is a context' + args.add(0, ctx) + module.&"$method".call(args.toArray()) + + then: 'no mock calls should happen' 0 * _ where: - method | args - 'taintIfInputIsTainted' | [null, null] - 'taintIfInputIsTainted' | [null, new Object()] - 'taintIfInputIsTainted' | [null, 'test'] - 'taintIfInputIsTainted' | [new Object(), null] - 'taintIfInputIsTainted' | [null as String, null] - 'taintIfInputIsTainted' | ['', null] - 'taintIfInputIsTainted' | ['', new Object()] - 'taintIfInputIsTainted' | [null as String, new Object()] - 'taintIfInputIsTainted' | ['test', null] - 'taintIfInputIsTainted' | [null as String, 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', null as String, null] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', '', null] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', '', new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', null as String, new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'test', null] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', null as String, 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [].toSet(), 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, ['test'].toSet(), null] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [:].entrySet().toList(), 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [key: "value"].entrySet().toList(), null] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', null as Collection, 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', [], 'test'] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ['value'], null] - 'taintObjectIfInputIsTaintedKeepingRanges' | [null, new Object()] - 'taintObjectIfInputIsTaintedKeepingRanges' | [new Object(), null] - 'taintIfAnyInputIsTainted' | [null, null] - 'taintIfAnyInputIsTainted' | [null, [].toArray()] - 'taintIfAnyInputIsTainted' | ['test', [].toArray()] - 'taint' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', null as String] - 'taint' | [SourceTypes.REQUEST_PARAMETER_VALUE, null as String, null as String] - 'taint' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ''] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, null as Object[]] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, [] as Object[]] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, null as Collection] - 'taintIfInputIsTaintedWithMarks' | ['', null, VulnerabilityMarks.XSS_MARK] - 'taintIfInputIsTaintedWithMarks' | ['', new Object(), VulnerabilityMarks.XSS_MARK] - 'taintIfInputIsTaintedWithMarks' | [null as String, new Object(), VulnerabilityMarks.XSS_MARK] - 'taintIfInputIsTaintedWithMarks' | ['test', null, VulnerabilityMarks.XSS_MARK] - 'taintIfInputIsTaintedWithMarks' | [null as String, 'test', VulnerabilityMarks.XSS_MARK] + method | args + 'taint' | [null, SourceTypes.REQUEST_PARAMETER_VALUE] + 'taint' | [null, SourceTypes.REQUEST_PARAMETER_VALUE, 'name'] + 'taint' | [null, SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] + 'taintIfTainted' | [null, 'test'] + 'taintIfTainted' | ['test', null] + 'taintIfTainted' | [null, 'test', false, NOT_MARKED] + 'taintIfTainted' | ['test', null, false, NOT_MARKED] + 'taintIfTainted' | [null, 'test'] + 'taintIfTainted' | ['test', null] + 'taintIfTainted' | [null, 'test', SourceTypes.REQUEST_PARAMETER_VALUE] + 'taintIfTainted' | ['test', null, SourceTypes.REQUEST_PARAMETER_VALUE] + 'taintIfTainted' | [null, 'test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name'] + 'taintIfTainted' | ['test', null, SourceTypes.REQUEST_PARAMETER_VALUE, 'name'] + 'taintIfTainted' | [null, 'test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] + 'taintIfTainted' | ['test', null, SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] + 'taintIfAnyTainted' | [null, ['test'] as Object[]] + 'taintIfAnyTainted' | ['test', null] + 'taintIfAnyTainted' | ['test', [] as Object[]] + 'taintDeeply' | [ + null, + SourceTypes.REQUEST_PARAMETER_VALUE, + { + true + } + ] + 'findSource' | [null] + 'isTainted' | [null] } void '#method without span'() { @@ -99,379 +91,264 @@ class PropagationModuleTest extends IastModuleImplTestBase { 0 * _ where: - method | args - 'taintIfInputIsTainted' | [new Object(), new Object()] - 'taintIfInputIsTainted' | [new Object(), 'test'] - 'taintIfInputIsTainted' | ['test', new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value', new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ['value'], new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, ['value'].toSet(), new Object()] - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [key: 'value'].entrySet().toList(), new Object()] - 'taintObjectIfInputIsTaintedKeepingRanges' | [new Object(), new Object()] - 'taintIfAnyInputIsTainted' | ['value', ['test', 'test2'].toArray()] - 'taint' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, [new Object()] as Object[]] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, [new Object()]] - 'taintObjects' | [SourceTypes.REQUEST_PARAMETER_VALUE, [new Object()] as Collection] - 'taintIfInputIsTaintedWithMarks' | ['test', new Object(), VulnerabilityMarks.XSS_MARK] + method | args + 'taint' | ['test', SourceTypes.REQUEST_PARAMETER_VALUE] + 'taint' | ['test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name'] + 'taint' | ['test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] + 'taintIfTainted' | ['test', 'test'] + 'taintIfTainted' | ['test', 'test', false, NOT_MARKED] + 'taintIfTainted' | ['test', 'test', SourceTypes.REQUEST_PARAMETER_VALUE] + 'taintIfTainted' | ['test', 'test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name'] + 'taintIfTainted' | ['test', 'test', SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'] + 'taintIfAnyTainted' | ['test', ['test']] + 'taintDeeply' | [ + 'test', + SourceTypes.REQUEST_PARAMETER_VALUE, + { + true + } + ] + 'findSource' | ['test'] + 'isTainted' | ['test'] } - void 'test propagation for #method'() { + void 'test taint'() { given: - final toTaint = toTaintClosure.call(args) - final targetMethod = module.&"$method" - final arguments = args.toArray() - final input = inputClosure.call(arguments) + final value = (target instanceof CharSequence) ? target.toString() : null + final source = taintedSource(value) + final ranges = Ranges.forObject(source) when: - targetMethod.call(arguments) + module.taint(target, source.origin, source.name, source.value) then: - assertNotTainted(toTaint) - - when: - taint(input) - targetMethod.call(arguments) - - then: - assertTainted(toTaint) + final tainted = getTaintedObject(target) + if (shouldTaint) { + assertTainted(tainted, ranges) + } else { + assert tainted == null + } where: - method | args | toTaintClosure | inputClosure - 'taintIfInputIsTainted' | [new Object(), 'I am an string'] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | [new Object(), new Object()] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | [new Object(), new MockTaintable()] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | ['Hello', 'I am an string'] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | ['Hello', new Object()] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | ['Hello', new MockTaintable()] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value', 'I am an string'] | { - it[2] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value', new Object()] | { - it[2] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value', new MockTaintable()] | { - it[2] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ['value'], 'I am an string'] | { - it[2][0] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ['value'], new Object()] | { - it[2][0] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, 'name', ['value'], new MockTaintable()] | { - it[2][0] - } | { - it[3] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, ['value'].toSet(), 'I am an string'] | { - it[1][0] - } | { - it[2] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, ['value'].toSet(), new Object()] | { - it[1][0] - } | { - it[2] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, ['value'].toSet(), new MockTaintable()] | { - it[1][0] - } | { - it[2] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [name: 'value'].entrySet().toList(), 'I am an string'] | { - it[1][0].value - } | { - it[2] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [name: 'value'].entrySet().toList(), new Object()] | { - it[1][0].value - } | { - it[2] - } - 'taintIfInputIsTainted' | [SourceTypes.REQUEST_PARAMETER_VALUE, [name: 'value'].entrySet().toList(), new MockTaintable()] | { - it[1][0].value - } | { - it[2] - } - 'taintObjectIfInputIsTaintedKeepingRanges' | [new Object(), new Object()] | { - it[0] - } | { - it[1] - } - 'taintObjectIfInputIsTaintedKeepingRanges' | [new Object(), new MockTaintable()] | { - it[0] - } | { - it[1] - } - 'taintIfAnyInputIsTainted' | [new Object(), ['I am an string'].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfAnyInputIsTainted' | [new Object(), [new Object()].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfAnyInputIsTainted' | [new Object(), [new MockTaintable()].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfAnyInputIsTainted' | ['Hello', ['I am an string'].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfAnyInputIsTainted' | ['Hello', [new Object()].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfAnyInputIsTainted' | ['Hello', [new MockTaintable()].toArray()] | { - it[0] - } | { - it[1][0] - } - 'taintIfInputIsTaintedWithMarks' | ['Hello', 'I am an string', VulnerabilityMarks.XSS_MARK] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTaintedWithMarks' | ['Hello', new Object(), VulnerabilityMarks.XSS_MARK] | { - it[0] - } | { - it[1] - } - 'taintIfInputIsTaintedWithMarks' | ['Hello', new MockTaintable(), VulnerabilityMarks.XSS_MARK] | { - it[0] - } | { - it[1] - } + target | shouldTaint + string('string') | true + stringBuilder('stringBuilder') | true + date() | true + taintable() | true } - void 'test value source for #method'() { + void 'test taintIfTainted keeping ranges'() { given: - final span = Mock(AgentSpan) - tracer.activeSpan() >> span - final reqCtx = Mock(RequestContext) - span.getRequestContext() >> reqCtx - final ctx = new IastRequestContext() - reqCtx.getData(RequestContextSlot.IAST) >> ctx + def (target, input) = suite + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] - when: - module."$method"(source, name, value) + when: 'input is not tainted' + module.taintIfTainted(target, input, true, NOT_MARKED) then: - 1 * tracer.activeSpan() >> span - 1 * span.getRequestContext() >> reqCtx - 1 * reqCtx.getData(RequestContextSlot.IAST) >> ctx - 0 * _ - ctx.getTaintedObjects().get(name) == null - def to = ctx.getTaintedObjects().get(value) - to != null - to.get() == value - to.ranges.size() == 1 - to.ranges[0].start == 0 - to.ranges[0].length == value.length() - to.ranges[0].source == new Source(source, name, value) + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfTainted(target, input, true, NOT_MARKED) + + then: + final tainted = getTaintedObject(target) + if (target instanceof Taintable) { + // only first range is kept + assertTainted(tainted, [taintedFrom.ranges[0]] as Range[]) + } else { + assertTainted(tainted, taintedFrom.ranges) + } where: - method | name | value | source - 'taint' | null | 'value' | SourceTypes.REQUEST_PARAMETER_VALUE - 'taint' | 'name' | 'value' | SourceTypes.REQUEST_PARAMETER_VALUE + suite << taintIfSuite() } - void 'taint with context for #method'() { - setup: - def ctx = new IastRequestContext() + void 'test taintIfTainted keeping ranges with a mark'() { + given: + def (target, input) = suite + Assume.assumeFalse(target instanceof Taintable) // taintable does not support multiple ranges or marks + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + final mark = VulnerabilityMarks.UNVALIDATED_REDIRECT_MARK - when: - module."$method"(ctx as Object, source, name, value) + when: 'input is not tainted' + module.taintIfTainted(target, input, true, mark) then: - ctx.getTaintedObjects().get(name) == null - def to = ctx.getTaintedObjects().get(value) - to != null - to.get() == value - to.ranges.size() == 1 - to.ranges[0].start == 0 - to.ranges[0].length == value.length() - to.ranges[0].source == new Source(source, name, value) - 0 * _ + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfTainted(target, input, true, mark) + + then: + final tainted = getTaintedObject(target) + assertTainted(tainted, taintedFrom.ranges, mark) where: - method | name | value | source - 'taint' | null | "value" | SourceTypes.REQUEST_PATH_PARAMETER - 'taint' | "" | "value" | SourceTypes.REQUEST_PATH_PARAMETER - 'taint' | "param" | "value" | SourceTypes.REQUEST_PATH_PARAMETER + suite << taintIfSuite() } - void 'test taintObject'() { - when: - module.taintObject(origin, toTaint) + void 'test taintIfTainted not keeping ranges'() { + given: + def (target, input) = suite + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + + when: 'input is not tainted' + module.taintIfTainted(target, input, false, NOT_MARKED) then: - assertTainted(toTaint) + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfTainted(target, input, false, NOT_MARKED) + + then: + final tainted = getTaintedObject(target) + assertTainted(tainted, [highestPriorityRange(taintedFrom.ranges)] as Range[]) where: - origin | toTaint - SourceTypes.REQUEST_PARAMETER_VALUE | new Object() - SourceTypes.REQUEST_PARAMETER_VALUE | new MockTaintable() + suite << taintIfSuite() } - void 'test taintObjects[array]'() { - when: - module.taintObjects(origin, new Object[]{ - toTaint - }) + void 'test taintIfTainted not keeping ranges with a mark'() { + given: + def (target, input) = suite + Assume.assumeFalse(target instanceof Taintable) // taintable does not support marks + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + final mark = VulnerabilityMarks.LDAP_INJECTION_MARK + + when: 'input is not tainted' + module.taintIfTainted(target, input, false, mark) then: - assertTainted(toTaint) + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfTainted(target, input, false, mark) + + then: + final tainted = getTaintedObject(target) + assertTainted(tainted, [highestPriorityRange(taintedFrom.ranges)] as Range[], mark) where: - origin | toTaint - SourceTypes.REQUEST_PARAMETER_VALUE | new Object() - SourceTypes.REQUEST_PARAMETER_VALUE | new MockTaintable() + suite << taintIfSuite() } - void 'onJsonFactoryCreateParser'() { + void 'test taintIfAnyTainted keeping ranges'() { given: - final taintedObjects = ctx.getTaintedObjects() - def shouldBeTainted = true + def (target, input) = suite + final inputs = ['test', input].toArray() + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] - def firstParam - if (param1 instanceof String) { - firstParam = addFromTaintFormat(taintedObjects, param1) - objectHolder.add(firstParam) - } else { - firstParam = param1 - } + when: 'input is not tainted' + module.taintIfAnyTainted(target, inputs, true, NOT_MARKED) - def secondParam - if (param2 instanceof String) { - secondParam = addFromTaintFormat(taintedObjects, param2) - objectHolder.add(secondParam) - shouldBeTainted = fromTaintFormat(param2) != null + then: + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfAnyTainted(target, inputs, true, NOT_MARKED) + + then: + final tainted = getTaintedObject(target) + if (target instanceof Taintable) { + // only first range is kept + assertTainted(tainted, [taintedFrom.ranges[0]] as Range[]) } else { - secondParam = param2 + assertTainted(tainted, taintedFrom.ranges) } - if (shouldBeTainted) { - def ranges = new Range[1] - ranges[0] = new Range(0, Integer.MAX_VALUE, new Source((byte) 1, "test", "test"), NOT_MARKED) - taintedObjects.taint(secondParam, ranges) - } + where: + suite << taintIfSuite() + } - when: - module.taintIfInputIsTainted(firstParam, secondParam) + void 'test taintIfAnyTainted keeping ranges with a mark'() { + given: + def (target, input) = suite + Assume.assumeFalse(target instanceof Taintable) // taintable does not support multiple ranges or marks + final inputs = ['test', input].toArray() + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + final mark = VulnerabilityMarks.UNVALIDATED_REDIRECT_MARK + + when: 'input is not tainted' + module.taintIfAnyTainted(target, inputs, true, mark) then: - def to = ctx.getTaintedObjects().get(param1) - if (shouldBeTainted) { - assert to != null - assert to.get() == param1 - if (param1 instanceof String) { - final ranges = to.getRanges() - assert ranges.length == 1 - assert ranges[0].start == 0 - assert ranges[0].length == param1.length() - } else { - final ranges = to.getRanges() - assert ranges.length == 1 - assert ranges[0].start == 0 - assert ranges[0].length == Integer.MAX_VALUE - } - } else { - assert to == null - } + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfAnyTainted(target, inputs, true, mark) + + then: + final tainted = getTaintedObject(target) + assertTainted(tainted, taintedFrom.ranges, mark) where: - param1 | param2 - '123' | new Object() - new Object() | new Object() - new Object() | '123' - new Object() | '==>123<==' + suite << taintIfSuite() } - void 'test first tainted source'() { - when: - final before = module.firstTaintedSource(target) + void 'test taintIfAnyTainted not keeping ranges'() { + given: + def (target, input) = suite + final inputs = ['test', input].toArray() + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + + when: 'input is not tainted' + module.taintIfAnyTainted(target, inputs, false, NOT_MARKED) then: - before == null + assert getTaintedObject(target) == null - when: - module.taintObject(origin, target) - final after = module.firstTaintedSource(target) + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfAnyTainted(target, inputs, false, NOT_MARKED) then: - after.origin == origin + final tainted = getTaintedObject(target) + assertTainted(tainted, [highestPriorityRange(taintedFrom.ranges)] as Range[]) where: - target | origin - 'this is a string' | SourceTypes.REQUEST_PARAMETER_VALUE - new Object() | SourceTypes.REQUEST_PARAMETER_VALUE - new MockTaintable() | SourceTypes.REQUEST_PARAMETER_VALUE + suite << taintIfSuite() } - void 'test taintIfInputIsTaintedWithMarks marks ranges for #mark'() { + void 'test taintIfAnyTainted not keeping ranges with a mark'() { given: - final toTaint = 'this is a string' - final tainted = new Object() - ctx.getTaintedObjects().taint(tainted, previousRanges) - objectHolder.add(toTaint) + def (target, input) = suite + Assume.assumeFalse(target instanceof Taintable) // taintable does not support marks + final inputs = ['test', input].toArray() + final source = taintedSource() + final ranges = [new Range(0, 1, source, NOT_MARKED), new Range(1, 1, source, NOT_MARKED)] as Range[] + final mark = VulnerabilityMarks.LDAP_INJECTION_MARK - when: - module.taintIfInputIsTaintedWithMarks(toTaint, tainted, mark) + when: 'input is not tainted' + module.taintIfAnyTainted(target, inputs, false, mark) then: - final to = ctx.getTaintedObjects().get(toTaint) - final ranges = to.getRanges() - ranges != null && ranges.length == 1 - ranges[0].marks == expected + assert getTaintedObject(target) == null + + when: 'input is tainted' + final taintedFrom = taintObject(input, ranges) + module.taintIfAnyTainted(target, inputs, false, mark) + + then: + final tainted = getTaintedObject(target) + assertTainted(tainted, [highestPriorityRange(taintedFrom.ranges)] as Range[], mark) where: - previousRanges | mark | expected - [new Range(0, Integer.MAX_VALUE, getDefaultSource(), NOT_MARKED)] as Range[] | XSS_MARK | XSS_MARK - [new Range(0, 1, getDefaultSource(), SQL_INJECTION_MARK), new Range(2, 3, getDefaultSource(), NOT_MARKED)] as Range[] | XSS_MARK | XSS_MARK - [new Range(2, 3, getDefaultSource(), NOT_MARKED), new Range(0, 1, getDefaultSource(), SQL_INJECTION_MARK)] as Range[] | XSS_MARK | XSS_MARK - [new Range(2, 3, getDefaultSource(), XPATH_INJECTION_MARK), new Range(0, 1, getDefaultSource(), SQL_INJECTION_MARK)] as Range[] | XSS_MARK | getMarks(XPATH_INJECTION_MARK, XSS_MARK) + suite << taintIfSuite() } void 'test taint deeply'() { @@ -479,81 +356,222 @@ class PropagationModuleTest extends IastModuleImplTestBase { final target = [Hello: " World!", Age: 25] when: - module.taintDeeply(null, SourceTypes.GRPC_BODY, target) + module.taintDeeply(target, SourceTypes.GRPC_BODY, { true }) then: - ctx.getTaintedObjects().empty + final taintedObjects = ctx.taintedObjects + target.keySet().each { key -> + assert taintedObjects.get(key) != null + } + assert taintedObjects.get(target['Hello']) != null + assert taintedObjects.size() == 3 // two keys and one string value + } + + void 'test taint deeply char sequence'() { + given: + final target = stringBuilder('taint me') when: - module.taintDeeply(ctx, SourceTypes.GRPC_BODY, target) + module.taintDeeply(target, SourceTypes.GRPC_BODY, { true }) then: final taintedObjects = ctx.taintedObjects - target.keySet().each { - key -> - assert taintedObjects.get(key) != null + assert taintedObjects.size() == 1 + final tainted = taintedObjects.get(target) + assert tainted != null + final source = tainted.ranges[0].source + assert source.origin == SourceTypes.GRPC_BODY + assert source.value == target.toString() + } + + void 'test is tainted and find source'() { + given: + if (source != null) { + taintObject(target, source) } - assert taintedObjects.get(target['Hello']) != null - assert taintedObjects.size() == 3 // two keys and one string value + + when: + final tainted = module.isTainted(target) + + then: + tainted == (source != null) + + when: + final foundSource = module.findSource(target) + + then: + foundSource == source + + where: + target | source + string('string') | null + stringBuilder('stringBuilder') | null + date() | null + taintable() | null + string('string') | taintedSource() + stringBuilder('stringBuilder') | taintedSource() + date() | taintedSource() + taintable() | taintedSource() + } + + void 'test source names over threshold'() { + given: + final maxSize = Config.get().iastTruncationMaxValueLength + + when: + module.taint(target, SourceTypes.REQUEST_PARAMETER_VALUE) + + then: + final tainted = ctx.getTaintedObjects().get(target) + tainted != null + final sourceValue = tainted.ranges.first().source.value + sourceValue.length() <= target.length() + sourceValue.length() <= maxSize + + where: + target | _ + string((0..Config.get().getIastTruncationMaxValueLength() * 2).join('')) | _ + stringBuilder((0..Config.get().getIastTruncationMaxValueLength() * 2).join('')) | _ } - private E taint(final E toTaint) { - final source = new Source(SourceTypes.REQUEST_PARAMETER_VALUE, null, null) - if (toTaint instanceof Taintable) { - toTaint.$$DD$setSource(source) + void 'test that source names should not make a strong reference over the value'() { + given: + final name = 'name' + + when: + module.taint(name, SourceTypes.REQUEST_PARAMETER_NAME, name) + + then: + final tainted = ctx.getTaintedObjects().get(name) + final taintedName = tainted.ranges[0].source.name + assert !taintedName.is(name) : 'Weak value should not be retained by the source name' + } + + private List> taintIfSuite() { + return [ + Tuple.tuple(string('string'), string('string')), + Tuple.tuple(string('string'), stringBuilder('stringBuilder')), + Tuple.tuple(string('string'), date()), + Tuple.tuple(string('string'), taintable()), + Tuple.tuple(stringBuilder('stringBuilder'), string('string')), + Tuple.tuple(stringBuilder('stringBuilder'), stringBuilder('stringBuilder')), + Tuple.tuple(stringBuilder('stringBuilder'), date()), + Tuple.tuple(stringBuilder('stringBuilder'), taintable()), + Tuple.tuple(date(), string('string')), + Tuple.tuple(date(), stringBuilder('stringBuilder')), + Tuple.tuple(date(), date()), + Tuple.tuple(date(), taintable()), + Tuple.tuple(taintable(), string('string')), + Tuple.tuple(taintable(), stringBuilder('stringBuilder')), + Tuple.tuple(taintable(), date()), + Tuple.tuple(taintable(), taintable()) + ] + } + + private TaintedObject getTaintedObject(final Object target) { + if (target instanceof Taintable) { + final source = (target as Taintable).$$DD$getSource() as Source + return source == null ? null : new TaintedObject(target, Ranges.forObject(source), null) + } + return ctx.getTaintedObjects().get(target) + } + + private TaintedObject taintObject(final Object target, Source source, int mark = NOT_MARKED) { + if (target instanceof Taintable) { + target.$$DD$setSource(source) + } else if (target instanceof CharSequence) { + ctx.getTaintedObjects().taint(target, Ranges.forCharSequence(target, source, mark)) } else { - ctx.taintedObjects.taintInputObject(toTaint, source) - objectHolder.add(toTaint) + ctx.getTaintedObjects().taint(target, Ranges.forObject(source, mark)) } - return toTaint + return getTaintedObject(target) } - private void assertTainted(final Object toTaint) { - final tainted = ctx.getTaintedObjects().get(toTaint) - if (toTaint instanceof Taintable) { - assert tainted == null - assert toTaint.$$DD$getSource() != null + private TaintedObject taintObject(final Object target, Range[] ranges) { + if (target instanceof Taintable) { + target.$$DD$setSource(ranges[0].getSource()) } else { - assert tainted != null + ctx.getTaintedObjects().taint(target, ranges) } + return getTaintedObject(target) } - private void assertNotTainted(final Object toTaint) { - final tainted = ctx.getTaintedObjects().get(toTaint) - assert tainted == null - if (toTaint instanceof Taintable) { - assert toTaint.$$DD$getSource() == null + private String string(String value, Source source = null, int mark = NOT_MARKED) { + final result = new String(value) + if (source != null) { + taintObject(result, source, mark) } + return result } - private static Source getDefaultSource() { - return new Source(SourceTypes.REQUEST_PARAMETER_VALUE, null, null) + private StringBuilder stringBuilder(String value, Source source = null, int mark = NOT_MARKED) { + final result = new StringBuilder(value) + if (source != null) { + taintObject(result, source, mark) + } + return result + } + + private Date date(Source source = null, int mark = NOT_MARKED) { + final result = new Date() + if (source != null) { + taintObject(result, source, mark) + } + return result } - private static int getMarks(int ... marks) { - int result = NOT_MARKED - for (int mark : marks) { - result = result | mark + private Taintable taintable(Source source = null) { + final result = new MockTaintable() + if (source != null) { + taintObject(result, source) } return result } - /** - * Mocking makes the test a bit more confusing*/ - private static final class MockTaintable implements Taintable { + private Source taintedSource(String value = 'value') { + return new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', value) + } + + private static void assertTainted(final TaintedObject tainted, final Range[] ranges, final int mark = NOT_MARKED) { + assert tainted != null + final originalValue = tainted.get() + assert tainted.ranges.length == ranges.length + ranges.eachWithIndex { Range expected, int i -> + final range = tainted.ranges[i] + if (mark == NOT_MARKED) { + assert range.marks == expected.marks + } else { + assert (range.marks & mark) > 0 + } + final source = range.source + assert !source.name.is(originalValue): 'Weak value should not be retained by the source name' + assert !source.value.is(originalValue): 'Weak value should not be retained by the source value' + + final expectedSource = expected.source + assert source.origin == expectedSource.origin + assert source.name == expectedSource.name + assert source.value == expectedSource.value + } + } + private static class MockTaintable implements Taintable { private Source source - @Override @SuppressWarnings('CodeNarc') + @Override Source $$DD$getSource() { return source } - @Override @SuppressWarnings('CodeNarc') + @Override void $$DD$setSource(Source source) { this.source = source } + + @Override + String toString() { + return Taintable.name + } } } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/CommandInjectionModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/CommandInjectionModuleTest.groovy index 9bc9148bb30..30b8adf5166 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/CommandInjectionModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/CommandInjectionModuleTest.groovy @@ -11,7 +11,7 @@ import datadog.trace.api.iast.sink.CommandInjectionModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan import groovy.transform.CompileDynamic -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat @@ -27,7 +27,7 @@ class CommandInjectionModuleTest extends IastModuleImplTestBase { private AgentSpan span def setup() { - module = registerDependencies(new CommandInjectionModuleImpl()) + module = new CommandInjectionModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy index 58009e2d5c7..505776b3ea4 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy @@ -1,13 +1,10 @@ package com.datadog.iast.sink -import com.datadog.iast.HasDependencies import com.datadog.iast.IastModuleImplTestBase import com.datadog.iast.IastRequestContext import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType -import com.datadog.iast.overhead.OverheadController -import datadog.trace.api.Config import datadog.trace.api.gateway.Flow import datadog.trace.api.gateway.IGSpanInfo import datadog.trace.api.gateway.RequestContext @@ -15,9 +12,8 @@ import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import datadog.trace.util.stacktrace.StackWalker -public class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { +class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { private List objectHolder @@ -29,7 +25,7 @@ public class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new HstsMissingHeaderModuleImpl()) + module = new HstsMissingHeaderModuleImpl(dependencies) InstrumentationBridge.registerIastModule(module) objectHolder = [] ctx = new IastRequestContext() @@ -46,14 +42,9 @@ public class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { void 'hsts vulnerability'() { given: Vulnerability savedVul1 - final OverheadController overheadController = Mock(OverheadController) final iastCtx = Mock(IastRequestContext) iastCtx.getxForwardedProto() >> 'https' iastCtx.getContentType() >> "text/html" - final StackWalker stackWalker = Mock(StackWalker) - final dependencies = new HasDependencies.Dependencies( - Config.get(), reporter, overheadController, stackWalker - ) final handler = new RequestEndedHandler(dependencies) final TraceSegment traceSegment = Mock(TraceSegment) final reqCtx = Mock(RequestContext) @@ -96,15 +87,9 @@ public class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { void 'no hsts vulnerability reported'() { given: - Vulnerability savedVul1 - final OverheadController overheadController = Mock(OverheadController) final iastCtx = Mock(IastRequestContext) iastCtx.getxForwardedProto() >> 'https' iastCtx.getContentType() >> "text/html" - final StackWalker stackWalker = Mock(StackWalker) - final dependencies = new HasDependencies.Dependencies( - Config.get(), reporter, overheadController, stackWalker - ) final handler = new RequestEndedHandler(dependencies) final TraceSegment traceSegment = Mock(TraceSegment) final reqCtx = Mock(RequestContext) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HttpResponseHeaderModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HttpResponseHeaderModuleTest.groovy index f884b16f60d..f3e30e1e180 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HttpResponseHeaderModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HttpResponseHeaderModuleTest.groovy @@ -25,13 +25,13 @@ class HttpResponseHeaderModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new HttpResponseHeaderModuleImpl()) + module = new HttpResponseHeaderModuleImpl(dependencies) InstrumentationBridge.registerIastModule(module) InstrumentationBridge.registerIastModule(new InsecureCookieModuleImpl()) InstrumentationBridge.registerIastModule(new NoHttpOnlyCookieModuleImpl()) InstrumentationBridge.registerIastModule(new NoSameSiteCookieModuleImpl()) - InstrumentationBridge.registerIastModule(new HstsMissingHeaderModuleImpl()) - InstrumentationBridge.registerIastModule(new UnvalidatedRedirectModuleImpl()) + InstrumentationBridge.registerIastModule(new HstsMissingHeaderModuleImpl(dependencies)) + InstrumentationBridge.registerIastModule(new UnvalidatedRedirectModuleImpl(dependencies)) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureCookieModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureCookieModuleTest.groovy index bf74a709519..b160f37a28b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureCookieModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureCookieModuleTest.groovy @@ -24,7 +24,7 @@ class InsecureCookieModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new HttpResponseHeaderModuleImpl()) + module = new HttpResponseHeaderModuleImpl(dependencies) InstrumentationBridge.registerIastModule(new InsecureCookieModuleImpl()) objectHolder = [] ctx = new IastRequestContext() diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/LdapInjectionModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/LdapInjectionModuleTest.groovy index 2159276a958..682e8dfef39 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/LdapInjectionModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/LdapInjectionModuleTest.groovy @@ -11,7 +11,7 @@ import datadog.trace.api.iast.sink.LdapInjectionModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan import groovy.transform.CompileDynamic -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat @@ -27,7 +27,7 @@ class LdapInjectionModuleTest extends IastModuleImplTestBase { private AgentSpan span def setup() { - module = registerDependencies(new LdapInjectionModuleImpl()) + module = new LdapInjectionModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoHttpCookieModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoHttpCookieModuleTest.groovy index 28fda1aeecb..a246f30ee9f 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoHttpCookieModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoHttpCookieModuleTest.groovy @@ -24,7 +24,7 @@ class NoHttpCookieModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new HttpResponseHeaderModuleImpl()) + module = new HttpResponseHeaderModuleImpl(dependencies) InstrumentationBridge.registerIastModule(new NoHttpOnlyCookieModuleImpl()) objectHolder = [] ctx = new IastRequestContext() diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoSameSiteCookieModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoSameSiteCookieModuleTest.groovy index f4a97aaffa1..4e32a9b7722 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoSameSiteCookieModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/NoSameSiteCookieModuleTest.groovy @@ -24,7 +24,7 @@ class NoSameSiteCookieModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new HttpResponseHeaderModuleImpl()) + module = new HttpResponseHeaderModuleImpl(dependencies) InstrumentationBridge.registerIastModule(new NoSameSiteCookieModuleImpl()) objectHolder = [] ctx = new IastRequestContext() diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/PathTraversalModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/PathTraversalModuleTest.groovy index d55e2975e7e..6c25122e393 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/PathTraversalModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/PathTraversalModuleTest.groovy @@ -10,7 +10,7 @@ import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.sink.PathTraversalModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.fromTaintFormat import static com.datadog.iast.taint.TaintUtils.getStringFromTaintFormat @@ -27,7 +27,7 @@ class PathTraversalModuleTest extends IastModuleImplTestBase { private IastRequestContext ctx def setup() { - module = registerDependencies(new PathTraversalModuleImpl()) + module = new PathTraversalModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SqlInjectionModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SqlInjectionModuleTest.groovy index 3041c928a2a..69b30fe5ef3 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SqlInjectionModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SqlInjectionModuleTest.groovy @@ -10,7 +10,7 @@ import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.sink.SqlInjectionModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat import static datadog.trace.api.iast.sink.SqlInjectionModule.DATABASE_PARAMETER @@ -24,7 +24,7 @@ class SqlInjectionModuleTest extends IastModuleImplTestBase { private IastRequestContext ctx def setup() { - module = registerDependencies(new SqlInjectionModuleImpl()) + module = new SqlInjectionModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SsrfModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SsrfModuleTest.groovy index 6884da0e616..c379cea4b8f 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SsrfModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SsrfModuleTest.groovy @@ -6,6 +6,7 @@ import com.datadog.iast.model.Range import com.datadog.iast.model.Source import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import com.datadog.iast.taint.Ranges import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.SourceTypes @@ -24,7 +25,7 @@ class SsrfModuleTest extends IastModuleImplTestBase { private AgentSpan span def setup() { - module = registerDependencies(new SsrfModuleImpl()) + module = new SsrfModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { @@ -82,6 +83,6 @@ class SsrfModuleTest extends IastModuleImplTestBase { } private void taint(final Object value) { - ctx.getTaintedObjects().taintInputObject(value, new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', value.toString())) + ctx.getTaintedObjects().taint(value, Ranges.forObject(new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', value.toString()))) } } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy new file mode 100644 index 00000000000..ecf8aaa3bd0 --- /dev/null +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/StacktraceLeakModuleTest.groovy @@ -0,0 +1,51 @@ +package com.datadog.iast.sink + +import com.datadog.iast.IastModuleImplTestBase +import com.datadog.iast.model.Evidence +import com.datadog.iast.model.Vulnerability +import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.iast.sink.StacktraceLeakModule +import datadog.trace.bootstrap.instrumentation.api.AgentSpan + +class StacktraceLeakModuleTest extends IastModuleImplTestBase { + private StacktraceLeakModule module + + def setup() { + module = new StacktraceLeakModuleImpl(dependencies) + } + + void 'iast stacktrace leak module'() { + given: + final spanId = 123456 + final span = Mock(AgentSpan) + + def throwable = new Exception('some exception') + def moduleName = 'moduleName' + def className = 'className' + def methodName = 'methodName' + + when: + module.onStacktraceLeak(throwable, moduleName, className, methodName) + + then: + 1 * tracer.activeSpan() >> span + 1 * span.getSpanId() >> spanId + 1 * span.getServiceName() + 1 * reporter.report(_, _) >> { args -> + Vulnerability vuln = args[1] as Vulnerability + assert vuln != null + assert vuln.getType() == VulnerabilityType.STACKTRACE_LEAK + assert vuln.getEvidence() == new Evidence('ExceptionHandler in moduleName \r\nthrown java.lang.Exception') + assert vuln.getLocation() != null + } + 0 * _ + } + + void 'iast stacktrace leak no exception'() { + when: + module.onStacktraceLeak(null, null, null, null) + + then: + 0 * _ + } +} diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/TrustBoundaryViolationModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/TrustBoundaryViolationModuleTest.groovy index e50a7b26674..a3fce16d195 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/TrustBoundaryViolationModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/TrustBoundaryViolationModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.IastRequestContext import com.datadog.iast.model.Source import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import com.datadog.iast.taint.Ranges import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge @@ -24,7 +25,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new TrustBoundaryViolationModuleImpl()) + module = new TrustBoundaryViolationModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { @@ -60,7 +61,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { given: Vulnerability savedVul final name = "name" - ctx.getTaintedObjects().taintInputString(name, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(name, Ranges.forCharSequence(name, new Source(SourceTypes.NONE, null, null))) when: module.onSessionValue(name, "value") @@ -77,7 +78,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { Vulnerability savedVul final name = "name" final badValue = "theValue" - ctx.getTaintedObjects().taintInputString(badValue, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(badValue, Ranges.forCharSequence(badValue, new Source(SourceTypes.NONE, null, null))) when: module.onSessionValue(name, badValue) @@ -95,7 +96,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { Vulnerability savedVul final name = "name" final badValue = "badValue" - ctx.getTaintedObjects().taintInputString(badValue, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(badValue, Ranges.forCharSequence(badValue, new Source(SourceTypes.NONE, null, null))) final values = ["A", "B", badValue] when: @@ -113,7 +114,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { Vulnerability savedVul final name = "name" final badValue = "badValue" - ctx.getTaintedObjects().taintInputString(badValue, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(badValue, Ranges.forCharSequence(badValue, new Source(SourceTypes.NONE, null, null))) final values = new String[3] values[0] = "A" values[1] = "B" @@ -134,7 +135,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { Vulnerability savedVul final name = "name" final badValue = "badValue" - ctx.getTaintedObjects().taintInputString(badValue, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(badValue, Ranges.forCharSequence(badValue, new Source(SourceTypes.NONE, null, null))) final values = new LinkedHashMap() values.put("A", "A") values.put("B", "B") @@ -155,7 +156,7 @@ class TrustBoundaryViolationModuleTest extends IastModuleImplTestBase { Vulnerability savedVul final name = "name" final badValue = "badValue" - ctx.getTaintedObjects().taintInputString(badValue, new Source(SourceTypes.NONE, null, null)) + ctx.getTaintedObjects().taint(badValue, Ranges.forCharSequence(badValue, new Source(SourceTypes.NONE, null, null))) final value = new VisitableClass(name: badValue) when: diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/UnvalidatedRedirectModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/UnvalidatedRedirectModuleTest.groovy index 708b665a902..5dabb12cc52 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/UnvalidatedRedirectModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/UnvalidatedRedirectModuleTest.groovy @@ -6,6 +6,7 @@ import com.datadog.iast.model.Range import com.datadog.iast.model.Source import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import com.datadog.iast.taint.Ranges import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge @@ -14,7 +15,7 @@ import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.sink.UnvalidatedRedirectModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat @@ -27,7 +28,7 @@ class UnvalidatedRedirectModuleTest extends IastModuleImplTestBase { private IastRequestContext ctx def setup() { - module = registerDependencies(new UnvalidatedRedirectModuleImpl()) + module = new UnvalidatedRedirectModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { @@ -64,7 +65,7 @@ class UnvalidatedRedirectModuleTest extends IastModuleImplTestBase { void 'iast module detects URI redirect (#value)'(final URI value, final String expected) { setup: - ctx.taintedObjects.taintInputObject(value, new Source(SourceTypes.NONE, null, null)) + ctx.taintedObjects.taint(value, Ranges.forObject(new Source(SourceTypes.NONE, null, null))) when: module.onURIRedirect(value) @@ -107,7 +108,7 @@ class UnvalidatedRedirectModuleTest extends IastModuleImplTestBase { void 'if onHeader receives a Location header call onRedirect'() { setup: - final urm = Spy(UnvalidatedRedirectModuleImpl) + final urm = Spy(new UnvalidatedRedirectModuleImpl(dependencies)) InstrumentationBridge.registerIastModule(urm) when: diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakCipherModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakCipherModuleTest.groovy index 5b71caa24d4..802df43ec28 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakCipherModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakCipherModuleTest.groovy @@ -12,7 +12,7 @@ class WeakCipherModuleTest extends IastModuleImplTestBase { private WeakCipherModule module def setup() { - module = registerDependencies(new WeakCipherModuleImpl()) + module = new WeakCipherModuleImpl(dependencies) } void 'iast module vulnerable cipher algorithm'(final String algorithm){ diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakHashModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakHashModuleTest.groovy index b3e83664558..2cfebb744e0 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakHashModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakHashModuleTest.groovy @@ -12,7 +12,7 @@ class WeakHashModuleTest extends IastModuleImplTestBase { private WeakHashModule module def setup() { - module = registerDependencies(new WeakHashModuleImpl()) + module = new WeakHashModuleImpl(dependencies) } void 'iast module vulnerable hash algorithm'(final String algorithm){ diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakRandomnessModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakRandomnessModuleTest.groovy index a7401b01a5f..fa3db3c8f19 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakRandomnessModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/WeakRandomnessModuleTest.groovy @@ -14,7 +14,7 @@ class WeakRandomnessModuleTest extends IastModuleImplTestBase { private AgentSpan span def setup() { - module = registerDependencies(new WeakRandomnessModuleImpl()) + module = new WeakRandomnessModuleImpl(dependencies) span = Mock(AgentSpan) { getSpanId() >> 123456 } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy index dfda4e18cdb..a8d377e968b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy @@ -1,13 +1,10 @@ package com.datadog.iast.sink -import com.datadog.iast.HasDependencies import com.datadog.iast.IastModuleImplTestBase import com.datadog.iast.IastRequestContext import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType -import com.datadog.iast.overhead.OverheadController -import datadog.trace.api.Config import datadog.trace.api.gateway.Flow import datadog.trace.api.gateway.IGSpanInfo import datadog.trace.api.gateway.RequestContext @@ -15,7 +12,6 @@ import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import datadog.trace.util.stacktrace.StackWalker public class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { @@ -29,7 +25,7 @@ public class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { def setup() { InstrumentationBridge.clearIastModules() - module = registerDependencies(new XContentTypeModuleImpl()) + module = new XContentTypeModuleImpl(dependencies) InstrumentationBridge.registerIastModule(module) objectHolder = [] ctx = new IastRequestContext() @@ -46,13 +42,8 @@ public class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { void 'x content options sniffing vulnerability'() { given: Vulnerability savedVul1 - final OverheadController overheadController = Mock(OverheadController) final iastCtx = Mock(IastRequestContext) iastCtx.getContentType() >> "text/html" - final StackWalker stackWalker = Mock(StackWalker) - final dependencies = new HasDependencies.Dependencies( - Config.get(), reporter, overheadController, stackWalker - ) final handler = new RequestEndedHandler(dependencies) final TraceSegment traceSegment = Mock(TraceSegment) final reqCtx = Mock(RequestContext) @@ -93,15 +84,9 @@ public class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { void 'no x content options sniffing reported'() { given: - Vulnerability savedVul1 - final OverheadController overheadController = Mock(OverheadController) final iastCtx = Mock(IastRequestContext) iastCtx.getxForwardedProto() >> 'https' iastCtx.getContentType() >> "text/html" - final StackWalker stackWalker = Mock(StackWalker) - final dependencies = new HasDependencies.Dependencies( - Config.get(), reporter, overheadController, stackWalker - ) final handler = new RequestEndedHandler(dependencies) final TraceSegment traceSegment = Mock(TraceSegment) final reqCtx = Mock(RequestContext) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XPathInjectionModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XPathInjectionModuleTest.groovy index 6a44afd10b8..3638b6f431b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XPathInjectionModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XPathInjectionModuleTest.groovy @@ -10,7 +10,7 @@ import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.sink.XPathInjectionModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat @@ -23,7 +23,7 @@ class XPathInjectionModuleTest extends IastModuleImplTestBase { private IastRequestContext ctx def setup() { - module = registerDependencies(new XPathInjectionModuleImpl()) + module = new XPathInjectionModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XssModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XssModuleTest.groovy index ff0340a99df..bbebbceae97 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XssModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XssModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.IastRequestContext import com.datadog.iast.model.Source import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import com.datadog.iast.taint.Ranges import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.iast.SourceTypes @@ -12,7 +13,7 @@ import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.api.iast.sink.XssModule import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.TaintUtils.addFromTaintFormat import static com.datadog.iast.taint.TaintUtils.taintFormat @@ -25,7 +26,7 @@ class XssModuleTest extends IastModuleImplTestBase { private IastRequestContext ctx def setup() { - module = registerDependencies(new XssModuleImpl()) + module = new XssModuleImpl(dependencies) objectHolder = [] ctx = new IastRequestContext() final reqCtx = Mock(RequestContext) { @@ -65,7 +66,7 @@ class XssModuleTest extends IastModuleImplTestBase { void 'module detects char[] XSS'() { setup: if (tainted) { - ctx.taintedObjects.taintInputObject(buf, new Source(SourceTypes.NONE, '', ''), mark) + ctx.taintedObjects.taint(buf, Ranges.forObject(new Source(SourceTypes.NONE, '', ''), mark)) } when: diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/source/WebModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/source/WebModuleTest.groovy deleted file mode 100644 index 1e93f5e3fb4..00000000000 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/source/WebModuleTest.groovy +++ /dev/null @@ -1,130 +0,0 @@ -package com.datadog.iast.source - -import com.datadog.iast.IastModuleImplTestBase -import com.datadog.iast.IastRequestContext -import com.datadog.iast.model.Source -import datadog.trace.api.gateway.RequestContext -import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.api.iast.SourceTypes -import datadog.trace.api.iast.source.WebModule -import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import groovy.transform.CompileDynamic - -@CompileDynamic -class WebModuleTest extends IastModuleImplTestBase { - - private WebModule module - - def setup() { - module = new WebModuleImpl() - } - - void 'test #method: null or empty'() { - when: - module."$method"(*args) - - then: - 0 * _ - - where: - method | args - 'onParameterNames' | [null] - 'onParameterNames' | [[]] - 'onParameterValues' | [null, null] - 'onParameterValues' | ['', []] - 'onParameterValues' | [null, null as String[]] - 'onParameterValues' | ['', [] as String[]] - 'onParameterValues' | [[:]] - 'onHeaderNames' | [null] - 'onHeaderNames' | [[]] - 'onHeaderValues' | [null, null] - 'onHeaderValues' | ['', []] - 'onCookieNames' | [null] - 'onCookieNames' | [[]] - } - - void 'test #method: without span'() { - when: - module."$method"(*args) - - then: - 1 * tracer.activeSpan() >> null - 0 * _ - - where: - method | args - 'onParameterNames' | [['param']] - 'onParameterValues' | ['name', ['value']] - 'onParameterValues' | ['name', ['value'] as String[]] - 'onParameterValues' | [[name: ['value'] as String[]]] - 'onHeaderNames' | [['header']] - 'onHeaderValues' | ['name', ['value']] - 'onCookieNames' | [['name']] - 'onNamed' | ['name', ['v1'], (byte)0] - 'onNamed' | ['name', ['v1'] as String[], (byte)0] - 'onNamed' | [[name: 'v1'], (byte)0] - } - - void 'onNamed #variant'() { - given: - final span = Mock(AgentSpan) - tracer.activeSpan() >> span - final reqCtx = Mock(RequestContext) - span.getRequestContext() >> reqCtx - final ctx = new IastRequestContext() - reqCtx.getData(RequestContextSlot.IAST) >> ctx - - when: - module.onNamed(*args, SourceTypes.REQUEST_PARAMETER_NAME) - - then: - 1 * tracer.activeSpan() >> span - 1 * span.getRequestContext() >> reqCtx - 1 * reqCtx.getData(RequestContextSlot.IAST) >> ctx - 0 * _ - def tos = ctx.taintedObjects - def to = tos.get('foo') - to.ranges.size() == 1 - to.ranges[0].start == 0 - to.ranges[0].length == 3 - to.ranges[0].source == new Source(SourceTypes.REQUEST_PARAMETER_NAME, 'var', 'foo') - - where: - variant | args - 'collection' | ['var', ['foo']] - 'array' | ['var', ['foo'] as String[]] - 'map' | [[var: ['foo'] as String[]]] - } - - void 'test #method'() { - given: - final span = Mock(AgentSpan) - tracer.activeSpan() >> span - final reqCtx = Mock(RequestContext) - span.getRequestContext() >> reqCtx - final ctx = new IastRequestContext() - reqCtx.getData(RequestContextSlot.IAST) >> ctx - - when: - module."$method"([name]) - - then: - 1 * tracer.activeSpan() >> span - 1 * span.getRequestContext() >> reqCtx - 1 * reqCtx.getData(RequestContextSlot.IAST) >> ctx - 0 * _ - def to = ctx.getTaintedObjects().get(name) - to != null - to.get() == name - to.ranges.size() == 1 - to.ranges[0].start == 0 - to.ranges[0].length == name.length() - to.ranges[0].source == new Source(source, name, name) - - where: - method | name | source - 'onParameterNames' | 'param' | SourceTypes.REQUEST_PARAMETER_NAME - 'onHeaderNames' | 'param' | SourceTypes.REQUEST_HEADER_NAME - 'onCookieNames' | 'param' | SourceTypes.REQUEST_COOKIE_NAME - } -} diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/RangesTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/RangesTest.groovy index 3c80b068b92..60525afa7e2 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/RangesTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/RangesTest.groovy @@ -8,7 +8,7 @@ import datadog.trace.api.iast.SourceTypes import datadog.trace.api.iast.VulnerabilityMarks import datadog.trace.test.util.DDSpecification -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static com.datadog.iast.taint.Ranges.mergeRanges import static com.datadog.iast.taint.Ranges.rangesProviderFor import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_NAME diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintUtils.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintUtils.groovy index 7db1a75c673..88b21e9406b 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintUtils.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintUtils.groovy @@ -4,7 +4,7 @@ import com.datadog.iast.model.Range import com.datadog.iast.model.Source import datadog.trace.api.iast.SourceTypes -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED class TaintUtils { @@ -76,7 +76,7 @@ class TaintUtils { if (value instanceof String) { return addFromTaintFormat(tos, value as String) } - tos.taintInputObject(value, new Source(SourceTypes.NONE, null, null)) + tos.taint(value, Ranges.forObject(new Source(SourceTypes.NONE, null, null))) return value } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectTest.groovy index cb0351bf0b0..d809f5b8b69 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectTest.groovy @@ -7,7 +7,7 @@ import com.datadog.iast.model.Range import java.lang.ref.ReferenceQueue -import static com.datadog.iast.model.Range.NOT_MARKED +import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_NAME class TaintedObjectTest extends Specification { diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLazyTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLazyTest.groovy deleted file mode 100644 index 94c6f4b0171..00000000000 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLazyTest.groovy +++ /dev/null @@ -1,70 +0,0 @@ -package com.datadog.iast.taint - -import com.datadog.iast.IastModuleImplTestBase -import com.datadog.iast.IastRequestContext -import com.datadog.iast.model.Source -import datadog.trace.api.gateway.RequestContext -import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.bootstrap.instrumentation.api.AgentSpan - -class TaintedObjectsLazyTest extends IastModuleImplTestBase { - - private TaintedObjects delegate = Mock(TaintedObjects) - private IastRequestContext iastCtx - private RequestContext reqCtx - private AgentSpan span - - void setup() { - delegate = Mock(TaintedObjects) - iastCtx = Mock(IastRequestContext) - iastCtx.getTaintedObjects() >> delegate - reqCtx = Mock(RequestContext) - reqCtx.getData(RequestContextSlot.IAST) >> iastCtx - span = Mock(AgentSpan) - span.getRequestContext() >> reqCtx - } - - void 'get non lazy instance'() { - when: - final to = TaintedObjects.activeTaintedObjects() - - then: - 1 * tracer.activeSpan() >> span - !(to instanceof TaintedObjects.LazyTaintedObjects) - } - - void 'get lazy objects instance'() { - when: - final to = TaintedObjects.activeTaintedObjects(true) - - then: - to instanceof TaintedObjects.LazyTaintedObjects - - when: - to.&"$method".call(args as Object[]) - - then: 'first time the active tainted objects if fetched' - 1 * delegate._ - 1 * tracer.activeSpan() >> span - - when: - to.&"$method".call(args as Object[]) - - then: 'the active tainted objets is already fetched' - 1 * delegate._ - 0 * _ - - where: - method | args - 'getEstimatedSize' | [] - 'isFlat' | [] - 'taintInputString' | ['', new Source((byte) 0, null, null)] - 'taintInputCharSequence' | ['', new Source((byte) 0, null, null)] - 'taintInputObject' | ['', new Source((byte) 0, null, null)] - 'taint' | ['', Ranges.EMPTY] - 'get' | [''] - 'release' | [] - 'count' | [] - 'iterator' | [] - } -} diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLogTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLogTest.groovy index fd50a1f9de3..ea01efa3989 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLogTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsLogTest.groovy @@ -3,7 +3,6 @@ package com.datadog.iast.taint import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import com.datadog.iast.IastSystem -import com.datadog.iast.model.Range import com.datadog.iast.model.Source import datadog.trace.api.iast.SourceTypes import datadog.trace.test.util.DDSpecification @@ -35,7 +34,7 @@ class TaintedObjectsLogTest extends DDSpecification { final value = "A" when: - def tainted = taintedObjects.taintInputString(value, new Source(SourceTypes.NONE, null, null)) + def tainted = taintedObjects.taint(value, Ranges.forCharSequence(value, new Source(SourceTypes.NONE, null, null))) then: noExceptionThrown() @@ -54,10 +53,8 @@ class TaintedObjectsLogTest extends DDSpecification { IastSystem.DEBUG = true logger.level = Level.ALL TaintedObjects taintedObjects = TaintedObjects.acquire() - taintedObjects.taint('A', [new Range(0, 1, new Source(SourceTypes.NONE, null, null), Range.NOT_MARKED)] as Range[]) - taintedObjects.taintInputString('B', new Source(SourceTypes.REQUEST_PARAMETER_NAME, 'test', 'value')) - taintedObjects.taintInputCharSequence(new StringBuffer('B'), new Source(SourceTypes.REQUEST_PARAMETER_NAME, 'test', 'value')) - taintedObjects.taintInputObject(new Date(), new Source(SourceTypes.REQUEST_HEADER_VALUE, 'test', 'value')) + final obj = 'A' + taintedObjects.taint(obj, Ranges.forCharSequence(obj, new Source(SourceTypes.NONE, null, null))) when: taintedObjects.release() @@ -65,5 +62,21 @@ class TaintedObjectsLogTest extends DDSpecification { then: noExceptionThrown() } + + void "test TaintedObjects api calls"() { + given: + IastSystem.DEBUG = true + logger.level = Level.ALL + TaintedObjects taintedObjects = TaintedObjects.acquire() + final obj = 'A' + + when: + taintedObjects.taint(obj, Ranges.forCharSequence(obj, new Source(SourceTypes.NONE, null, null))) + + then: + taintedObjects.size() == 1 + taintedObjects.iterator().size() == 1 + !taintedObjects.flat + } } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsNoOpTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsNoOpTest.groovy index 304a2e7187b..c8f1f91fe86 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsNoOpTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/TaintedObjectsNoOpTest.groovy @@ -12,15 +12,12 @@ class TaintedObjectsNoOpTest extends Specification { final toTaint = 'test' when: - final taintedString = instance.taintInputString(toTaint, new Source(0 as byte, 'test', 'test')) - final taintedCharSequence = instance.taintInputCharSequence(toTaint, new Source(0 as byte, 'test', 'test')) - final taintedObject = instance.taintInputObject(toTaint, new Source(0 as byte, 'test', 'test')) + final tainted = instance.taint(toTaint, Ranges.forCharSequence(toTaint, new Source(0 as byte, 'test', 'test'))) then: - taintedString == null - taintedCharSequence == null - taintedObject == null + tainted == null instance.get(toTaint) == null + instance.count() == 0 instance.estimatedSize == 0 instance.size() == 0 !instance.flat diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/AbstractTelemetryCallbackTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/AbstractTelemetryCallbackTest.groovy index aefaf6c46a1..92f03e910d2 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/AbstractTelemetryCallbackTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/AbstractTelemetryCallbackTest.groovy @@ -1,7 +1,7 @@ package com.datadog.iast.telemetry -import com.datadog.iast.HasDependencies.Dependencies +import com.datadog.iast.Dependencies import com.datadog.iast.IastModuleImplTestBase import datadog.trace.api.Config import datadog.trace.api.gateway.RequestContext diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/TelemetryRequestEndedHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/TelemetryRequestEndedHandlerTest.groovy index 24e2ef187b5..30a61959d98 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/TelemetryRequestEndedHandlerTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/TelemetryRequestEndedHandlerTest.groovy @@ -3,6 +3,7 @@ package com.datadog.iast.telemetry import com.datadog.iast.IastRequestContext import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Source +import com.datadog.iast.taint.Ranges import com.datadog.iast.taint.TaintedObjects import com.datadog.iast.telemetry.taint.TaintedObjectsWithTelemetry import datadog.trace.api.gateway.RequestContextSlot @@ -41,7 +42,7 @@ class TelemetryRequestEndedHandlerTest extends AbstractTelemetryCallbackTest { final handler = new TelemetryRequestEndedHandler(delegate) final toTaint = 'hello' final source = new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value') - iastCtx.taintedObjects.taintInputString(toTaint, source) + iastCtx.taintedObjects.taint(toTaint, Ranges.forCharSequence(toTaint, source)) when: handler.apply(reqCtx, span) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetryTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetryTest.groovy index bca55f07c11..c20ca32dace 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetryTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/telemetry/taint/TaintedObjectsWithTelemetryTest.groovy @@ -2,13 +2,11 @@ package com.datadog.iast.telemetry.taint import com.datadog.iast.IastRequestContext import com.datadog.iast.model.Range -import com.datadog.iast.model.Source import com.datadog.iast.taint.Ranges import com.datadog.iast.taint.TaintedObject import com.datadog.iast.taint.TaintedObjects import datadog.trace.api.gateway.RequestContext import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.api.iast.SourceTypes import datadog.trace.api.iast.telemetry.IastMetric import datadog.trace.api.iast.telemetry.IastMetricCollector import datadog.trace.api.iast.telemetry.Verbosity @@ -74,13 +72,11 @@ class TaintedObjectsWithTelemetryTest extends DDSpecification { final taintedObjects = TaintedObjectsWithTelemetry.build(verbosity, Mock(TaintedObjects)) when: - taintedObjects.taintInputString('test', new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value')) - taintedObjects.taintInputObject(new Date(), new Source(SourceTypes.REQUEST_HEADER_VALUE, 'name', 'value')) taintedObjects.taint('test', new Range[0]) then: if (IastMetric.EXECUTED_TAINTED.isEnabled(verbosity)) { - 3 * mockCollector.addMetric(IastMetric.EXECUTED_TAINTED, _, 1) // two calls with one element + 1 * mockCollector.addMetric(IastMetric.EXECUTED_TAINTED, _, 1) } else { 0 * mockCollector.addMetric } diff --git a/dd-java-agent/agent-iast/src/test/resources/redaction/evidence-redaction-suite.yml b/dd-java-agent/agent-iast/src/test/resources/redaction/evidence-redaction-suite.yml index 485afe29f2c..cf92538ae54 100644 --- a/dd-java-agent/agent-iast/src/test/resources/redaction/evidence-redaction-suite.yml +++ b/dd-java-agent/agent-iast/src/test/resources/redaction/evidence-redaction-suite.yml @@ -247,6 +247,38 @@ suite: } ] } + - type: 'VULNERABILITIES' + description: 'Query with single quoted string literal and null source' + input: > + [ + { + "type": "SQL_INJECTION", + "evidence": { + "value": "select * from users where username = 'user'", + "ranges": [ + { "start" : 38, "length" : 4, "source": { "origin": "http.request.body" } } + ] + } + } + ] + expected: > + { + "sources": [ + { "origin": "http.request.body" } + ], + "vulnerabilities": [ + { + "type": "SQL_INJECTION", + "evidence": { + "valueParts": [ + { "value": "select * from users where username = '" }, + { "redacted": true, "source": 0, "pattern": "****" }, + { "value": "'" } + ] + } + } + ] + } - type: 'VULNERABILITIES' description: '$1 query with double quoted string literal $2' parameters: @@ -1753,6 +1785,40 @@ suite: ] } + + - type: 'VULNERABILITIES' + description: 'Tainted range based redaction - with null source ' + input: > + [ + { + "type": "XSS", + "evidence": { + "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", + "ranges": [ + { "start" : 123, "length" : 3, "source": { "origin": "http.request.body" } } + ] + } + } + ] + expected: > + { + "sources": [ + { "origin": "http.request.body" } + ], + "vulnerabilities": [ + { + "type": "XSS", + "evidence": { + "valueParts": [ + { "redacted": true }, + { "source": 0, "value": "XSS" }, + { "redacted": true } + ] + } + } + ] + } + - type: 'VULNERABILITIES' description: 'Tainted range based redaction - multiple ranges' input: > diff --git a/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/IastRequestContextPreparationTrait.groovy b/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/IastRequestContextPreparationTrait.groovy index ffa956d5ded..6e0b849f6ca 100644 --- a/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/IastRequestContextPreparationTrait.groovy +++ b/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/IastRequestContextPreparationTrait.groovy @@ -3,16 +3,9 @@ package com.datadog.iast.test import com.datadog.iast.IastRequestContext import com.datadog.iast.IastSystem import com.datadog.iast.model.Range -import com.datadog.iast.model.Source import com.datadog.iast.taint.TaintedObject import com.datadog.iast.taint.TaintedObjects -import datadog.trace.api.gateway.CallbackProvider -import datadog.trace.api.gateway.EventType -import datadog.trace.api.gateway.Events -import datadog.trace.api.gateway.Flow -import datadog.trace.api.gateway.IGSpanInfo -import datadog.trace.api.gateway.RequestContext -import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.api.gateway.* import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.bootstrap.instrumentation.api.AgentTracer import org.slf4j.Logger @@ -68,22 +61,12 @@ trait IastRequestContextPreparationTrait { List objects = Collections.synchronizedList([]) - TaintedObject taintInputString(String obj, Source source, int mark) { - objects << obj - this.delegate.taintInputString(obj, source, mark) - logTaint obj - } - - TaintedObject taintInputObject(Object obj, Source source, int mark) { - objects << obj - this.delegate.taintInputObject(obj, source, mark) - logTaint obj - } - + @Override TaintedObject taint(Object obj, Range[] ranges) { objects << obj - this.delegate.taintInputString(obj, ranges) + final tainted = this.delegate.taint(obj, ranges) logTaint obj + return tainted } private final static Logger LOGGER = LoggerFactory.getLogger("map tainted objects") diff --git a/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/NoopOverheadController.groovy b/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/NoopOverheadController.groovy index 74964a13634..32152663dc1 100644 --- a/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/NoopOverheadController.groovy +++ b/dd-java-agent/agent-iast/src/testFixtures/groovy/com/datadog/iast/test/NoopOverheadController.groovy @@ -2,6 +2,7 @@ package com.datadog.iast.test import com.datadog.iast.overhead.Operation import com.datadog.iast.overhead.OverheadController +import com.github.javaparser.quality.Nullable import datadog.trace.bootstrap.instrumentation.api.AgentSpan import groovy.transform.CompileStatic @@ -18,12 +19,12 @@ class NoopOverheadController implements OverheadController { } @Override - boolean hasQuota(Operation operation, AgentSpan span) { + boolean hasQuota(Operation operation, @Nullable AgentSpan span) { true } @Override - boolean consumeQuota(Operation operation, AgentSpan span) { + boolean consumeQuota(Operation operation, @Nullable AgentSpan span) { true } diff --git a/dd-java-agent/agent-jmxfetch/integrations-core b/dd-java-agent/agent-jmxfetch/integrations-core index 03aed80d105..d211ef6ec4c 160000 --- a/dd-java-agent/agent-jmxfetch/integrations-core +++ b/dd-java-agent/agent-jmxfetch/integrations-core @@ -1 +1 @@ -Subproject commit 03aed80d105aa81b047e74c6da086165cac5ff6f +Subproject commit d211ef6ec4c9584df4b9f430dd10382f169d4727 diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java index 958dfe6f14d..99e9d2fbd97 100644 --- a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLogger.java @@ -329,7 +329,7 @@ public void formatLog(LogLevel level, Marker marker, String format, Object arg) } FormattingTuple tuple = MessageFormatter.format(format, arg); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } public void formatLog(LogLevel level, Marker marker, String format, Object arg1, Object arg2) { @@ -338,7 +338,7 @@ public void formatLog(LogLevel level, Marker marker, String format, Object arg1, } FormattingTuple tuple = MessageFormatter.format(format, arg1, arg2); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } public void formatLog(LogLevel level, Marker marker, String format, Object... arguments) { @@ -347,17 +347,17 @@ public void formatLog(LogLevel level, Marker marker, String format, Object... ar } FormattingTuple tuple = MessageFormatter.arrayFormat(format, arguments); - alwaysLog(level, marker, tuple.getMessage(), tuple.getThrowable()); + alwaysLog(level, marker, format, tuple.getMessage(), tuple.getThrowable()); } private void log(LogLevel level, Marker marker, String msg, Throwable t) { if (!helper.enabled(level, marker)) { return; } - alwaysLog(level, marker, msg, t); + alwaysLog(level, marker, null, msg, t); } - private void alwaysLog(LogLevel level, Marker marker, String msg, Throwable t) { + protected void alwaysLog(LogLevel level, Marker marker, String format, String msg, Throwable t) { helper.log(level, marker, msg, t); } } diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java index afa838ae20a..efd8ef2293f 100644 --- a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDLoggerFactory.java @@ -1,5 +1,6 @@ package datadog.trace.logging.ddlogger; +import datadog.trace.api.Platform; import datadog.trace.logging.LogLevel; import datadog.trace.logging.LogLevelSwitcher; import datadog.trace.logging.LoggerHelper; @@ -12,8 +13,9 @@ public class DDLoggerFactory implements ILoggerFactory, LogLevelSwitcher { - private volatile LoggerHelperFactory helperFactory = null; + private final boolean telemetryLogCollectionEnabled = getLogCollectionEnabled(false); + private volatile LoggerHelperFactory helperFactory = null; private volatile LogLevel override = null; public DDLoggerFactory() {} @@ -59,7 +61,12 @@ public void log(LogLevel level, Marker marker, String message, Throwable t) { @Override public Logger getLogger(String name) { LoggerHelper helper = getHelperFactory().loggerHelperForName(name); - return new DDLogger(new HelperWrapper(helper), name); + HelperWrapper helperWrapper = new HelperWrapper(helper); + if (!telemetryLogCollectionEnabled || Platform.isNativeImageBuilder()) { + return new DDLogger(helperWrapper, name); + } else { + return new DDTelemetryLogger(helperWrapper, name); + } } private LoggerHelperFactory getHelperFactory() { @@ -75,4 +82,29 @@ private LoggerHelperFactory getHelperFactory() { } return factory; } + + @Override + public void reinitialize() { + helperFactory = null; + } + + // DDLoggerFactory can be called at very early stage, before Config loaded + // So to get property/env we use this custom function + private static boolean getLogCollectionEnabled(boolean defaultValue) { + String value = System.getProperty("dd.telemetry.log-collection.enabled"); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + value = System.getenv("DD_TELEMETRY_LOG_COLLECTION_ENABLED"); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + return defaultValue; + } } diff --git a/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java new file mode 100644 index 00000000000..781a51d06d0 --- /dev/null +++ b/dd-java-agent/agent-logging/src/main/java/datadog/trace/logging/ddlogger/DDTelemetryLogger.java @@ -0,0 +1,31 @@ +package datadog.trace.logging.ddlogger; + +import datadog.trace.api.Platform; +import datadog.trace.api.telemetry.LogCollector; +import datadog.trace.logging.LogLevel; +import datadog.trace.logging.LoggerHelper; +import org.slf4j.Marker; + +public class DDTelemetryLogger extends DDLogger { + + public DDTelemetryLogger(LoggerHelper helper, String name) { + super(helper, name); + } + + @Override + protected void alwaysLog(LogLevel level, Marker marker, String format, String msg, Throwable t) { + super.alwaysLog(level, marker, format, msg, t); + if (!Platform.isNativeImageBuilder()) { + // We report only messages with Throwable or explicitly marked with SEND_TELEMETRY + if (t != null || marker == LogCollector.SEND_TELEMETRY) { + // We are scrubbing all data from messages, and only send static (compile time) + // log messages, plus redacted stack traces. + if (format != null) { + LogCollector.get().addLogMessage(level.name(), format, t); + } else if (msg != null) { + LogCollector.get().addLogMessage(level.name(), msg, t); + } + } + } + } +} diff --git a/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy b/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy new file mode 100644 index 00000000000..fe68514c72f --- /dev/null +++ b/dd-java-agent/agent-logging/src/test/groovy/datadog/trace/logging/ddlogger/DDTelemetryLoggerTest.groovy @@ -0,0 +1,52 @@ +package datadog.trace.logging.ddlogger + +import datadog.trace.api.telemetry.LogCollector +import datadog.trace.logging.LogValidatingSpecification +import datadog.trace.logging.simplelogger.SLCompatFactory +import datadog.trace.logging.simplelogger.SLCompatSettings + +class DDTelemetryLoggerTest extends LogValidatingSpecification { + def "test logging with log collector"() { + setup: + def name = "foo.bar" + Properties props = new Properties() + props.setProperty(SLCompatSettings.Keys.DEFAULT_LOG_LEVEL, "debug") + props.setProperty(SLCompatSettings.Keys.SHOW_THREAD_NAME, "false") + def settings = new SLCompatSettings(props) + def factory = new SLCompatFactory(props, settings) + def logger = new DDTelemetryLogger(factory.loggerHelperForName(name), name) + + when: + logger.debug("debug message {}", 42, new Exception()) + def collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'debug message {}' + + when: + logger.warn(LogCollector.SEND_TELEMETRY, "warning message {}", 42) + collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'warning message {}' + + when: + logger.error(LogCollector.SEND_TELEMETRY, "plain error message") + collection = LogCollector.get().drain() + + then: + collection.size() == 1 + collection[0].message() == 'plain error message' + + when: + logger.debug(LogCollector.SEND_TELEMETRY, null) + logger.error(null, new Exception()) + logger.warn("warn message", null) + collection = LogCollector.get().drain() + + then: + collection.size() == 0 + } +} diff --git a/dd-java-agent/agent-profiling/build.gradle b/dd-java-agent/agent-profiling/build.gradle index 23a64cc6aa9..410efe145a6 100644 --- a/dd-java-agent/agent-profiling/build.gradle +++ b/dd-java-agent/agent-profiling/build.gradle @@ -4,6 +4,14 @@ plugins { apply from: "$rootDir/gradle/java.gradle" +excludedClassesCoverage += [ + // ControllerFactory gets better tested in actual controller implementations + 'com.datadog.profiling.agent.ControllerFactory', + 'com.datadog.profiling.agent.ProfilingAgent', + 'com.datadog.profiling.agent.ProfilingAgent.ShutdownHook', + 'com.datadog.profiling.agent.ProfilingAgent.DataDumper' +] + dependencies { api deps.slf4j api project(':internal-api') @@ -16,6 +24,9 @@ dependencies { api project(':dd-java-agent:agent-profiling:profiling-controller-ddprof') api project(':dd-java-agent:agent-profiling:profiling-controller-openjdk') api project(':dd-java-agent:agent-profiling:profiling-controller-oracle') + + testImplementation deps.junit5 + testImplementation deps.mockito } Project parent_project = project diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java b/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java index bf3dc01fdc7..43cc0bdf0e5 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary-ddprof/src/main/java/com/datadog/profiling/auxiliary/ddprof/AuxiliaryDatadogProfiler.java @@ -2,10 +2,10 @@ import com.datadog.profiling.auxiliary.AuxiliaryImplementation; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.ddprof.DatadogProfiler; import com.datadog.profiling.utils.ProfilingMode; import com.google.auto.service.AutoService; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.EnumSet; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java index b3d955167d1..0f28f087c37 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryImplementation.java @@ -1,8 +1,8 @@ package com.datadog.profiling.auxiliary; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Collections; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryProfiler.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryProfiler.java index 9aa8b064a9d..f4a73d03196 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryProfiler.java @@ -38,7 +38,8 @@ private AuxiliaryProfiler() { this(ConfigProvider.getInstance()); } - AuxiliaryProfiler(ConfigProvider configProvider) { + /** This constructor allows passing in a custom config provider for testing purposes */ + public AuxiliaryProfiler(ConfigProvider configProvider) { String auxilliaryType = Platform.isLinux() && configProvider.getBoolean( diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java index 155775e054d..4de89e7c428 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingData.java @@ -1,7 +1,7 @@ package com.datadog.profiling.auxiliary; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java index 3109a5b7e35..e7b9f56fa5f 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/main/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingStreams.java @@ -1,7 +1,7 @@ package com.datadog.profiling.auxiliary; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java index 319591cd749..fec7322970f 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/AuxiliaryRecordingDataTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java index 764a2199940..aeb47f30b03 100644 --- a/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java +++ b/dd-java-agent/agent-profiling/profiling-auxiliary/src/test/java/com/datadog/profiling/auxiliary/TestAuxiliaryProfilerImplementation.java @@ -1,9 +1,9 @@ package com.datadog.profiling.auxiliary; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; import com.google.auto.service.AutoService; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.EnumSet; import java.util.Set; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle b/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle index 855c5b68bca..0bcf681ca5b 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle @@ -30,6 +30,7 @@ dependencies { testImplementation deps.junit5 testImplementation deps.mockito testImplementation group: 'org.hamcrest', name: 'hamcrest', version: '2.1' + testImplementation project(':dd-java-agent:agent-profiling') } /* diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerController.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerController.java index 38086ae0746..f094c7ab859 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerController.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerController.java @@ -31,6 +31,10 @@ public final class DatadogProfilerController implements Controller { private final DatadogProfiler datadogProfiler; + public static Controller instance(ConfigProvider configProvider) { + return new DatadogProfilerController(configProvider); + } + /** * Main constructor for Async profiling controller. * diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java index 00b44761bc6..1d61e984768 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/main/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecording.java @@ -17,11 +17,11 @@ import com.datadog.profiling.controller.OngoingRecording; import com.datadog.profiling.controller.ProfilerSettingsSupport; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.ddprof.DatadogProfiler; import datadog.trace.api.Platform; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java index 7d3e4d74960..8cdcec2eca4 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import com.datadog.profiling.agent.ControllerFactory; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -20,8 +21,7 @@ public class ControllerFactoryTest { * tests with java11 that is guaranteed to have JFR. */ @Test - public void testCreateController() - throws UnsupportedEnvironmentException, ConfigurationException { + public void testCreateController() throws UnsupportedEnvironmentException { // Enable test on OpenJ9 only final String javaVendor = System.getProperty("java.vendor"); final String javaVmName = System.getProperty("java.vm.name"); diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java index 8f344543279..ca07b0d3229 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerControllerTest.java @@ -1,9 +1,9 @@ package com.datadog.profiling.controller.ddprof; import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Properties; import org.junit.jupiter.api.Test; diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java index a40d67a9a8a..6ebe1f63029 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/src/test/java/com/datadog/profiling/controller/ddprof/DatadogProfilerOngoingRecordingTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.ddprof.DatadogProfiler; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; diff --git a/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp b/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp index f5e10fa9c38..63027a566a2 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp +++ b/dd-java-agent/agent-profiling/profiling-controller-jfr/src/main/resources/jfr/dd.jfp @@ -248,3 +248,5 @@ datadog.ExceptionSample#enabled=true datadog.ExceptionCount#enabled=true datadog.ProfilerSetting#enabled=true datadog.ContextInterval#enabled=true +datadog.Timeline#enabled=true +datadog.Timeline#threshold=10 ms diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/build.gradle b/dd-java-agent/agent-profiling/profiling-controller-openjdk/build.gradle index 5de37b3baaa..ae24e90e76e 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/build.gradle +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/build.gradle @@ -17,6 +17,9 @@ apply plugin: 'idea' dependencies { api deps.slf4j api project(':internal-api') + api(project(':dd-java-agent:agent-bootstrap')) { + exclude group: 'com.datadoghq', module: 'agent-logging' + } api project(':dd-java-agent:agent-profiling:profiling-auxiliary') api project(':dd-java-agent:agent-profiling:profiling-controller') api project(':dd-java-agent:agent-profiling:profiling-controller-jfr') @@ -25,6 +28,7 @@ dependencies { testImplementation deps.mockito testImplementation group: 'org.hamcrest', name: 'hamcrest', version: '2.1' testImplementation files(project(':dd-java-agent:agent-profiling:profiling-controller-jfr').sourceSets.test.output) + testImplementation project(':dd-java-agent:agent-profiling') } /* diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/JFREventContextIntegration.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/JFREventContextIntegration.java new file mode 100644 index 00000000000..36f5efc7813 --- /dev/null +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/JFREventContextIntegration.java @@ -0,0 +1,38 @@ +package com.datadog.profiling.controller.openjdk; + +import com.datadog.profiling.controller.openjdk.events.TimelineEvent; +import com.datadog.profiling.utils.ExcludedVersions; +import datadog.trace.api.Stateful; +import datadog.trace.bootstrap.instrumentation.api.ProfilerContext; +import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; + +public class JFREventContextIntegration implements ProfilingContextIntegration { + + public JFREventContextIntegration() { + ExcludedVersions.checkVersionExclusion(); + } + + private volatile boolean isStarted = false; + + @Override + public void onStart() { + // avoid initialising JFR until called + isStarted = true; + } + + @Override + public Stateful newScopeState(ProfilerContext profilerContext) { + if (!isStarted) { + return Stateful.DEFAULT; + } + return new TimelineEvent( + profilerContext.getRootSpanId(), + profilerContext.getSpanId(), + String.valueOf(profilerContext.getOperationName())); + } + + @Override + public String name() { + return "jfr"; + } +} diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java index c8b680acf21..2cee895827b 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java @@ -20,6 +20,8 @@ import static datadog.trace.api.Platform.isJavaVersionAtLeast; import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED_DEFAULT; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_ULTRA_MINIMAL; import com.datadog.profiling.controller.ConfigurationException; @@ -30,6 +32,7 @@ import datadog.trace.api.Platform; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.bootstrap.config.provider.ConfigProvider; +import datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionProfiling; import de.thetaphi.forbiddenapis.SuppressForbidden; import java.io.IOException; import java.time.Duration; @@ -53,8 +56,18 @@ public final class OpenJdkController implements Controller { private static final Logger log = LoggerFactory.getLogger(OpenJdkController.class); + private final ConfigProvider configProvider; private final Map recordingSettings; + public static Controller instance(ConfigProvider configProvider) { + try { + return new OpenJdkController(configProvider); + } catch (ConfigurationException | ClassNotFoundException e) { + log.debug("Unable to create OpenJDK controller", e); + return new MisconfiguredController(e); + } + } + /** * Main constructor for OpenJDK profiling controller. * @@ -68,6 +81,8 @@ public OpenJdkController(final ConfigProvider configProvider) Class.forName("jdk.jfr.Recording"); Class.forName("jdk.jfr.FlightRecorder"); + this.configProvider = configProvider; + boolean ultraMinimal = configProvider.getBoolean(PROFILING_ULTRA_MINIMAL, false); Map recordingSettings; @@ -109,7 +124,15 @@ public OpenJdkController(final ConfigProvider configProvider) "enabling Datadog heap histogram on JVM without an efficient implementation of the jdk.ObjectCount event. " + "This may increase p99 latency. Consider upgrading to JDK 17.0.9+ or 21+ to reduce latency impact."); } - enableEvent(recordingSettings, "jdk.ObjectCount", "user enabled histogram heap collection"); + String mode = + configProvider.getString( + PROFILING_HEAP_HISTOGRAM_MODE, PROFILING_HEAP_HISTOGRAM_MODE_DEFAULT); + if ("periodic".equalsIgnoreCase(mode)) { + enableEvent(recordingSettings, "jdk.ObjectCount", "user enabled histogram heap collection"); + } else { + enableEvent( + recordingSettings, "jdk.ObjectCountAfterGC", "user enabled histogram heap collection"); + } } // Toggle settings from override file @@ -182,6 +205,10 @@ && isEventEnabled(recordingSettings, "jdk.NativeMethodSample")) { this.recordingSettings = Collections.unmodifiableMap(recordingSettings); + if (isEventEnabled(this.recordingSettings, "datadog.ExceptionSample")) { + ExceptionProfiling.getInstance().start(); + } + // Register periodic events AvailableProcessorCoresEvent.register(); } @@ -197,7 +224,7 @@ int getMaxSize() { public OpenJdkOngoingRecording createRecording(final String recordingName) throws UnsupportedEnvironmentException { return new OpenJdkOngoingRecording( - recordingName, recordingSettings, getMaxSize(), RECORDING_MAX_AGE); + recordingName, recordingSettings, getMaxSize(), RECORDING_MAX_AGE, configProvider); } private static void disableEvent( diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java index 9c0f4401b5a..aa030361289 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecording.java @@ -3,10 +3,11 @@ import com.datadog.profiling.auxiliary.AuxiliaryProfiler; import com.datadog.profiling.auxiliary.AuxiliaryRecordingData; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.utils.ProfilingMode; import datadog.trace.api.profiling.ProfilingListenersRegistry; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.time.Duration; import java.time.Instant; import java.util.Map; @@ -28,13 +29,26 @@ public class OpenJdkOngoingRecording implements OngoingRecording { OpenJdkOngoingRecording( String recordingName, Map settings, int maxSize, Duration maxAge) { + this(recordingName, settings, maxSize, maxAge, ConfigProvider.getInstance()); + } + + OpenJdkOngoingRecording( + String recordingName, + Map settings, + int maxSize, + Duration maxAge, + ConfigProvider configProvider) { log.debug("Creating new recording: {}", recordingName); recording = new Recording(); recording.setName(recordingName); recording.setSettings(settings); recording.setMaxSize(maxSize); recording.setMaxAge(maxAge); - this.auxiliaryProfiler = AuxiliaryProfiler.getInstance(); + // for testing purposes we are supporting passing in a custom config provider + this.auxiliaryProfiler = + (configProvider == ConfigProvider.getInstance() + ? AuxiliaryProfiler.getInstance() + : new AuxiliaryProfiler(configProvider)); if (auxiliaryProfiler.isEnabled()) { auxiliaryRecording = auxiliaryProfiler.start(); if (auxiliaryRecording != null) { diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java index 3c0b3a0d13b..6cedf96691b 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkRecordingData.java @@ -15,8 +15,8 @@ */ package com.datadog.profiling.controller.openjdk; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/events/TimelineEvent.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/events/TimelineEvent.java new file mode 100644 index 00000000000..b7ac1615da8 --- /dev/null +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/events/TimelineEvent.java @@ -0,0 +1,47 @@ +package com.datadog.profiling.controller.openjdk.events; + +import datadog.trace.api.Stateful; +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +@Name("datadog.Timeline") +@Label("Profiler Timeline Event") +@Description("Datadog profiler timeline event") +@Category("Datadog") +@StackTrace(false) +public class TimelineEvent extends Event implements Stateful { + + @Label("Local Root Span Id") + private final long localRootSpanId; + + @Label("Span Id") + private final long spanId; + + @Label("Span Name") + @Name("_dd.trace.operation") + private final String operation; + + public TimelineEvent(long localRootSpanId, long spanId, String operation) { + this.localRootSpanId = localRootSpanId; + this.spanId = spanId; + this.operation = operation; + begin(); + } + + @Override + public void close() { + end(); + if (shouldCommit()) { + commit(); + } + } + + @Override + public void activate(Object context) { + // nothing to do, either we get an event or we don't + } +} diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java index 45d4c13cf20..dae26c9d48f 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java @@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import com.datadog.profiling.agent.ControllerFactory; import datadog.trace.bootstrap.config.provider.ConfigProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,8 +25,7 @@ public class ControllerFactoryTest { * tests with java11 that is guaranteed to have JFR. */ @Test - public void testCreateController() - throws UnsupportedEnvironmentException, ConfigurationException { + public void testCreateController() throws UnsupportedEnvironmentException { assertEquals( "com.datadog.profiling.controller.openjdk.OpenJdkController", ControllerFactory.createController(configProvider).getClass().getName()); @@ -35,10 +35,10 @@ public void testCreateController() public void testConfigurationException() { when(configProvider.getString(eq(PROFILING_TEMPLATE_OVERRIDE_FILE))) .thenReturn("some-path-that-is-not-supposed-to-exist!!!"); - assertThrows( - ConfigurationException.class, - () -> { - ControllerFactory.createController(configProvider); - }); + UnsupportedEnvironmentException exception = + assertThrows( + UnsupportedEnvironmentException.class, + () -> ControllerFactory.createController(configProvider)); + assertEquals(ConfigurationException.class, exception.getCause().getClass()); } } diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java index d1a4ee5d9c2..a2c1fa2e961 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkControllerTest.java @@ -2,12 +2,15 @@ import static com.datadog.profiling.controller.ProfilingSupport.*; import static datadog.trace.api.config.ProfilingConfig.PROFILING_ALLOCATION_ENABLED; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_AUXILIARY_TYPE_DEFAULT; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_TEMPLATE_OVERRIDE_FILE; import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.jfr.JfpUtilsTest; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.util.Properties; import jdk.jfr.Recording; @@ -23,8 +26,9 @@ public class OpenJdkControllerTest { @Test public void testCreateContinuousRecording() throws Exception { - Properties props = new Properties(); - props.put(PROFILING_TEMPLATE_OVERRIDE_FILE, JfpUtilsTest.OVERRIDES); + Properties props = getConfigProperties(); + props.put(PROFILING_DATADOG_PROFILER_ENABLED, "false"); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -39,7 +43,8 @@ public void testCreateContinuousRecording() throws Exception { @Test public void testHeapProfilerIsDisabledOnUnsupportedVersion() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -54,8 +59,9 @@ public void testHeapProfilerIsDisabledOnUnsupportedVersion() throws Exception { @Test public void testHeapProfilerIsStillOverriddenOnUnsupportedVersion() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); props.put(PROFILING_TEMPLATE_OVERRIDE_FILE, JfpUtilsTest.OVERRIDES_OLD_OBJECT_SAMPLE); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -71,8 +77,9 @@ public void testHeapProfilerIsStillOverriddenOnUnsupportedVersion() throws Excep @Test public void testHeapProfilerIsStillOverriddenThroughConfig() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); props.put(PROFILING_HEAP_ENABLED, "true"); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -87,7 +94,8 @@ public void testHeapProfilerIsStillOverriddenThroughConfig() throws Exception { @Test public void testAllocationProfilerIsDisabledOnUnsupportedVersion() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -126,7 +134,7 @@ public void testAllocationProfilerIsDisabledOnUnsupportedVersion() throws Except @Test public void testAllocationProfilerIsStillOverriddenOnUnsupportedVersion() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); props.put(PROFILING_TEMPLATE_OVERRIDE_FILE, JfpUtilsTest.OVERRIDES_OBJECT_ALLOCATION); ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); @@ -149,8 +157,9 @@ public void testAllocationProfilerIsStillOverriddenOnUnsupportedVersion() throws @Test public void testAllocationProfilerIsStillOverriddenThroughConfig() throws Exception { - Properties props = new Properties(); + Properties props = getConfigProperties(); props.put(PROFILING_ALLOCATION_ENABLED, "true"); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -172,7 +181,8 @@ public void testAllocationProfilerIsStillOverriddenThroughConfig() throws Except @Test public void testNativeProfilerIsDisabledOnUnsupportedVersion() throws Exception { Assumptions.assumeFalse(isNativeMethodSampleAvailable()); - Properties props = new Properties(); + Properties props = getConfigProperties(); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -186,8 +196,8 @@ public void testNativeProfilerIsDisabledOnUnsupportedVersion() throws Exception @Test public void testNativeProfilerIsStillOverriddenOnUnsupportedVersion() throws Exception { - Properties props = new Properties(); - props.put(PROFILING_TEMPLATE_OVERRIDE_FILE, JfpUtilsTest.OVERRIDES_NATIVE_METHOD_SAMPLE); + Properties props = getConfigProperties(); + ConfigProvider configProvider = ConfigProvider.withPropertiesOverride(props); OpenJdkController controller = new OpenJdkController(configProvider); @@ -200,4 +210,12 @@ public void testNativeProfilerIsStillOverriddenOnUnsupportedVersion() throws Exc } } } + + private static Properties getConfigProperties() { + Properties props = new Properties(); + // make sure the async profiler is not force-enabled + props.put(PROFILING_AUXILIARY_TYPE, PROFILING_AUXILIARY_TYPE_DEFAULT); + props.put(PROFILING_DATADOG_PROFILER_ENABLED, "false"); + return props; + } } diff --git a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java index 226305b6469..7ce507aeef7 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller-openjdk/src/test/java/com/datadog/profiling/controller/openjdk/OpenJdkOngoingRecordingTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import java.time.Instant; import jdk.jfr.Recording; import jdk.jfr.RecordingState; diff --git a/dd-java-agent/agent-profiling/profiling-controller-oracle/build.gradle b/dd-java-agent/agent-profiling/profiling-controller-oracle/build.gradle index 03d245fd32f..8ed1fe90038 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-oracle/build.gradle +++ b/dd-java-agent/agent-profiling/profiling-controller-oracle/build.gradle @@ -26,6 +26,7 @@ dependencies { testImplementation deps.mockito testImplementation group: 'org.hamcrest', name: 'hamcrest', version: '2.1' testImplementation files(project(':dd-java-agent:agent-profiling:profiling-controller-jfr').sourceSets.test.output) + testImplementation project(':dd-java-agent:agent-profiling') } // Oracle JDK requires extra JVM arguments to enable JFR diff --git a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkController.java b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkController.java index 5d177d8fc7b..cae24a49e3a 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkController.java +++ b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkController.java @@ -26,6 +26,15 @@ public final class OracleJdkController implements Controller { private final Map eventSettings; private final JfrMBeanHelper helper; + public static Controller instance(ConfigProvider configProvider) { + try { + return new OracleJdkController(configProvider); + } catch (ConfigurationException e) { + log.debug("Unable to create OracleJdkController", e); + return new MisconfiguredController(e); + } + } + /** * Main constructor for Oracle JDK profiling controller. * diff --git a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java index e66d32dccb3..226e1b0f24d 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-controller-oracle/src/main/java/com/datadog/profiling/controller/oracle/OracleJdkRecordingData.java @@ -15,8 +15,8 @@ */ package com.datadog.profiling.controller.oracle; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.io.InputStream; import java.time.Instant; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/Controller.java b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/Controller.java index 279e1643be6..3b076f67f19 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/Controller.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/Controller.java @@ -22,6 +22,21 @@ * want to support multiple versions later. */ public interface Controller { + /** A special type for a noop controller instance which could not be properly configured */ + class MisconfiguredController implements Controller { + public final Exception exception; + + public MisconfiguredController(Exception exception) { + this.exception = exception; + } + + @Nonnull + @Override + public OngoingRecording createRecording(@Nonnull String recordingName) + throws UnsupportedEnvironmentException { + throw new UnsupportedEnvironmentException("Controller is not configured"); + } + } /** * Creates a continuous recording using the specified template. * diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java index f2b0630ae6c..767957adf3a 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/OngoingRecording.java @@ -1,6 +1,7 @@ package com.datadog.profiling.controller; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.io.Closeable; import java.time.Instant; import javax.annotation.Nonnull; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java index d5ae0359da5..f1bf7757dae 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ProfilingSystem.java @@ -18,6 +18,9 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_RECORDING_SCHEDULER; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.util.AgentTaskScheduler; import java.time.Duration; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java b/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java index f3cb16ff88d..286790b315c 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java +++ b/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ProfilingSystemTest.java @@ -15,7 +15,7 @@ */ package com.datadog.profiling.controller; -import static com.datadog.profiling.controller.RecordingType.CONTINUOUS; +import static datadog.trace.api.profiling.RecordingType.CONTINUOUS; import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_RECORDING_SCHEDULER; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; @@ -36,6 +36,8 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.util.AgentTaskScheduler; import java.time.Duration; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java index 0178fb76d3b..8f29aba8365 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfiler.java @@ -16,6 +16,7 @@ import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isCpuProfilerEnabled; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isLiveHeapSizeTrackingEnabled; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isMemoryLeakProfilingEnabled; +import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isResourceNameContextAttributeEnabled; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isSpanNameContextAttributeEnabled; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.isWallClockProfilerEnabled; import static com.datadog.profiling.ddprof.DatadogProfilerConfig.omitLineNumbers; @@ -27,12 +28,12 @@ import static datadog.trace.api.config.ProfilingConfig.PROFILING_QUEUEING_TIME_THRESHOLD_MILLIS_DEFAULT; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.utils.ProfilingMode; import com.datadoghq.profiler.ContextSetter; import com.datadoghq.profiler.JavaProfiler; import datadog.trace.api.config.ProfilingConfig; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.IOException; import java.nio.file.Files; @@ -58,6 +59,7 @@ public final class DatadogProfiler { private static final int[] EMPTY = new int[0]; private static final String OPERATION = "_dd.trace.operation"; + private static final String RESOURCE = "_dd.trace.resource"; private static final int MAX_NUM_ENDPOINTS = 8192; @@ -149,7 +151,7 @@ private DatadogProfiler(ConfigProvider configProvider) throws UnsupportedEnviron ProfilingConfig.PROFILING_DATADOG_PROFILER_SCRATCH, ProfilingConfig.PROFILING_DATADOG_PROFILER_SCRATCH_DEFAULT)); } catch (IOException e) { - throw new UnsupportedOperationException("Unable to instantiate datadog profiler"); + throw new UnsupportedOperationException("Unable to instantiate datadog profiler", e); } if (profiler == null) { throw new UnsupportedEnvironmentException("Unable to instantiate datadog profiler"); @@ -173,6 +175,9 @@ private DatadogProfiler(ConfigProvider configProvider) throws UnsupportedEnviron if (isSpanNameContextAttributeEnabled(configProvider)) { orderedContextAttributes.add(OPERATION); } + if (isResourceNameContextAttributeEnabled(configProvider)) { + orderedContextAttributes.add(RESOURCE); + } this.contextSetter = new ContextSetter(profiler, orderedContextAttributes); this.queueTimeThreshold = configProvider.getLong( @@ -367,6 +372,10 @@ public int operationNameOffset() { return offsetOf(OPERATION); } + public int resourceNameOffset() { + return offsetOf(RESOURCE); + } + public int offsetOf(String attribute) { return contextSetter.offsetOf(attribute); } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java index d6eeac40899..3746ca29936 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java @@ -3,6 +3,7 @@ import static datadog.trace.api.Platform.isJ9; import static datadog.trace.api.config.ProfilingConfig.PROFILING_ALLOCATION_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES; +import static datadog.trace.api.config.ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES_RESOURCE_NAME_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_CONTEXT_ATTRIBUTES_SPAN_NAME_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_ALLOC_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DATADOG_PROFILER_ALLOC_ENABLED_DEFAULT; @@ -289,6 +290,10 @@ public static boolean isSpanNameContextAttributeEnabled(ConfigProvider configPro return configProvider.getBoolean(PROFILING_CONTEXT_ATTRIBUTES_SPAN_NAME_ENABLED, true); } + public static boolean isResourceNameContextAttributeEnabled(ConfigProvider configProvider) { + return configProvider.getBoolean(PROFILING_CONTEXT_ATTRIBUTES_RESOURCE_NAME_ENABLED, false); + } + public static String getString(ConfigProvider configProvider, String key, String defaultValue) { return configProvider.getString(key, configProvider.getString(normalizeKey(key), defaultValue)); } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java index d1e0781b964..8154a8a444e 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecording.java @@ -1,8 +1,8 @@ package com.datadog.profiling.ddprof; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java index e9cb4f03393..954e79834d2 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingData.java @@ -1,7 +1,7 @@ package com.datadog.profiling.ddprof; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java index 1fe10273789..ab31d14f3bc 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilingIntegration.java @@ -1,5 +1,6 @@ package com.datadog.profiling.ddprof; +import datadog.trace.api.Stateful; import datadog.trace.api.profiling.ProfilingContextAttribute; import datadog.trace.api.profiling.ProfilingScope; import datadog.trace.bootstrap.instrumentation.api.ProfilerContext; @@ -13,11 +14,32 @@ public class DatadogProfilingIntegration implements ProfilingContextIntegration private static final DatadogProfiler DDPROF = DatadogProfiler.getInstance(); private static final int SPAN_NAME_INDEX = DDPROF.operationNameOffset(); + private static final int RESOURCE_NAME_INDEX = DDPROF.resourceNameOffset(); private static final boolean WALLCLOCK_ENABLED = DatadogProfilerConfig.isWallClockProfilerEnabled(); - private static final boolean QUEUEING_TIME_ENABLED = - WALLCLOCK_ENABLED && DatadogProfilerConfig.isQueueingTimeEnabled(); + private final Stateful contextManager = + new Stateful() { + @Override + public void close() { + // this implementation is stateless so nothing to do here + } + + @Override + public void activate(Object context) { + if (context instanceof ProfilerContext) { + ProfilerContext profilerContext = (ProfilerContext) context; + DDPROF.setSpanContext(profilerContext.getSpanId(), profilerContext.getRootSpanId()); + DDPROF.setContextValue(SPAN_NAME_INDEX, profilerContext.getEncodedOperationName()); + DDPROF.setContextValue(RESOURCE_NAME_INDEX, profilerContext.getEncodedResourceName()); + } + } + }; + + @Override + public Stateful newScopeState(ProfilerContext profilerContext) { + return contextManager; + } @Override public void onAttach() { @@ -28,6 +50,7 @@ public void onAttach() { @Override public void onDetach() { + clearContext(); if (WALLCLOCK_ENABLED) { DDPROF.removeThread(); } @@ -39,20 +62,30 @@ public int encode(CharSequence constant) { } @Override - public void setContext(ProfilerContext profilerContext) { - DDPROF.setSpanContext(profilerContext.getSpanId(), profilerContext.getRootSpanId()); - DDPROF.setContextValue(SPAN_NAME_INDEX, profilerContext.getEncodedOperationName()); + public int encodeOperationName(CharSequence constant) { + if (SPAN_NAME_INDEX >= 0) { + return DDPROF.encode(constant); + } + return 0; } @Override - public void clearContext() { - DDPROF.clearSpanContext(); - DDPROF.clearContextValue(SPAN_NAME_INDEX); + public int encodeResourceName(CharSequence constant) { + if (RESOURCE_NAME_INDEX >= 0) { + return DDPROF.encode(constant); + } + return 0; } @Override - public void setContext(long rootSpanId, long spanId) { - DDPROF.setSpanContext(spanId, rootSpanId); + public String name() { + return "ddprof"; + } + + public void clearContext() { + DDPROF.clearSpanContext(); + DDPROF.clearContextValue(SPAN_NAME_INDEX); + DDPROF.clearContextValue(RESOURCE_NAME_INDEX); } @Override @@ -64,12 +97,4 @@ public ProfilingContextAttribute createContextAttribute(String attribute) { public ProfilingScope newScope() { return new DatadogProfilingScope(DDPROF); } - - @Override - public boolean isQueuingTimeEnabled() { - return QUEUEING_TIME_ENABLED; - } - - @Override - public void recordQueueingTime(long duration) {} } diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java index 6d9ab83f3e2..da5f9d7ad60 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerRecordingTest.java @@ -2,7 +2,8 @@ import static org.junit.jupiter.api.Assertions.*; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.Platform; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.InputStream; import java.nio.file.Files; @@ -22,6 +23,7 @@ class DatadogProfilerRecordingTest { @BeforeEach void setup() throws Exception { + Assume.assumeTrue(Platform.isLinux()); profiler = DatadogProfiler.newInstance(ConfigProvider.getInstance()); log.info( "Datadog Profiler: available={}, active={}", profiler.isAvailable(), profiler.isActive()); diff --git a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java index 67531996499..4c586ccc9c8 100644 --- a/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java +++ b/dd-java-agent/agent-profiling/profiling-ddprof/src/test/java/com/datadog/profiling/ddprof/DatadogProfilerTest.java @@ -6,11 +6,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.datadog.profiling.controller.OngoingRecording; -import com.datadog.profiling.controller.RecordingData; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.utils.ProfilingMode; +import datadog.trace.api.Platform; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.api.profiling.ProfilingScope; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.nio.file.Path; import java.nio.file.Paths; @@ -20,6 +21,8 @@ import java.util.UUID; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -32,6 +35,11 @@ class DatadogProfilerTest { private static final Logger log = LoggerFactory.getLogger(DatadogProfilerTest.class); + @BeforeEach + public void setup() { + Assumptions.assumeTrue(Platform.isLinux()); + } + @Test void test() throws Exception { DatadogProfiler profiler = DatadogProfiler.newInstance(ConfigProvider.getInstance()); @@ -143,7 +151,7 @@ private static ConfigProvider configProvider( props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_WALL_ENABLED, Boolean.toString(wall)); props.put(ProfilingConfig.PROFILING_DATADOG_PROFILER_ALLOC_ENABLED, Boolean.toString(alloc)); props.put( - ProfilingConfig.PROFILING_DATADOG_PROFILER_MEMLEAK_ENABLED, Boolean.toString(memleak)); + ProfilingConfig.PROFILING_DATADOG_PROFILER_LIVEHEAP_ENABLED, Boolean.toString(memleak)); return ConfigProvider.withPropertiesOverride(props); } } diff --git a/dd-java-agent/agent-profiling/profiling-uploader/build.gradle b/dd-java-agent/agent-profiling/profiling-uploader/build.gradle index a531d2af6c3..3ff1658ee27 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/build.gradle +++ b/dd-java-agent/agent-profiling/profiling-uploader/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation project(':dd-java-agent:agent-profiling:profiling-controller') implementation deps.okhttp - implementation group: 'org.lz4', name: 'lz4-java', version: '1.7.1' + implementation deps.lz4 testImplementation deps.junit5 testImplementation project(':dd-java-agent:agent-profiling:profiling-testing') diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java index eadf3c998ec..d41511d1b0a 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/CompressingRequestBody.java @@ -1,6 +1,7 @@ package com.datadog.profiling.uploader; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.Platform; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; @@ -23,8 +24,17 @@ */ final class CompressingRequestBody extends RequestBody { - private static final LZ4Factory LZ4_FACTORY = LZ4Factory.fastestJavaInstance(); - private static final XXHashFactory XXHASH_FACTORY = XXHashFactory.fastestJavaInstance(); + /* + * LZ4 is not available in native image. + * LZ4Factory is using reflection heavily and since we are shading the lz4 classes for the usage in profiler + * it seems to be impossible to configure the native-image generation such as to make the reflection work. + * + * For now, we are disabling the lz4 compression in native image. + */ + private static final LZ4Factory LZ4_FACTORY = + Platform.isNativeImage() ? null : LZ4Factory.fastestJavaInstance(); + private static final XXHashFactory XXHASH_FACTORY = + Platform.isNativeImage() ? null : XXHashFactory.fastestJavaInstance(); static final class MissingInputException extends IOException { public MissingInputException(String message) { @@ -323,6 +333,10 @@ private static OutputStreamMappingFunction getOutputStreamMapper( @Nonnull CompressionType compressionType) { // currently only gzip and off are supported // this needs to be updated once more compression types are added + compressionType = + (Platform.isNativeImage() && compressionType != CompressionType.OFF + ? CompressionType.GZIP + : compressionType); switch (compressionType) { case GZIP: { diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java index 19c4ae3d21f..159c16ecb9a 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/ProfileUploader.java @@ -17,8 +17,6 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_HTTP_DISPATCHER; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.uploader.util.JfrCliHelper; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonReader; @@ -29,6 +27,8 @@ import datadog.trace.api.DDTags; import datadog.trace.api.git.GitInfo; import datadog.trace.api.git.GitInfoProvider; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.relocate.api.IOLogger; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java index 68d8a0bab2d..9668dc61d72 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/main/java/com/datadog/profiling/uploader/util/JfrCliHelper.java @@ -2,7 +2,7 @@ import static datadog.trace.util.AgentThreadFactory.AgentThread.PROFILER_HTTP_DISPATCHER; -import com.datadog.profiling.controller.RecordingData; +import datadog.trace.api.profiling.RecordingData; import datadog.trace.relocate.api.IOLogger; import datadog.trace.util.AgentThreadFactory; import java.io.ByteArrayOutputStream; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java index 5b2cf5bec79..9dd3a6e23ac 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/CompressingRequestBodyTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingInputStream; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java index cb6a2f284e3..fce3459ddec 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/ProfileUploaderTest.java @@ -33,9 +33,6 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.testing.ProfilingTestUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,6 +42,9 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.api.profiling.ProfilingSnapshot; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import datadog.trace.relocate.api.IOLogger; import datadog.trace.util.PidHelper; diff --git a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java index 892d9ea96c0..a8d2c1fd7a1 100644 --- a/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java +++ b/dd-java-agent/agent-profiling/profiling-uploader/src/test/java/com/datadog/profiling/uploader/util/JfrCliHelperTest.java @@ -9,8 +9,8 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingInputStream; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingInputStream; import datadog.trace.relocate.api.IOLogger; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ControllerFactory.java b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ControllerFactory.java similarity index 76% rename from dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ControllerFactory.java rename to dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ControllerFactory.java index 0608ab201ec..94c8a641e4b 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/main/java/com/datadog/profiling/controller/ControllerFactory.java +++ b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ControllerFactory.java @@ -13,13 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.datadog.profiling.controller; +package com.datadog.profiling.agent; +import com.datadog.profiling.controller.ConfigurationException; +import com.datadog.profiling.controller.Controller; +import com.datadog.profiling.controller.UnsupportedEnvironmentException; +import com.datadog.profiling.controller.ddprof.DatadogProfilerController; +import com.datadog.profiling.controller.openjdk.OpenJdkController; +import com.datadog.profiling.controller.oracle.OracleJdkController; import datadog.trace.api.Config; import datadog.trace.api.Platform; import datadog.trace.bootstrap.config.provider.ConfigProvider; import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.InvocationTargetException; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,25 +34,32 @@ public final class ControllerFactory { private static final Logger log = LoggerFactory.getLogger(ControllerFactory.class); - private static enum Implementation { + private enum Implementation { NONE(), - ORACLE("com.datadog.profiling.controller.oracle.OracleJdkController"), - OPENJDK("com.datadog.profiling.controller.openjdk.OpenJdkController"), - ASYNC("com.datadog.profiling.controller.ddprof.DatadogProfilerController"); + ORACLE(OracleJdkController::instance, OracleJdkController.class.getName()), + OPENJDK(OpenJdkController::instance, OpenJdkController.class.getName()), + DDPROF(DatadogProfilerController::instance, DatadogProfilerController.class.getName()); + private final Function instantiator; private final String className; Implementation() { - this.className = null; + this.instantiator = null; + this.className = ""; } - Implementation(String className) { + Implementation(Function instantiator, String className) { + this.instantiator = instantiator; this.className = className; } String className() { return className; } + + Controller instance(ConfigProvider configProvider) { + return instantiator.apply(configProvider); + } } /** @@ -58,7 +72,7 @@ String className() { */ @SuppressForbidden public static Controller createController(final ConfigProvider configProvider) - throws UnsupportedEnvironmentException, ConfigurationException { + throws UnsupportedEnvironmentException { Implementation impl = Implementation.NONE; boolean isOracleJDK8 = Platform.isOracleJDK8(); boolean isDatadogProfilerEnabled = Config.get().isDatadogProfilerEnabled(); @@ -87,7 +101,7 @@ public static Controller createController(final ConfigProvider configProvider) datadogProfilerClass .getMethod("isAvailable") .invoke(datadogProfilerClass.getMethod("getInstance").invoke(null))) { - impl = Implementation.ASYNC; + impl = Implementation.DDPROF; } else { log.debug("Failed to load Datadog profiler, it is not available"); } @@ -103,22 +117,13 @@ public static Controller createController(final ConfigProvider configProvider) throw new UnsupportedEnvironmentException(getFixProposalMessage()); } - try { - log.debug("Trying to load {}", impl.className()); - return Class.forName(impl.className()) - .asSubclass(Controller.class) - .getDeclaredConstructor(ConfigProvider.class) - .newInstance(configProvider); - } catch (final ClassNotFoundException - | NoSuchMethodException - | InstantiationException - | IllegalAccessException - | InvocationTargetException e) { - if (e.getCause() != null && e.getCause() instanceof ConfigurationException) { - throw (ConfigurationException) e.getCause(); - } - throw new UnsupportedEnvironmentException(getFixProposalMessage(), e); + Controller instance = impl.instance(configProvider); + if (instance instanceof Controller.MisconfiguredController) { + throw new UnsupportedEnvironmentException( + "Failed to configure controller " + impl.className, + ((Controller.MisconfiguredController) instance).exception); } + return instance; } private static String getFixProposalMessage() { diff --git a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java index 3a0832f5c8e..3bd212adc82 100644 --- a/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java +++ b/dd-java-agent/agent-profiling/src/main/java/com/datadog/profiling/agent/ProfilingAgent.java @@ -6,16 +6,15 @@ import com.datadog.profiling.controller.ConfigurationException; import com.datadog.profiling.controller.Controller; -import com.datadog.profiling.controller.ControllerFactory; import com.datadog.profiling.controller.ProfilingSystem; -import com.datadog.profiling.controller.RecordingData; -import com.datadog.profiling.controller.RecordingDataListener; -import com.datadog.profiling.controller.RecordingType; import com.datadog.profiling.controller.UnsupportedEnvironmentException; import com.datadog.profiling.uploader.ProfileUploader; import datadog.trace.api.Config; import datadog.trace.api.Platform; import datadog.trace.api.config.ProfilingConfig; +import datadog.trace.api.profiling.RecordingData; +import datadog.trace.api.profiling.RecordingDataListener; +import datadog.trace.api.profiling.RecordingType; import datadog.trace.bootstrap.config.provider.ConfigProvider; import java.io.IOException; import java.lang.ref.WeakReference; diff --git a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java b/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ControllerFactoryTest.java similarity index 78% rename from dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java rename to dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ControllerFactoryTest.java index 86c8473ad8c..de3b6cb5f03 100644 --- a/dd-java-agent/agent-profiling/profiling-controller/src/test/java/com/datadog/profiling/controller/ControllerFactoryTest.java +++ b/dd-java-agent/agent-profiling/src/test/java/com/datadog/profiling/agent/ControllerFactoryTest.java @@ -1,12 +1,10 @@ -package com.datadog.profiling.controller; +package com.datadog.profiling.agent; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import com.datadog.profiling.controller.UnsupportedEnvironmentException; import datadog.trace.bootstrap.config.provider.ConfigProvider; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -18,14 +16,15 @@ public class ControllerFactoryTest { @Mock private ConfigProvider configProvider; @Test - @EnabledOnJre({JAVA_8}) - public void testCreateControllerJava8() { - final UnsupportedEnvironmentException unsupportedEnvironmentException = - assertThrows( - UnsupportedEnvironmentException.class, - () -> { - ControllerFactory.createController(configProvider); - }); + public void testCreateControllerSanity() { + UnsupportedEnvironmentException unsupportedEnvironmentException = null; + try { + ControllerFactory.createController(configProvider); + // successfully created controller, return + return; + } catch (UnsupportedEnvironmentException e) { + unsupportedEnvironmentException = e; + } final String javaVendor = System.getProperty("java.vendor"); final String javaRuntimeName = System.getProperty("java.runtime.name"); final String javaVersion = System.getProperty("java.version"); diff --git a/dd-java-agent/agent-profiling/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/dd-java-agent/agent-profiling/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000000..1f0955d450f --- /dev/null +++ b/dd-java-agent/agent-profiling/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/dd-java-agent/agent-tooling/build.gradle b/dd-java-agent/agent-tooling/build.gradle index 93e2e2f84ff..3fec4166c59 100644 --- a/dd-java-agent/agent-tooling/build.gradle +++ b/dd-java-agent/agent-tooling/build.gradle @@ -42,6 +42,7 @@ dependencies { api(project(':dd-java-agent:agent-bootstrap')) { exclude group: 'com.datadoghq', module: 'agent-logging' } + compileOnly project(':dd-java-agent:agent-profiling') api group: 'com.blogspot.mydailyjava', name: 'weak-lock-free', version: '0.17' api deps.bytebuddy api deps.bytebuddyagent diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ProfilerInstaller.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ProfilerInstaller.java new file mode 100644 index 00000000000..651ff96cd04 --- /dev/null +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ProfilerInstaller.java @@ -0,0 +1,16 @@ +package datadog.trace.agent.tooling; + +import com.datadog.profiling.agent.ProfilingAgent; +import datadog.trace.api.Config; + +public class ProfilerInstaller { + public static void installProfiler() { + if (Config.get().isProfilingEnabled()) { + try { + ProfilingAgent.run(true, null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java index d83fd6f44a3..12839ab8bd0 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/TracerInstaller.java @@ -21,6 +21,7 @@ public static synchronized void installGlobalTracer( CoreTracer.builder() .sharedCommunicationObjects(sharedCommunicationObjects) .profilingContextIntegration(profilingContextIntegration) + .pollForTracerFlareRequests() .pollForTracingConfiguration() .build()); } else { diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/ScalaTraitMatchers.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/ScalaTraitMatchers.java new file mode 100644 index 00000000000..51464848f33 --- /dev/null +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/matcher/ScalaTraitMatchers.java @@ -0,0 +1,44 @@ +package datadog.trace.agent.tooling.bytebuddy.matcher; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.is; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class ScalaTraitMatchers { + public static ElementMatcher.Junction isTraitMethod( + String traitName, String name, Object... argumentTypes) { + + ElementMatcher.Junction scalaOldArgs = + isStatic() + .and(takesArguments(argumentTypes.length + 1)) + .and(takesArgument(0, named(traitName))); + ElementMatcher.Junction scalaNewArgs = + not(isStatic()).and(takesArguments(argumentTypes.length)); + + for (int i = 0; i < argumentTypes.length; i++) { + Object argumentType = argumentTypes[i]; + ElementMatcher matcher; + if (argumentType instanceof ElementMatcher) { + matcher = (ElementMatcher) argumentType; + } else if (argumentType instanceof String) { + matcher = named((String) argumentType); + } else if (argumentType instanceof Class) { + matcher = is((Class) argumentType); + } else { + throw new IllegalArgumentException("Unexpected type for argument type specification"); + } + scalaOldArgs = scalaOldArgs.and(takesArgument(i + 1, matcher)); + scalaNewArgs = scalaNewArgs.and(takesArgument(i, matcher)); + } + + return isMethod().and(named(name)).and(scalaOldArgs.or(scalaNewArgs)); + } +} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/iast/TaintableEnumeration.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/iast/TaintableEnumeration.java new file mode 100644 index 00000000000..cfc8fb3b44f --- /dev/null +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/iast/TaintableEnumeration.java @@ -0,0 +1,101 @@ +package datadog.trace.agent.tooling.iast; + +import datadog.trace.api.iast.IastContext; +import datadog.trace.api.iast.propagation.PropagationModule; +import datadog.trace.util.stacktrace.StackUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Enumeration; +import javax.annotation.Nullable; + +public class TaintableEnumeration implements Enumeration { + + private static final String CLASS_NAME = TaintableEnumeration.class.getName(); + + private volatile IastContext context; + private volatile boolean contextFetched; + + private final PropagationModule module; + + private final byte origin; + + private final CharSequence name; + + private final boolean useValueAsName; + + private final Enumeration delegate; + + private TaintableEnumeration( + @NonNull final Enumeration delegate, + @NonNull final PropagationModule module, + final byte origin, + @Nullable final CharSequence name, + final boolean useValueAsName) { + this.delegate = delegate; + this.module = module; + this.origin = origin; + this.name = name; + this.useValueAsName = useValueAsName; + } + + @Override + public boolean hasMoreElements() { + try { + return delegate.hasMoreElements(); + } catch (Throwable e) { + StackUtils.filterFirst(e, TaintableEnumeration::nonTaintableEnumerationStack); + throw e; + } + } + + @Override + public String nextElement() { + final String next; + try { + next = delegate.nextElement(); + } catch (Throwable e) { + StackUtils.filterFirst(e, TaintableEnumeration::nonTaintableEnumerationStack); + throw e; + } + try { + module.taint(context(), next, origin, name(next)); + } catch (final Throwable e) { + module.onUnexpectedException("Failed to taint enumeration", e); + } + return next; + } + + private IastContext context() { + if (!contextFetched) { + contextFetched = true; + context = IastContext.Provider.get(); + } + return context; + } + + private CharSequence name(final String value) { + if (name != null) { + return name; + } + return useValueAsName ? value : null; + } + + private static boolean nonTaintableEnumerationStack(final StackTraceElement element) { + return !CLASS_NAME.equals(element.getClassName()); + } + + public static Enumeration wrap( + @NonNull final Enumeration delegate, + @NonNull final PropagationModule module, + final byte origin, + @Nullable final CharSequence name) { + return new TaintableEnumeration(delegate, module, origin, name, false); + } + + public static Enumeration wrap( + @NonNull final Enumeration delegate, + @NonNull final PropagationModule module, + final byte origin, + boolean useValueAsName) { + return new TaintableEnumeration(delegate, module, origin, null, useValueAsName); + } +} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java index 219dcda7e11..802393601d7 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/log/UnionMap.java @@ -1,7 +1,9 @@ package datadog.trace.agent.tooling.log; +import java.io.Serializable; import java.util.AbstractMap; import java.util.AbstractSet; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -11,7 +13,7 @@ * New entries are put in the primary map while old entries are deleted from both, as appropriate. * Lazy deduplication occurs once: before iterating over entries/values, or when combining sizes. */ -public final class UnionMap extends AbstractMap { +public final class UnionMap extends AbstractMap implements Serializable { private final Map primaryMap; private final Map secondaryMap; private transient Set> entrySet; @@ -145,4 +147,8 @@ public void remove() { } return entrySet; } + + public Object writeReplace() { + return new HashMap<>(this); // serialize de-duplicated copy + } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java index 200ec799963..c2af65a7624 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java @@ -1,6 +1,7 @@ package datadog.trace.agent.tooling.nativeimage; import datadog.communication.ddagent.SharedCommunicationObjects; +import datadog.trace.agent.tooling.ProfilerInstaller; import datadog.trace.agent.tooling.TracerInstaller; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import org.slf4j.Logger; @@ -12,8 +13,10 @@ public final class TracerActivation { public static void activate() { try { - TracerInstaller.installGlobalTracer( - new SharedCommunicationObjects(), ProfilingContextIntegration.NoOp.INSTANCE); + // the JFR based profiling does not allow context propagation - use the noop integration + ProfilingContextIntegration contextIntegration = ProfilingContextIntegration.NoOp.INSTANCE; + TracerInstaller.installGlobalTracer(new SharedCommunicationObjects(), contextIntegration); + ProfilerInstaller.installProfiler(); } catch (Throwable e) { log.warn("Problem activating datadog tracer", e); } diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index fd259e3cd80..74cb0df43b1 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -42,7 +42,8 @@ 1 io.opentelemetry.javaagent.* 1 java.* # allow exception profiling instrumentation -0 java.lang.Throwable +0 java.lang.Exception +0 java.lang.Error # allow ProcessImpl instrumentation 0 java.lang.ProcessImpl 0 java.net.http.* @@ -187,11 +188,17 @@ 2 com.github.mustachejava.* 2 com.google.api.* 0 com.google.api.client.http.HttpRequest +0 com.google.api.gax.grpc.* +0 com.google.api.gax.retrying.* +0 com.google.cloud.pubsub.v1.* 2 com.google.cloud.* 2 com.google.common.* 0 com.google.common.base.internal.Finalizer 0 com.google.common.util.concurrent.* 2 com.google.gson.* +# Need for IAST: we instrument this class +0 com.google.gson.Gson +0 com.google.gson.stream.JsonReader 2 com.google.inject.* # We instrument Runnable there 0 com.google.inject.internal.AbstractBindingProcessor$* @@ -250,6 +257,7 @@ 0 org.springframework.beans.factory.support.AbstractBeanFactory 0 org.springframework.beans.factory.support.DefaultListableBeanFactory 0 org.springframework.beans.factory.support.DisposableBeanAdapter +1 org.springframework.boot.SpringApplicationShutdownHook$Handlers 2 org.springframework.boot.* 0 org.apache.xalan.transformer.TransformerImpl # More runnables to deal with @@ -284,8 +292,9 @@ 2 org.springframework.expression.* 2 org.springframework.format.* 2 org.springframework.http.* -# Need for IAST: calls ServletRequest methods instrumented at callsite -0 org.springframework.http.server.ServletServerHttpRequest +# Need for IAST: Spring gson support +0 org.springframework.http.converter.json.AbstractJsonHttpMessageConverter +0 org.springframework.http.converter.json.GsonHttpMessageConverter # Need for IAST: we instrument these classes 0 org.springframework.http.HttpHeaders 0 org.springframework.http.ReadOnlyHttpHeaders @@ -321,16 +330,15 @@ 2 org.springframework.validation.* 2 org.springframework.web.* 0 org.springframework.web.context.request.async.* -0 org.springframework.web.context.request.* 0 org.springframework.web.context.support.AbstractRefreshableWebApplicationContext 0 org.springframework.web.context.support.GenericWebApplicationContext 0 org.springframework.web.context.support.XmlWebApplicationContext 0 org.springframework.web.reactive.* 0 org.springframework.web.servlet.* -# Included for IAST -0 org.springframework.web.util.WebUtils # Need for IAST so propagation of tainted objects is complete in spring 2.7.5 0 org.springframework.util.StreamUtils$NonClosingInputStream +# Need for IAST gson propagation +0 org.springframework.util.StreamUtils # Included for IAST Spring mvc unvalidated redirect and xss vulnerability 0 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite 2 org.xml.* diff --git a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/iast/TaintableEnumerationTest.groovy b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/iast/TaintableEnumerationTest.groovy new file mode 100644 index 00000000000..78566a8bec7 --- /dev/null +++ b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/iast/TaintableEnumerationTest.groovy @@ -0,0 +1,99 @@ +package datadog.trace.agent.tooling.iast + +import datadog.trace.api.gateway.RequestContext +import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.api.iast.IastContext +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.SourceTypes +import datadog.trace.api.iast.propagation.PropagationModule +import datadog.trace.bootstrap.instrumentation.api.AgentSpan +import datadog.trace.bootstrap.instrumentation.api.AgentTracer +import datadog.trace.test.util.DDSpecification +import spock.lang.Shared + +class TaintableEnumerationTest extends DDSpecification { + + @Shared + protected static final AgentTracer.TracerAPI ORIGINAL_TRACER = AgentTracer.get() + + protected AgentTracer.TracerAPI tracer = Mock(AgentTracer.TracerAPI) + + protected IastContext iastCtx = Mock(IastContext) + + protected RequestContext reqCtx = Mock(RequestContext) { + getData(RequestContextSlot.IAST) >> iastCtx + } + + protected AgentSpan span = Mock(AgentSpan) { + getRequestContext() >> reqCtx + } + + protected PropagationModule module + + + void setup() { + AgentTracer.forceRegister(tracer) + module = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + } + + void cleanup() { + AgentTracer.forceRegister(ORIGINAL_TRACER) + InstrumentationBridge.clearIastModules() + } + + void 'underlying enumerated values are tainted with a name'() { + given: + final values = (1..10).collect { "value$it".toString() } + final origin = SourceTypes.REQUEST_PARAMETER_NAME + final name = 'test' + final enumeration = TaintableEnumeration.wrap(Collections.enumeration(values), module, origin, name) + + when: + final result = enumeration.collect() + + then: + result == values + values.each { 1 * module.taint(_, it, origin, name) } + 1 * tracer.activeSpan() >> span // only one access to the active context + } + + void 'underlying enumerated values are tainted with the value as a name'() { + given: + final values = (1..10).collect { "value$it".toString() } + final origin = SourceTypes.REQUEST_PARAMETER_NAME + final enumeration = TaintableEnumeration.wrap(Collections.enumeration(values), module, origin, true) + + when: + final result = enumeration.collect() + + then: + result == values + values.each { 1 * module.taint(_, it, origin, it) } + } + + void 'taintable enumeration leaves no trace in case of error'() { + given: + final origin = SourceTypes.REQUEST_PARAMETER_NAME + final enumeration = TaintableEnumeration.wrap(new BadEnumeration(), module, origin, true) + + when: + enumeration.hasMoreElements() + + then: + final first = thrown(Error) + first.stackTrace.find { it.className == TaintableEnumeration.name } == null + } + + private static class BadEnumeration implements Enumeration { + @Override + boolean hasMoreElements() { + throw new Error('Ooops!!!') + } + + @Override + String nextElement() { + throw new Error('Boom!!!') + } + } +} diff --git a/dd-java-agent/appsec/src/main/resources/default_config.json b/dd-java-agent/appsec/src/main/resources/default_config.json index a6e01468548..172558f06e3 100644 --- a/dd-java-agent/appsec/src/main/resources/default_config.json +++ b/dd-java-agent/appsec/src/main/resources/default_config.json @@ -1,7 +1,7 @@ { "version": "2.2", "metadata": { - "rules_version": "1.8.0" + "rules_version": "1.9.0" }, "rules": [ { @@ -3004,6 +3004,7 @@ ], "regex": "]*>[\\s\\S]*?", "options": { + "case_sensitive": false, "min_length": 8 } }, @@ -4207,7 +4208,6 @@ "name": "Remote Command Execution: Java process spawn (CVE-2017-9805)", "tags": { "type": "java_code_injection", - "crs_id": "944110", "category": "attack_attempt", "cwe": "94", "capec": "1000/152/242" @@ -4235,48 +4235,16 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "(?:runtime|processbuilder)", + "regex": "(?:unmarshaller|base64data|java\\.).*(?:runtime|processbuilder)", "options": { - "case_sensitive": true, - "min_length": 7 - } - }, - "operator": "match_regex" - }, - { - "parameters": { - "inputs": [ - { - "address": "server.request.query" - }, - { - "address": "server.request.body" - }, - { - "address": "server.request.path_params" - }, - { - "address": "server.request.headers.no_cookies" - }, - { - "address": "grpc.server.request.message" - }, - { - "address": "graphql.server.all_resolvers" - } - ], - "regex": "(?:unmarshaller|base64data|java\\.)", - "options": { - "case_sensitive": true, - "min_length": 5 + "case_sensitive": false, + "min_length": 13 } }, "operator": "match_regex" } ], - "transformers": [ - "lowercase" - ] + "transformers": [] }, { "id": "crs-944-130", @@ -4479,6 +4447,9 @@ }, { "address": "graphql.server.all_resolvers" + }, + { + "address": "server.request.headers.no_cookies" } ], "regex": "[#%$]{(?:[^}]+[^\\w\\s}\\-_][^}]+|\\d+-\\d+)}", @@ -4752,7 +4723,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\bqualysperiscope\\.com\\b" + "regex": "\\bqualysperiscope\\.com\\b|\\.oscomm\\." }, "operator": "match_regex" } @@ -4833,7 +4804,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:webhook\\.site|\\.canarytokens\\.com|vii\\.one|act1on3\\.ru|gdsburp\\.com)\\b" + "regex": "\\b(?:webhook\\.site|\\.canarytokens\\.com|vii\\.one|act1on3\\.ru|gdsburp\\.com|arcticwolf\\.net|oob\\.li|htbiw\\.com|h4\\.vc|mochan\\.cloud|imshopping\\.com|bootstrapnodejs\\.com|mooo-ng\\.com|securitytrails\\.com|canyouhackit\\.io|7bae\\.xyz)\\b" }, "operator": "match_regex" } @@ -4955,7 +4926,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:interact\\.sh|oast\\.(?:pro|live|site|online|fun|me))\\b" + "regex": "\\b(?:interact\\.sh|oast\\.(?:pro|live|site|online|fun|me)|indusfacefinder\\.in|where\\.land|syhunt\\.net|tssrt\\.de|boardofcyber\\.io|assetnote-callback\\.com|praetorianlabs\\.dev|netspi\\.sh)\\b" }, "operator": "match_regex" } @@ -4996,7 +4967,187 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b", + "regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)?r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b", + "options": { + "case_sensitive": false, + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-009", + "name": "WhiteHat Security OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "WhiteHatSecurity", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bwhsec(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)us\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-010", + "name": "Nessus OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Nessus", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\b\\.nessus\\.org\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-011", + "name": "Watchtowr OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Watchtowr", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bwatchtowr\\.com\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-012", + "name": "AppCheck NG OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "AppCheckNG", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "\\bptst\\.io\\b", "options": { "case_sensitive": false, "min_length": 7 @@ -5048,6 +5199,50 @@ ], "transformers": [] }, + { + "id": "dog-932-100", + "name": "Shell spawn executing network command", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + } + ], + "regex": "(?:(?:['\"\\x60({|;&]|(?:^|['\"\\x60({|;&])(?:cmd(?:\\.exe)?\\s+(?:/\\w(?::\\w+)?\\s+)*))(?:ping|curl|wget|telnet)|\\bnslookup)[\\s,]", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, { "id": "dog-934-001", "name": "XXE - XML file loads external entity", @@ -5056,7 +5251,7 @@ "category": "attack_attempt", "cwe": "91", "capec": "1000/152/248/250", - "confidence": "0" + "confidence": "1" }, "conditions": [ { @@ -5091,7 +5286,7 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "0" + "confidence": "1" }, "conditions": [ { @@ -5125,7 +5320,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|//)[^\\s'\"]+['\"]?", + "regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|dns:|//)[^\\s'\"]+['\"]?", "options": { "min_length": 14 } @@ -5171,7 +5366,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "https?:\\/\\/(?:.*\\.)?(?:bxss\\.in|xss\\.ht|js\\.rip)", + "regex": "https?:\\/\\/(?:.*\\.)?(?:bxss\\.(?:in|me)|xss\\.ht|js\\.rip)", "options": { "case_sensitive": false } @@ -6110,7 +6305,7 @@ "address": "graphql.server.all_resolvers" } ], - "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii.one|act1on3.ru)" + "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii\\.one|act1on3\\.ru)" }, "operator": "match_regex" } @@ -7610,6 +7805,35 @@ ], "transformers": [] }, + { + "id": "ua0-600-63x", + "name": "FeroxBuster", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "feroxbuster", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^feroxbuster/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, { "id": "ua0-600-6xx", "name": "Stealthy scanner", @@ -7631,7 +7855,7 @@ ] } ], - "regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; win32|4\\.0; Windows NT))?\\)", + "regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; (?:win32|Windows NT 5\\.0)|4\\.0; Windows NT))?\\)", "options": { "case_sensitive": false } diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index 0e31e9aa07f..810db9e782c 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -275,7 +275,7 @@ tasks.register('checkAgentJarSize').configure { doLast { // Arbitrary limit to prevent unintentional increases to the agent jar size // Raise or lower as required - def megs = (project.rootProject.hasProperty("agentIncludeCwsTls") && agentIncludeCwsTls) ? 28 : 27 + def megs = (project.rootProject.hasProperty("agentIncludeCwsTls") && agentIncludeCwsTls) ? 29 : 28 assert shadowJar.archiveFile.get().getAsFile().length() <= megs * 1024 * 1024 } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/AkkaHttpServerHeaders.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/AkkaHttpServerHeaders.java index 589bc3d81b4..c5aabac9926 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/AkkaHttpServerHeaders.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/AkkaHttpServerHeaders.java @@ -1,6 +1,7 @@ package datadog.trace.instrumentation.akkahttp; import akka.http.javadsl.model.HttpHeader; +import akka.http.javadsl.model.headers.RawRequestURI; import akka.http.javadsl.model.headers.RemoteAddress; import akka.http.javadsl.model.headers.TimeoutAccess; import akka.http.scaladsl.model.ContentType; @@ -45,7 +46,9 @@ private static void doForEachKey( for (final HttpHeader header : carrier.getHeaders()) { // skip synthetic headers - if (header instanceof RemoteAddress || header instanceof TimeoutAccess) { + if (header instanceof RemoteAddress + || header instanceof TimeoutAccess + || header instanceof RawRequestURI) { continue; } if (!classifier.accept(header.lowercaseName(), header.value())) { diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java index 064448c764f..046d6c96bdb 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java @@ -10,6 +10,7 @@ import akka.http.scaladsl.model.HttpResponse; import akka.http.scaladsl.model.ResponseEntity; import akka.http.scaladsl.model.StatusCode; +import akka.http.scaladsl.model.StatusCodes; import akka.util.ByteString; import datadog.appsec.api.blocking.BlockingContentType; import datadog.trace.api.gateway.BlockResponseFunction; @@ -95,7 +96,12 @@ public static HttpResponse maybeCreateBlockingResponse( RawHeader.create(e.getKey(), e.getValue())) .collect(ScalaListCollector.toScalaList()); - return HttpResponse.apply( - StatusCode.int2StatusCode(httpCode), headersList, entity, request.protocol()); + StatusCode code; + try { + code = StatusCode.int2StatusCode(httpCode); + } catch (RuntimeException e) { + code = StatusCodes.custom(httpCode, "Request Blocked", "", false, true); + } + return HttpResponse.apply(code, headersList, entity, request.protocol()); } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/MultipartUnmarshallersInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/MultipartUnmarshallersInstrumentation.java index b631cb87154..ab6e90c1517 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/MultipartUnmarshallersInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/MultipartUnmarshallersInstrumentation.java @@ -1,7 +1,7 @@ package datadog.trace.instrumentation.akkahttp.appsec; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.instrumentation.akkahttp.iast.TraitMethodMatchers.isTraitMethod; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ScalaTraitMatchers.isTraitMethod; import static net.bytebuddy.matcher.ElementMatchers.returns; import akka.http.scaladsl.unmarshalling.MultipartUnmarshallers; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/PredefinedFromEntityUnmarshallersInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/PredefinedFromEntityUnmarshallersInstrumentation.java index 0cda26c28bf..ab01b6c94a3 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/PredefinedFromEntityUnmarshallersInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/PredefinedFromEntityUnmarshallersInstrumentation.java @@ -2,7 +2,7 @@ import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf; -import static datadog.trace.instrumentation.akkahttp.iast.TraitMethodMatchers.isTraitMethod; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ScalaTraitMatchers.isTraitMethod; import static net.bytebuddy.matcher.ElementMatchers.returns; import akka.http.scaladsl.unmarshalling.PredefinedFromEntityUnmarshallers; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/SprayUnmarshallerInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/SprayUnmarshallerInstrumentation.java index eeb85b694ee..2e5aa79515f 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/SprayUnmarshallerInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/SprayUnmarshallerInstrumentation.java @@ -1,7 +1,7 @@ package datadog.trace.instrumentation.akkahttp.appsec; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.instrumentation.akkahttp.iast.TraitMethodMatchers.isTraitMethod; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ScalaTraitMatchers.isTraitMethod; import static net.bytebuddy.matcher.ElementMatchers.returns; import akka.http.scaladsl.unmarshalling.Unmarshaller; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/CookieHeaderInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/CookieHeaderInstrumentation.java index fa9e098b81e..1c8b35e4bb3 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/CookieHeaderInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/CookieHeaderInstrumentation.java @@ -12,13 +12,11 @@ import akka.http.scaladsl.model.headers.HttpCookiePair; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.Source; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; -import datadog.trace.api.iast.source.WebModule; -import java.util.ArrayList; -import java.util.List; import net.bytebuddy.asm.Advice; import scala.collection.Iterator; import scala.collection.immutable.Seq; @@ -56,23 +54,22 @@ static class TaintAllCookiesAdvice { @Source(SourceTypes.REQUEST_COOKIE_VALUE) static void after( @Advice.This HttpHeader cookie, @Advice.Return Seq cookiePairs) { - WebModule mod = InstrumentationBridge.WEB; PropagationModule prop = InstrumentationBridge.PROPAGATION; - if (mod == null || prop == null || cookiePairs == null || cookiePairs.isEmpty()) { + if (prop == null || cookiePairs == null || cookiePairs.isEmpty()) { return; } if (!prop.isTainted(cookie)) { return; } + final IastContext ctx = IastContext.Provider.get(); Iterator iterator = cookiePairs.iterator(); - List cookieNames = new ArrayList<>(); while (iterator.hasNext()) { HttpCookiePair pair = iterator.next(); - cookieNames.add(pair.name()); - prop.taint(SourceTypes.REQUEST_COOKIE_VALUE, pair.name(), pair.value()); + final String name = pair.name(), value = pair.value(); + prop.taint(ctx, name, SourceTypes.REQUEST_COOKIE_NAME, name); + prop.taint(ctx, value, SourceTypes.REQUEST_COOKIE_VALUE, name); } - mod.onCookieNames(cookieNames); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HeaderNameCallSite.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HeaderNameCallSite.java index 6122b91b361..f7f330a14b5 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HeaderNameCallSite.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HeaderNameCallSite.java @@ -26,7 +26,7 @@ public static String after(@CallSite.This HttpHeader header, @CallSite.Return St return result; } try { - module.taintIfInputIsTainted(SourceTypes.REQUEST_HEADER_NAME, result, result, header); + module.taintIfTainted(result, header, SourceTypes.REQUEST_HEADER_NAME, result); } catch (final Throwable e) { module.onUnexpectedException("onHeaderNames threw", e); } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpHeaderSubclassesInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpHeaderSubclassesInstrumentation.java index 2c9b5cb507b..57cd04ce127 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpHeaderSubclassesInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpHeaderSubclassesInstrumentation.java @@ -62,7 +62,7 @@ static void onExit(@Advice.This HttpHeader h, @Advice.Return String retVal) { return; } - propagation.taintIfInputIsTainted(retVal, h); + propagation.taintIfTainted(retVal, h); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpRequestInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpRequestInstrumentation.java index 16b124624f8..a0c5f432e85 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpRequestInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/HttpRequestInstrumentation.java @@ -13,6 +13,7 @@ import akka.http.scaladsl.model.HttpRequest; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.Propagation; import datadog.trace.api.iast.Source; @@ -70,6 +71,7 @@ static void onExit( return; } + final IastContext ctx = IastContext.Provider.get(); Iterator iterator = headers.iterator(); while (iterator.hasNext()) { HttpHeader h = iterator.next(); @@ -78,7 +80,7 @@ static void onExit( } // unfortunately, the call to h.value() is instrumented, but // because the call to taint() only happens after, the call is a noop - propagation.taintObject(SourceTypes.REQUEST_HEADER_VALUE, h.name(), h.value(), h); + propagation.taint(ctx, h, SourceTypes.REQUEST_HEADER_VALUE, h.name(), h.value()); } } } @@ -98,7 +100,7 @@ static void onExit( return; } - propagation.taintIfInputIsTainted(entity, thiz); + propagation.taintIfTainted(entity, thiz); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/PathMatcherInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/PathMatcherInstrumentation.java index 1a4dcf27ce2..80c3b2687f0 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/PathMatcherInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/PathMatcherInstrumentation.java @@ -11,6 +11,7 @@ import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.Source; import datadog.trace.api.iast.SourceTypes; @@ -68,11 +69,8 @@ static void onExit( } if (value instanceof String) { - module.taint( - reqCtx.getData(RequestContextSlot.IAST), - SourceTypes.REQUEST_PATH_PARAMETER, - null, - (String) value); + final IastContext ctx = reqCtx.getData(RequestContextSlot.IAST); + module.taint(ctx, value, SourceTypes.REQUEST_PATH_PARAMETER); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/RequestContextInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/RequestContextInstrumentation.java index 0cdd0266796..f18148a7ceb 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/RequestContextInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/RequestContextInstrumentation.java @@ -53,7 +53,7 @@ static void onExit( return; } - propagation.taintIfInputIsTainted(request, requestContext); + propagation.taintIfTainted(request, requestContext); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/TraitMethodMatchers.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/TraitMethodMatchers.java index d9db76ece90..fb58538102e 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/TraitMethodMatchers.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/TraitMethodMatchers.java @@ -1,46 +1,15 @@ package datadog.trace.instrumentation.akkahttp.iast; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isStatic; -import static net.bytebuddy.matcher.ElementMatchers.not; +import static datadog.trace.agent.tooling.bytebuddy.matcher.ScalaTraitMatchers.isTraitMethod; import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; public class TraitMethodMatchers { - public static ElementMatcher.Junction isTraitMethod( - String traitName, String name, Object... argumentTypes) { - - ElementMatcher.Junction scalaOldArgs = - isStatic() - .and(takesArguments(argumentTypes.length + 1)) - .and(takesArgument(0, named(traitName))); - ElementMatcher.Junction scalaNewArgs = - not(isStatic()).and(takesArguments(argumentTypes.length)); - - for (int i = 0; i < argumentTypes.length; i++) { - Object argumentType = argumentTypes[i]; - ElementMatcher matcher; - if (argumentType instanceof ElementMatcher) { - matcher = (ElementMatcher) argumentType; - } else { - matcher = named((String) argumentType); - } - scalaOldArgs = scalaOldArgs.and(takesArgument(i + 1, matcher)); - scalaNewArgs = scalaNewArgs.and(takesArgument(i, matcher)); - } - - return isMethod().and(named(name)).and(scalaOldArgs.or(scalaNewArgs)); - } - public static ElementMatcher.Junction isTraitDirectiveMethod( - String traitName, String name, String... argumentTypes) { - + String traitName, String name, Object... argumentTypes) { return isTraitMethod(traitName, name, (Object[]) argumentTypes) .and(returns(named("akka.http.scaladsl.server.Directive"))); } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/UriInstrumentation.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/UriInstrumentation.java index 9faab16a32b..49a03c9bf32 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/UriInstrumentation.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/UriInstrumentation.java @@ -11,13 +11,12 @@ import akka.http.scaladsl.model.Uri; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.Propagation; import datadog.trace.api.iast.Source; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; -import datadog.trace.api.iast.source.WebModule; -import java.util.Collections; import net.bytebuddy.asm.Advice; import scala.Tuple2; import scala.collection.Iterator; @@ -70,7 +69,7 @@ static void after(@Advice.This Uri uri, @Advice.Return scala.Option ret) if (mod == null || ret.isEmpty()) { return; } - mod.taintIfInputIsTainted(ret.get(), uri); + mod.taintIfTainted(ret.get(), uri); } } @@ -80,9 +79,8 @@ public static class TaintQueryAdvice { @Advice.OnMethodExit(suppress = Throwable.class) @Source(SourceTypes.REQUEST_PARAMETER_VALUE) static void after(@Advice.This /*Uri*/ Object uri, @Advice.Return Uri.Query ret) { - WebModule web = InstrumentationBridge.WEB; PropagationModule prop = InstrumentationBridge.PROPAGATION; - if (prop == null || web == null || ret.isEmpty()) { + if (prop == null || ret.isEmpty()) { return; } @@ -90,11 +88,13 @@ static void after(@Advice.This /*Uri*/ Object uri, @Advice.Return Uri.Query ret) return; } + final IastContext ctx = IastContext.Provider.get(); Iterator> iterator = ret.iterator(); while (iterator.hasNext()) { Tuple2 pair = iterator.next(); - web.onParameterNames(Collections.singleton(pair._1())); - prop.taint(SourceTypes.REQUEST_PARAMETER_VALUE, pair._1(), pair._2()); + final String name = pair._1(), value = pair._2(); + prop.taint(ctx, name, SourceTypes.REQUEST_PARAMETER_NAME, name); + prop.taint(ctx, value, SourceTypes.REQUEST_PARAMETER_VALUE, name); } } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintCookieFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintCookieFunction.java index 2d9c23c51ba..0955a0a9e49 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintCookieFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintCookieFunction.java @@ -16,10 +16,13 @@ public Tuple1 apply(Tuple1 v1) { HttpCookiePair httpCookiePair = v1._1(); PropagationModule mod = InstrumentationBridge.PROPAGATION; - if (mod == null) { + if (mod == null || httpCookiePair == null) { return v1; } - mod.taint(SourceTypes.REQUEST_COOKIE_VALUE, httpCookiePair.name(), httpCookiePair.value()); + final String name = httpCookiePair.name(); + final String value = httpCookiePair.value(); + mod.taint(name, SourceTypes.REQUEST_COOKIE_NAME, name); + mod.taint(value, SourceTypes.REQUEST_COOKIE_VALUE, name); return v1; } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintFutureHelper.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintFutureHelper.java index 922397f52f6..c1ff9dbc8c2 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintFutureHelper.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintFutureHelper.java @@ -10,7 +10,7 @@ public static Future wrapFuture( Future f, Object input, PropagationModule mod, ExecutionContext ec) { JFunction1 mapf = t -> { - mod.taintIfInputIsTainted(t, input); + mod.taintIfTainted(t, input); return t; }; return f.map(mapf, ec); diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMapFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMapFunction.java index 40d75a9e15f..705b1921742 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMapFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMapFunction.java @@ -1,9 +1,9 @@ package datadog.trace.instrumentation.akkahttp.iast.helpers; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; -import datadog.trace.api.iast.source.WebModule; import scala.Tuple1; import scala.Tuple2; import scala.collection.Iterator; @@ -19,18 +19,17 @@ public Tuple1> apply(Tuple1> v1) { Map m = v1._1; PropagationModule prop = InstrumentationBridge.PROPAGATION; - WebModule web = InstrumentationBridge.WEB; - if (web == null || prop == null || m == null) { + if (prop == null || m == null || m.isEmpty()) { return v1; } - java.util.List keysAsCollection = ScalaToJava.keySetAsCollection(m); - web.onParameterNames(keysAsCollection); - + final IastContext ctx = IastContext.Provider.get(); Iterator> iterator = m.iterator(); while (iterator.hasNext()) { Tuple2 e = iterator.next(); - prop.taint(SourceTypes.REQUEST_PARAMETER_VALUE, e._1(), e._2()); + final String name = e._1(), value = e._2(); + prop.taint(ctx, name, SourceTypes.REQUEST_PARAMETER_NAME, name); + prop.taint(ctx, value, SourceTypes.REQUEST_PARAMETER_VALUE, name); } return v1; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMultiMapFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMultiMapFunction.java index dcfe919d598..b72ac179c0b 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMultiMapFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintMultiMapFunction.java @@ -1,7 +1,9 @@ package datadog.trace.instrumentation.akkahttp.iast.helpers; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; -import datadog.trace.api.iast.source.WebModule; +import datadog.trace.api.iast.SourceTypes; +import datadog.trace.api.iast.propagation.PropagationModule; import scala.Tuple1; import scala.Tuple2; import scala.collection.Iterator; @@ -17,19 +19,21 @@ public class TaintMultiMapFunction public Tuple1>> apply(Tuple1>> v1) { Map> m = v1._1; - WebModule mod = InstrumentationBridge.WEB; - if (mod == null || m == null) { + PropagationModule mod = InstrumentationBridge.PROPAGATION; + if (mod == null || m == null || m.isEmpty()) { return v1; } - java.util.List keysAsCollection = ScalaToJava.keySetAsCollection(m); - mod.onParameterNames(keysAsCollection); - + final IastContext ctx = IastContext.Provider.get(); Iterator>> entriesIterator = m.iterator(); while (entriesIterator.hasNext()) { Tuple2> e = entriesIterator.next(); + final String name = e._1(); + mod.taint(ctx, name, SourceTypes.REQUEST_PARAMETER_NAME, name); List values = e._2(); - mod.onParameterValues(e._1(), ScalaToJava.listAsList(values)); + for (final String value : ScalaToJava.listAsList(values)) { + mod.taint(ctx, value, SourceTypes.REQUEST_PARAMETER_VALUE, name); + } } return v1; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintOptionalCookieFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintOptionalCookieFunction.java index 1f6d8bb62a2..620bea2ebf8 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintOptionalCookieFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintOptionalCookieFunction.java @@ -17,13 +17,14 @@ public Tuple1> apply(Tuple1> v1) { Option httpCookiePair = v1._1(); PropagationModule mod = InstrumentationBridge.PROPAGATION; - if (mod == null || httpCookiePair.isEmpty()) { + if (mod == null || httpCookiePair == null || httpCookiePair.isEmpty()) { return v1; } - mod.taint( - SourceTypes.REQUEST_COOKIE_VALUE, - httpCookiePair.get().name(), - httpCookiePair.get().value()); + final HttpCookiePair cookie = httpCookiePair.get(); + final String name = cookie.name(); + final String value = cookie.value(); + mod.taint(name, SourceTypes.REQUEST_COOKIE_NAME, name); + mod.taint(value, SourceTypes.REQUEST_COOKIE_VALUE, name); return v1; } } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestContextFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestContextFunction.java index c4c8cbb2d5c..bc86e937643 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestContextFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestContextFunction.java @@ -16,10 +16,10 @@ public Tuple1 apply(Tuple1 v1) { RequestContext reqCtx = v1._1(); PropagationModule mod = InstrumentationBridge.PROPAGATION; - if (mod == null) { + if (mod == null || reqCtx == null) { return v1; } - mod.taintObject(SourceTypes.REQUEST_BODY, reqCtx); + mod.taint(reqCtx, SourceTypes.REQUEST_BODY); return v1; } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestFunction.java index e1b9a5e4af4..7894326ab89 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintRequestFunction.java @@ -17,10 +17,10 @@ public Tuple1 apply(Tuple1 v1) { HttpRequest httpRequest = v1._1(); PropagationModule mod = InstrumentationBridge.PROPAGATION; - if (mod == null) { + if (mod == null || httpRequest == null) { return v1; } - mod.taintObject(SourceTypes.REQUEST_BODY, httpRequest); + mod.taint(httpRequest, SourceTypes.REQUEST_BODY); return v1; } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSeqFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSeqFunction.java index 0d41fc32e56..fdec8e35b4e 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSeqFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSeqFunction.java @@ -1,9 +1,9 @@ package datadog.trace.instrumentation.akkahttp.iast.helpers; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; -import datadog.trace.api.iast.source.WebModule; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Set; @@ -22,22 +22,22 @@ public class TaintSeqFunction public Tuple1>> apply(Tuple1>> v1) { Seq> seq = v1._1; - WebModule web = InstrumentationBridge.WEB; PropagationModule prop = InstrumentationBridge.PROPAGATION; - if (web == null || prop == null || seq == null) { + if (prop == null || seq == null || seq.isEmpty()) { return v1; } + final IastContext ctx = IastContext.Provider.get(); Iterator> iterator = seq.iterator(); - Set seenKeys = Collections.newSetFromMap(new IdentityHashMap()); + Set seenKeys = Collections.newSetFromMap(new IdentityHashMap<>()); while (iterator.hasNext()) { Tuple2 t = iterator.next(); String name = t._1(); String value = t._2(); if (seenKeys.add(name)) { - web.onParameterNames(Collections.singleton(name)); + prop.taint(ctx, name, SourceTypes.REQUEST_PARAMETER_NAME, name); } - prop.taint(SourceTypes.REQUEST_PARAMETER_VALUE, name, value); + prop.taint(ctx, value, SourceTypes.REQUEST_PARAMETER_VALUE, name); } return v1; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSingleParameterFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSingleParameterFunction.java index e3130be8062..f6c1073fff8 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSingleParameterFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintSingleParameterFunction.java @@ -1,5 +1,6 @@ package datadog.trace.instrumentation.akkahttp.iast.helpers; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; @@ -40,15 +41,16 @@ public Tuple1 apply(Tuple1 v1) { } if (value instanceof Iterable) { - Iterator iterator = ((Iterable) value).iterator(); + final IastContext ctx = IastContext.Provider.get(); + Iterator iterator = ((Iterable) value).iterator(); while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof String) { - mod.taint(SourceTypes.REQUEST_PARAMETER_VALUE, paramName, (String) o); + mod.taint(ctx, (String) o, SourceTypes.REQUEST_PARAMETER_VALUE, paramName); } } } else if (value instanceof String) { - mod.taint(SourceTypes.REQUEST_PARAMETER_VALUE, paramName, (String) value); + mod.taint((String) value, SourceTypes.REQUEST_PARAMETER_VALUE, paramName); } return v1; diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUnmarshaller.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUnmarshaller.java index 88d1580643a..d109cc92d9d 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUnmarshaller.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUnmarshaller.java @@ -28,7 +28,7 @@ public TaintUnmarshaller(PropagationModule propagationModule, Unmarshaller @Override public Future apply(A value, ExecutionContext ec, Materializer materializer) { - propagationModule.taintObject(SourceTypes.REQUEST_BODY, value); + propagationModule.taint(value, SourceTypes.REQUEST_BODY); return delegate.apply(value, ec, materializer); } diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUriFunction.java b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUriFunction.java index d11830b047e..5feb8315119 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUriFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/iast/helpers/TaintUriFunction.java @@ -18,7 +18,7 @@ public Tuple1 apply(Tuple1 v1) { if (mod == null) { return v1; } - mod.taintObject(SourceTypes.REQUEST_QUERY, uri); + mod.taint(uri, SourceTypes.REQUEST_QUERY); return v1; } diff --git a/dd-java-agent/instrumentation/akka-http-10.2-iast/src/main/java/datadog/trace/instrumentation/akkahttp102/iast/helpers/TaintParametersFunction.java b/dd-java-agent/instrumentation/akka-http-10.2-iast/src/main/java/datadog/trace/instrumentation/akkahttp102/iast/helpers/TaintParametersFunction.java index 213ee7638e1..87008ac5ab4 100644 --- a/dd-java-agent/instrumentation/akka-http-10.2-iast/src/main/java/datadog/trace/instrumentation/akkahttp102/iast/helpers/TaintParametersFunction.java +++ b/dd-java-agent/instrumentation/akka-http-10.2-iast/src/main/java/datadog/trace/instrumentation/akkahttp102/iast/helpers/TaintParametersFunction.java @@ -1,5 +1,6 @@ package datadog.trace.instrumentation.akkahttp102.iast.helpers; +import datadog.trace.api.iast.IastContext; import datadog.trace.api.iast.InstrumentationBridge; import datadog.trace.api.iast.SourceTypes; import datadog.trace.api.iast.propagation.PropagationModule; @@ -33,15 +34,16 @@ public Tuple1 apply(Tuple1 v1) { } if (value instanceof Iterable) { - Iterator iterator = ((Iterable) value).iterator(); + final IastContext ctx = IastContext.Provider.get(); + Iterator iterator = ((Iterable) value).iterator(); while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof String) { - mod.taint(SourceTypes.REQUEST_PARAMETER_VALUE, paramName, (String) o); + mod.taint(ctx, (String) o, SourceTypes.REQUEST_PARAMETER_VALUE, paramName); } } } else if (value instanceof String) { - mod.taint(SourceTypes.REQUEST_PARAMETER_VALUE, paramName, (String) value); + mod.taint((String) value, SourceTypes.REQUEST_PARAMETER_VALUE, paramName); } return v1; diff --git a/dd-java-agent/instrumentation/armeria-grpc/src/main/java/datadog/trace/instrumentation/armeria/grpc/server/TracingServerInterceptor.java b/dd-java-agent/instrumentation/armeria-grpc/src/main/java/datadog/trace/instrumentation/armeria/grpc/server/TracingServerInterceptor.java index 9e7aed6b735..158c7c72d01 100644 --- a/dd-java-agent/instrumentation/armeria-grpc/src/main/java/datadog/trace/instrumentation/armeria/grpc/server/TracingServerInterceptor.java +++ b/dd-java-agent/instrumentation/armeria-grpc/src/main/java/datadog/trace/instrumentation/armeria/grpc/server/TracingServerInterceptor.java @@ -68,7 +68,9 @@ public ServerCall.Listener interceptCall( final AgentSpan span = startSpan(DECORATE.instrumentationNames()[0], GRPC_SERVER, spanContext).setMeasured(true); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, SERVER_PATHWAY_EDGE_TAGS, 0); + AgentTracer.get() + .getDataStreamsMonitoring() + .setCheckpoint(span, SERVER_PATHWAY_EDGE_TAGS, 0, 0); RequestContext reqContext = span.getRequestContext(); if (reqContext != null) { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle index 2e036a050e1..6625bc50fb0 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/build.gradle @@ -49,6 +49,8 @@ dependencies { // needed for kinesis: testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: versions.jackson + testImplementation group: 'org.json', name: 'json', version: '20231013' + test_before_1_11_106Implementation(group: 'com.amazonaws', name: 'aws-java-sdk-s3') { version { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy index 9bf73151744..9874e1a5510 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/AWS1ClientTest.groovy @@ -39,6 +39,7 @@ import datadog.trace.api.Config import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.Flaky +import org.json.XML import spock.lang.AutoCleanup import spock.lang.Shared @@ -61,12 +62,23 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { def credentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()) @Shared def responseBody = new AtomicReference() + @Shared + def jsonPointer = new AtomicReference() + @AutoCleanup @Shared def server = httpServer { handlers { all { - response.status(200).send(responseBody.get()) + def body = responseBody.get() + if (request.headers.get("Content-Type")?.contains("json")) { + def json = XML.toJSONObject(body) + if (jsonPointer.get() != null) { + json = json.query(jsonPointer.get()) + } + body = json.toString() + } + response.status(200).send(body) } } } @@ -131,6 +143,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { def "send #operation request with mocked response"() { setup: responseBody.set(body) + jsonPointer.set(jsonPointerStr) when: def response = call.call(client) @@ -181,19 +194,22 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { server.lastRequest.headers.get("x-datadog-trace-id") == null server.lastRequest.headers.get("x-datadog-parent-id") == null + cleanup: + jsonPointer.set(null) + where: - service | operation | method | path | client | call | additionalTags | body | peerService - "S3" | "CreateBucket" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | "aws.bucket.name" - "S3" | "GetObject" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | "aws.bucket.name" - "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" - "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | "aws.stream.name" + service | operation | method | path | client | call | additionalTags | body | peerService | jsonPointerStr + "S3" | "CreateBucket" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | "aws.bucket.name" | null + "S3" | "GetObject" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | "aws.bucket.name" | null + "DynamoDBv2" | "CreateTable" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | "aws.table.name" | null + "Kinesis" | "DeleteStream" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | "aws.stream.name" | null "SQS" | "CreateQueue" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue", "queuename": "somequeue"] | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 - """ | "aws.queue.name" + """ | "aws.queue.name" | "/CreateQueueResponse/CreateQueueResult" "SQS" | "SendMessage" | "POST" | "/someurl" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("someurl", "")) } | ["aws.queue.url": "someurl"] | """ @@ -203,7 +219,7 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { 27daac76-34dd-47df-bd01-1f6e873584a0 - """ | "aws.queue.url" + """ | "aws.queue.url" | "/SendMessageResponse/SendMessageResult" "SNS" | "Publish" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ @@ -211,22 +227,21 @@ abstract class AWS1ClientTest extends VersionedNamingTestBase { d74b8436-ae13-5ab4-a9ff-ce54dfea72a0 - """ | "aws.topic.name" + """ | "aws.topic.name" | "/PublishResponse/PublishResult" "EC2" | "AllocateAddress" | "POST" | "/" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.allocateAddress() } | [:] | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard - """ | null - + """ | null | "/AllocateAddressResponse" "RDS" | "DeleteOptionGroup" | "POST" | "/" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 - """ | null + """ | null | null } def "send #operation request to closed port"() { diff --git a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy index e59540b4ab5..a03133a45c8 100644 --- a/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sdk-1.11.0/src/test/groovy/LegacyAWS1ClientForkedTest.groovy @@ -36,6 +36,7 @@ import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.test.util.Flaky import org.apache.http.conn.HttpHostConnectException import org.apache.http.impl.execchain.RequestAbortedException +import org.json.XML import spock.lang.AutoCleanup import spock.lang.Shared @@ -68,12 +69,23 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { def credentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()) @Shared def responseBody = new AtomicReference() + @Shared + def jsonPointer = new AtomicReference() + @AutoCleanup @Shared def server = httpServer { handlers { all { - response.status(200).send(responseBody.get()) + def body = responseBody.get() + if (request.headers.get("Content-Type")?.contains("json")) { + def json = XML.toJSONObject(body) + if (jsonPointer.get() != null) { + json = json.query(jsonPointer.get()) + } + body = json.toString() + } + response.status(200).send(body) } } } @@ -124,6 +136,7 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { def "send #operation request with mocked response"() { setup: responseBody.set(body) + jsonPointer.set(jsonPointerStr) when: def response = call.call(client) @@ -187,18 +200,21 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { server.lastRequest.headers.get("x-datadog-trace-id") == null server.lastRequest.headers.get("x-datadog-parent-id") == null + cleanup: + jsonPointer.set(null) + where: - service | operation | ddService | method | path | client | call | additionalTags | body - "S3" | "CreateBucket" | "java-aws-sdk" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" - "S3" | "GetObject" | "java-aws-sdk" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" - "DynamoDBv2" | "CreateTable" | "java-aws-sdk" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" - "Kinesis" | "DeleteStream" | "java-aws-sdk" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" + service | operation | ddService | method | path | client | call | additionalTags | body | jsonPointerStr + "S3" | "CreateBucket" | "java-aws-sdk" | "PUT" | "/testbucket/" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createBucket("testbucket") } | ["aws.bucket.name": "testbucket", "bucketname": "testbucket"] | "" | null + "S3" | "GetObject" | "java-aws-sdk" | "GET" | "/someBucket/someKey" | AmazonS3ClientBuilder.standard().withPathStyleAccessEnabled(true).withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.getObject("someBucket", "someKey") } | ["aws.bucket.name": "someBucket", "bucketname": "someBucket"] | "" | null + "DynamoDBv2" | "CreateTable" | "java-aws-sdk" | "POST" | "/" | AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createTable(new CreateTableRequest("sometable", null)) } | ["aws.table.name": "sometable", "tablename": "sometable"] | "" | null + "Kinesis" | "DeleteStream" | "java-aws-sdk" | "POST" | "/" | AmazonKinesisClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteStream(new DeleteStreamRequest().withStreamName("somestream")) } | ["aws.stream.name": "somestream", "streamname": "somestream"] | "" | null "SQS" | "CreateQueue" | "java-aws-sdk" | "POST" | "/" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.createQueue(new CreateQueueRequest("somequeue")) } | ["aws.queue.name": "somequeue", "queuename": "somequeue"] | """ https://queue.amazonaws.com/123456789012/MyQueue 7a62c49f-347e-4fc4-9331-6e8e7a96aa73 - """ + """ | "/CreateQueueResponse/CreateQueueResult" "SQS" | "SendMessage" | "sqs" | "POST" | "/someurl" | AmazonSQSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.sendMessage(new SendMessageRequest("someurl", "")) } | ["aws.queue.url": "someurl"] | """ @@ -208,7 +224,7 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { 27daac76-34dd-47df-bd01-1f6e873584a0 - """ + """ | "/SendMessageResponse/SendMessageResult" "SNS" | "Publish" | "sns" | "POST" | "/" | AmazonSNSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.publish(new PublishRequest("arn:aws:sns::123:some-topic", "")) } | ["aws.topic.name": "some-topic", "topicname": "some-topic"] | """ @@ -216,21 +232,21 @@ class LegacyAWS1ClientForkedTest extends AgentTestRunner { d74b8436-ae13-5ab4-a9ff-ce54dfea72a0 - """ + """ | "/PublishResponse/PublishResult" "EC2" | "AllocateAddress" | "java-aws-sdk" | "POST" | "/" | AmazonEC2ClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.allocateAddress() } | [:] | """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE 192.0.2.1 standard - """ + """ | null "RDS" | "DeleteOptionGroup" | "java-aws-sdk" | "POST" | "/" | AmazonRDSClientBuilder.standard().withEndpointConfiguration(endpoint).withCredentials(credentialsProvider).build() | { c -> c.deleteOptionGroup(new DeleteOptionGroupRequest()) } | [:] | """ 0ac9cda2-bbf4-11d3-f92b-31fa5e8dbc99 - """ + """ | null } def "send #operation request to closed port"() { diff --git a/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle index 3f9a4eca2cf..e979e34e7fd 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sqs-1.0/build.gradle @@ -12,6 +12,7 @@ muzzle { apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') dependencies { compileOnly group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.0' @@ -23,7 +24,7 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:jms') // SQS<->JMS testing: - testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.2.3' + testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.4.7' testImplementation group: 'com.amazonaws', name: 'amazon-sqs-java-messaging-lib', version: '1.0.8' latestDepTestImplementation group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '+' diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle b/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle index a292ea34a70..adff4418500 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/build.gradle @@ -13,6 +13,8 @@ muzzle { apply from: "$rootDir/gradle/java.gradle" addTestSuiteForDir('latestDepTest', 'test') +addTestSuiteExtendingForDir('latestDepForkedTest', 'latestDepTest', 'test') + dependencies { compileOnly group: 'software.amazon.awssdk', name: 'sqs', version: '2.2.0' @@ -24,7 +26,7 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:jms') // SQS<->JMS testing: - testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.2.3' + testImplementation group: 'org.elasticmq', name: 'elasticmq-rest-sqs_2.13', version: '1.4.7' testImplementation group: 'com.amazonaws', name: 'amazon-sqs-java-messaging-lib', version: '2.0.0' latestDepTestImplementation group: 'software.amazon.awssdk', name: 'sqs', version: '2.20.33' diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sqs/TracingIterator.java b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sqs/TracingIterator.java index c5b47dff8af..ee58cbf3538 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sqs/TracingIterator.java +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/sqs/TracingIterator.java @@ -91,7 +91,7 @@ protected void startNewMessageSpan(Message message) { sortedTags.put(DIRECTION_TAG, DIRECTION_IN); sortedTags.put(TOPIC_TAG, urlFileName(queueUrl)); sortedTags.put(TYPE_TAG, "sqs"); - AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, sortedTags, 0); + AgentTracer.get().getDataStreamsMonitoring().setCheckpoint(span, sortedTags, 0, 0); CONSUMER_DECORATE.afterStart(span); CONSUMER_DECORATE.onConsume(span, queueUrl, requestId); diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy index d2692a61d0c..84ad8f22268 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/LegacySqsClientForkedTest.groovy @@ -94,7 +94,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "SendMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -140,7 +140,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -222,7 +222,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "SendMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -285,7 +285,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "DeleteMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -330,7 +330,7 @@ class LegacySqsClientForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy index 05b5b5a0971..ad1219478f5 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/SqsClientTest.groovy @@ -394,7 +394,7 @@ abstract class SqsClientTest extends VersionedNamingTestBase { "aws.operation" "DeleteMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } diff --git a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy index 7c864453979..b19b99c0c82 100644 --- a/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-sqs-2.0/src/test/groovy/TimeInQueueForkedTest.groovy @@ -302,7 +302,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "aws.operation" "SendMessageBatch" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags() } } @@ -325,7 +325,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "aws.operation" "ReceiveMessage" "aws.agent" "java-aws-sdk" "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags(parent.resourceName as String == "Sqs.SendMessageBatch") } } @@ -344,7 +344,7 @@ class TimeInQueueForkedTest extends AgentTestRunner { "$Tags.COMPONENT" "java-aws-sdk" "$Tags.SPAN_KIND" Tags.SPAN_KIND_BROKER "aws.queue.url" "http://localhost:${address.port}/000000000000/somequeue" - "aws.requestId" "00000000-0000-0000-0000-000000000000" + "aws.requestId" { it.trim() == "00000000-0000-0000-0000-000000000000" } // the test server seem messing with request id and insert \n defaultTags(true) } } diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index 3847c8b9dd0..1d22aba7ebe 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -99,7 +99,7 @@ subprojects { Project subProj -> onlyIf { !project.rootProject.hasProperty("skipInstTests") } - if (subTask.name == 'latestDepTest') { + if (subTask.name in ['latestDepTest', 'latestDepForkedTest']) { subTask.jvmArgs '-Dtest.dd.latestDepTest=true' } } diff --git a/dd-java-agent/instrumentation/commons-fileupload/build.gradle b/dd-java-agent/instrumentation/commons-fileupload/build.gradle new file mode 100644 index 00000000000..eadd96edbea --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/build.gradle @@ -0,0 +1,13 @@ + +apply from: "$rootDir/gradle/java.gradle" +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'org.apache.commons', name: 'commons-fileupload2', version: '2.0.0-M1' + testImplementation group: 'org.apache.commons', name: 'commons-fileupload2', version: '2.0.0-M1' + testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '7.0.0' + + + testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter') + latestDepTestImplementation group: 'org.apache.commons', name: 'commons-fileupload2', version: '+' +} diff --git a/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java b/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java new file mode 100644 index 00000000000..8f67ae94cb0 --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/src/main/java/datadog/trace/instrumentation/commons/fileupload/CommonsFileuploadInstrumenter.java @@ -0,0 +1,65 @@ +package datadog.trace.instrumentation.commons.fileupload; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.api.iast.IastContext; +import datadog.trace.api.iast.InstrumentationBridge; +import datadog.trace.api.iast.Source; +import datadog.trace.api.iast.SourceTypes; +import datadog.trace.api.iast.propagation.PropagationModule; +import java.util.Map; +import net.bytebuddy.asm.Advice; + +@AutoService(Instrumenter.class) +public class CommonsFileuploadInstrumenter extends Instrumenter.Iast + implements Instrumenter.ForKnownTypes { + + public CommonsFileuploadInstrumenter() { + super("commons-fileupload"); + } + + @Override + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice( + isMethod() + .and(named("parse")) + .and(isPublic()) + .and(returns(Map.class)) + .and(takesArguments(char[].class, int.class, int.class, char.class)), + getClass().getName() + "$ParseAdvice"); + } + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "org.apache.commons.fileupload.ParameterParser", + "org.apache.tomcat.util.http.fileupload.ParameterParser" + }; + } + + public static class ParseAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + @Source(SourceTypes.REQUEST_MULTIPART_PARAMETER) + public static Map onExit(@Advice.Return final Map map) { + if (!map.isEmpty()) { + final PropagationModule module = InstrumentationBridge.PROPAGATION; + if (module != null) { + final IastContext ctx = IastContext.Provider.get(); + for (final Map.Entry entry : map.entrySet()) { + if (entry.getValue() != null) { + module.taint( + ctx, entry.getValue(), SourceTypes.REQUEST_MULTIPART_PARAMETER, entry.getKey()); + } + } + } + } + return map; + } + } +} diff --git a/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy b/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy new file mode 100644 index 00000000000..4e780a02d1a --- /dev/null +++ b/dd-java-agent/instrumentation/commons-fileupload/src/test/groovy/MultipartInstrumentationTest.groovy @@ -0,0 +1,40 @@ +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.SourceTypes +import datadog.trace.api.iast.propagation.PropagationModule + + +class MultipartInstrumentationTest extends AgentTestRunner { + @Override + protected void configurePreAgent() { + injectSysConfig('dd.iast.enabled', 'true') + } + + @Override + void cleanup() { + InstrumentationBridge.clearIastModules() + } + + void 'test commons fileupload2 ParameterParser.parse'() { + given: + final module = Mock(PropagationModule) + InstrumentationBridge.registerIastModule(module) + final content = "Content-Disposition: form-data; name=\"file\"; filename=\"=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\"\r\n" + final parser = clazz.newInstance() + + when: + parser.parse(content, new char[]{ + ',', ';' + }) + + then: + 1 * module.taint(null, 'file', SourceTypes.REQUEST_MULTIPART_PARAMETER, 'name') + 1 * module.taint(null, _, SourceTypes.REQUEST_MULTIPART_PARAMETER, 'filename') + 0 * _ + + where: + clazz | _ + org.apache.commons.fileupload.ParameterParser | _ + org.apache.tomcat.util.http.fileupload.ParameterParser | _ + } +} diff --git a/dd-java-agent/instrumentation/commons-httpclient-2/src/main/java/datadog/trace/instrumentation/commonshttpclient/IastHttpMethodBaseInstrumentation.java b/dd-java-agent/instrumentation/commons-httpclient-2/src/main/java/datadog/trace/instrumentation/commonshttpclient/IastHttpMethodBaseInstrumentation.java index 8371ad69442..11522f224fd 100644 --- a/dd-java-agent/instrumentation/commons-httpclient-2/src/main/java/datadog/trace/instrumentation/commonshttpclient/IastHttpMethodBaseInstrumentation.java +++ b/dd-java-agent/instrumentation/commons-httpclient-2/src/main/java/datadog/trace/instrumentation/commonshttpclient/IastHttpMethodBaseInstrumentation.java @@ -51,7 +51,7 @@ public static void afterCtor( @Advice.This final Object self, @Advice.Argument(0) final Object argument) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { - module.taintIfInputIsTainted(self, argument); + module.taintIfTainted(self, argument); } } } diff --git a/dd-java-agent/instrumentation/commons-httpclient-2/src/test/groovy/IastCommonsHttpClientInstrumentationTest.groovy b/dd-java-agent/instrumentation/commons-httpclient-2/src/test/groovy/IastCommonsHttpClientInstrumentationTest.groovy index 3624973f60e..0a3f5ba5bc7 100644 --- a/dd-java-agent/instrumentation/commons-httpclient-2/src/test/groovy/IastCommonsHttpClientInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/commons-httpclient-2/src/test/groovy/IastCommonsHttpClientInstrumentationTest.groovy @@ -51,7 +51,7 @@ class IastCommonsHttpClientInstrumentationTest extends AgentTestRunner { private void mockPropagation() { final propagation = Mock(PropagationModule) { - taintIfInputIsTainted(_, _) >> { + taintIfTainted(_, _) >> { if (tainteds.containsKey(it[1])) { tainteds.put(it[0], null) } diff --git a/dd-java-agent/instrumentation/commons-lang-2/src/main/java/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSite.java b/dd-java-agent/instrumentation/commons-lang-2/src/main/java/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSite.java index 63e71ac0325..d7fdd84417d 100644 --- a/dd-java-agent/instrumentation/commons-lang-2/src/main/java/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSite.java +++ b/dd-java-agent/instrumentation/commons-lang-2/src/main/java/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSite.java @@ -6,7 +6,7 @@ import datadog.trace.api.iast.Propagation; import datadog.trace.api.iast.VulnerabilityMarks; import datadog.trace.api.iast.propagation.PropagationModule; -import javax.annotation.Nonnull; +import javax.annotation.Nullable; @Propagation @CallSite(spi = IastCallSites.class) @@ -21,11 +21,11 @@ public class StringEscapeUtilsCallSite { @CallSite.After( "java.lang.String org.apache.commons.lang.StringEscapeUtils.escapeXml(java.lang.String)") public static String afterEscape( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTaintedWithMarks(result, input, VulnerabilityMarks.XSS_MARK); + module.taintIfTainted(result, input, false, VulnerabilityMarks.XSS_MARK); } catch (final Throwable e) { module.onUnexpectedException("afterEscape threw", e); } @@ -36,11 +36,11 @@ public static String afterEscape( @CallSite.After( "java.lang.String org.apache.commons.lang.StringEscapeUtils.escapeSql(java.lang.String)") public static String afterEscapeSQL( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTaintedWithMarks(result, input, VulnerabilityMarks.SQL_INJECTION_MARK); + module.taintIfTainted(result, input, false, VulnerabilityMarks.SQL_INJECTION_MARK); } catch (final Throwable e) { module.onUnexpectedException("afterEscapeSQL threw", e); } diff --git a/dd-java-agent/instrumentation/commons-lang-2/src/test/groovy/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSiteTest.groovy b/dd-java-agent/instrumentation/commons-lang-2/src/test/groovy/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSiteTest.groovy index 7a4d9457619..348aaa57503 100644 --- a/dd-java-agent/instrumentation/commons-lang-2/src/test/groovy/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/commons-lang-2/src/test/groovy/datadog/trace/instrumentation/commonslang/StringEscapeUtilsCallSiteTest.groovy @@ -27,7 +27,7 @@ class StringEscapeUtilsCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTaintedWithMarks(_ as String, args[0], mark) + 1 * module.taintIfTainted(_ as String, args[0], false, mark) 0 * _ where: diff --git a/dd-java-agent/instrumentation/commons-lang-3/src/main/java/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSite.java b/dd-java-agent/instrumentation/commons-lang-3/src/main/java/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSite.java index e151128f760..b52f57c57e9 100644 --- a/dd-java-agent/instrumentation/commons-lang-3/src/main/java/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSite.java +++ b/dd-java-agent/instrumentation/commons-lang-3/src/main/java/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSite.java @@ -6,7 +6,7 @@ import datadog.trace.api.iast.Propagation; import datadog.trace.api.iast.VulnerabilityMarks; import datadog.trace.api.iast.propagation.PropagationModule; -import javax.annotation.Nonnull; +import javax.annotation.Nullable; @Propagation @CallSite(spi = IastCallSites.class) @@ -23,11 +23,11 @@ public class StringEscapeUtilsCallSite { @CallSite.After( "java.lang.String org.apache.commons.lang3.StringEscapeUtils.escapeEcmaScript(java.lang.String)") public static String afterEscape( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTaintedWithMarks(result, input, VulnerabilityMarks.XSS_MARK); + module.taintIfTainted(result, input, false, VulnerabilityMarks.XSS_MARK); } catch (final Throwable e) { module.onUnexpectedException("afterEscape threw", e); } @@ -38,11 +38,11 @@ public static String afterEscape( @CallSite.After( "java.lang.String org.apache.commons.lang3.StringEscapeUtils.escapeJson(java.lang.String)") public static String afterEscapeJson( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTainted(result, input); + module.taintIfTainted(result, input); } catch (final Throwable e) { module.onUnexpectedException("afterEscapeJson threw", e); } diff --git a/dd-java-agent/instrumentation/commons-lang-3/src/test/groovy/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSiteTest.groovy b/dd-java-agent/instrumentation/commons-lang-3/src/test/groovy/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSiteTest.groovy index dc7147fe6e4..18730b70612 100644 --- a/dd-java-agent/instrumentation/commons-lang-3/src/test/groovy/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/commons-lang-3/src/test/groovy/datadog/trace/instrumentation/commonslang3/StringEscapeUtilsCallSiteTest.groovy @@ -25,7 +25,7 @@ class StringEscapeUtilsCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTaintedWithMarks(_ as String, args[0], VulnerabilityMarks.XSS_MARK) + 1 * module.taintIfTainted(_ as String, args[0], false, VulnerabilityMarks.XSS_MARK) 0 * _ where: @@ -47,7 +47,7 @@ class StringEscapeUtilsCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTainted(_ as String, args[0]) + 1 * module.taintIfTainted(_ as String, args[0]) 0 * _ where: diff --git a/dd-java-agent/instrumentation/commons-text/src/main/java/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSite.java b/dd-java-agent/instrumentation/commons-text/src/main/java/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSite.java index 68620efd078..ebd592fa241 100644 --- a/dd-java-agent/instrumentation/commons-text/src/main/java/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSite.java +++ b/dd-java-agent/instrumentation/commons-text/src/main/java/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSite.java @@ -6,7 +6,7 @@ import datadog.trace.api.iast.Propagation; import datadog.trace.api.iast.VulnerabilityMarks; import datadog.trace.api.iast.propagation.PropagationModule; -import javax.annotation.Nonnull; +import javax.annotation.Nullable; @Propagation @CallSite(spi = IastCallSites.class) @@ -25,11 +25,11 @@ public class StringEscapeUtilsCallSite { @CallSite.After( "java.lang.String org.apache.commons.text.StringEscapeUtils.escapeXml11(java.lang.String)") public static String afterEscape( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTaintedWithMarks(result, input, VulnerabilityMarks.XSS_MARK); + module.taintIfTainted(result, input, false, VulnerabilityMarks.XSS_MARK); } catch (final Throwable e) { module.onUnexpectedException("afterEscape threw", e); } @@ -40,11 +40,11 @@ public static String afterEscape( @CallSite.After( "java.lang.String org.apache.commons.text.StringEscapeUtils.escapeJson(java.lang.String)") public static String afterEscapeJson( - @CallSite.Argument(0) @Nonnull final String input, @CallSite.Return final String result) { + @CallSite.Argument(0) @Nullable final String input, @CallSite.Return final String result) { final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTainted(result, input); + module.taintIfTainted(result, input); } catch (final Throwable e) { module.onUnexpectedException("afterEscapeJson threw", e); } diff --git a/dd-java-agent/instrumentation/commons-text/src/test/groovy/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSiteTest.groovy b/dd-java-agent/instrumentation/commons-text/src/test/groovy/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSiteTest.groovy index 12296ad58c2..440e31ea3be 100644 --- a/dd-java-agent/instrumentation/commons-text/src/test/groovy/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/commons-text/src/test/groovy/datadog/trace/instrumentation/commonstext/StringEscapeUtilsCallSiteTest.groovy @@ -25,7 +25,7 @@ class StringEscapeUtilsCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTaintedWithMarks(_ as String, args[0], VulnerabilityMarks.XSS_MARK) + 1 * module.taintIfTainted(_ as String, args[0], false, VulnerabilityMarks.XSS_MARK) 0 * _ where: @@ -48,7 +48,7 @@ class StringEscapeUtilsCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTainted(_ as String, args[0]) + 1 * module.taintIfTainted(_ as String, args[0]) 0 * _ where: diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java index a1e62890976..c50eceebd82 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/CouchbaseClientDecorator.java @@ -2,10 +2,15 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_TYPE; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.Function; +import java.util.function.ToIntFunction; class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "couchbase"; @@ -16,6 +21,13 @@ class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { public static final CharSequence COUCHBASE_CLIENT = UTF8BytesString.create("couchbase-client"); public static final CouchbaseClientDecorator DECORATE = new CouchbaseClientDecorator(); + private static final Function NORMALIZE = SQLNormalizer::normalize; + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // characters + + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + @Override protected String[] instrumentationNames() { return new String[] {"couchbase"}; @@ -55,4 +67,8 @@ protected String dbInstance(final Object o) { protected String dbHostname(Object o) { return null; } + + protected static UTF8BytesString normalizedQuery(String sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, NORMALIZE); + } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java index bf36d0b4ee5..0e4af4d1373 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/main/java/datadog/trace/instrumentation/couchbase_31/client/DatadogRequestSpan.java @@ -64,7 +64,11 @@ public void setAttribute(String key, String value) { // TODO when `db.statement` is set here it will be intercepted by the TagInterceptor, so any // sort of obfuscation should go in there, preferably as a lazy sort of Utf8String that does // the actual work at the end - span.setTag(key, value); + if ("db.statement".equals(key)) { + span.setTag(key, CouchbaseClientDecorator.normalizedQuery(value)); + } else { + span.setTag(key, value); + } } // This method shows up in later versions diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy index afdc418332a..20ce3f32219 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.1/src/test/groovy/CouchbaseClient31Test.groovy @@ -139,7 +139,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], 'select * from `test-bucket` limit 1') + ], 'select * from `test-bucket` limit ?') assertCouchbaseDispatchCall(it, span(0)) } } @@ -148,6 +148,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check query spans with parent"() { setup: def query = 'select * from `test-bucket` limit 1' + def normalizedQuery = 'select * from `test-bucket` limit ?' when: runUnderTrace('query.parent') { @@ -163,7 +164,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseDispatchCall(it, span(1)) } } @@ -171,6 +172,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check query spans with parent and adhoc #adhoc"() { def query = 'select count(1) from `test-bucket` where (`something` = "else") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "else") limit ?' int count = 0 when: @@ -192,12 +194,12 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) if (!adhoc) { assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true) + ], "PREPARE $normalizedQuery", span(1), true) } assertCouchbaseDispatchCall(it, span(adhoc ? 1 : 2)) } @@ -209,6 +211,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check multiple query spans with parent and adhoc false"() { def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limit ?' int count1 = 0 int count2 = 0 @@ -237,20 +240,20 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true) + ], "PREPARE $normalizedQuery", span(1), true) assertCouchbaseDispatchCall(it, span(2)) assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false) + ], normalizedQuery, span(0), false) assertCouchbaseCall(it, "execute", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(4), true) + ], normalizedQuery, span(4), true) assertCouchbaseDispatchCall(it, span(5)) } } @@ -259,6 +262,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check error query spans with parent"() { setup: def query = 'select * from `test-bucket` limeit 1' + def normalizedQuery = 'select * from `test-bucket` limeit ?' Throwable ex = null when: @@ -280,7 +284,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex) + ], normalizedQuery, span(0), false, ex) assertCouchbaseDispatchCall(it, span(1)) } } @@ -288,6 +292,7 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { def "check multiple error query spans with parent and adhoc false"() { def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limeit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limeit ?' int count1 = 0 int count2 = 0 Throwable ex1 = null @@ -328,20 +333,20 @@ abstract class CouchbaseClient31Test extends VersionedNamingTestBase { assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex1) + ], normalizedQuery, span(0), false, ex1) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(1), true, ex1) + ], "PREPARE $normalizedQuery", span(1), true, ex1) assertCouchbaseDispatchCall(it, span(2)) assertCouchbaseCall(it, "cb.query", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], query, span(0), false, ex2) + ], normalizedQuery, span(0), false, ex2) assertCouchbaseCall(it, "prepare", [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', - ], "PREPARE $query", span(4), true, ex2) + ], "PREPARE $normalizedQuery", span(4), true, ex2) assertCouchbaseDispatchCall(it, span(5)) } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java index 2b84ad23c07..cad336cc2de 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/CouchbaseClientDecorator.java @@ -2,10 +2,15 @@ import static datadog.trace.bootstrap.instrumentation.api.Tags.DB_TYPE; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.Function; +import java.util.function.ToIntFunction; class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "couchbase"; @@ -16,6 +21,13 @@ class CouchbaseClientDecorator extends DBTypeProcessingDatabaseClientDecorator { public static final CharSequence COUCHBASE_CLIENT = UTF8BytesString.create("couchbase-client"); public static final CouchbaseClientDecorator DECORATE = new CouchbaseClientDecorator(); + private static final Function NORMALIZE = SQLNormalizer::normalize; + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // characters + + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + @Override protected String[] instrumentationNames() { return new String[] {"couchbase"}; @@ -55,4 +67,8 @@ protected String dbInstance(final Object o) { protected String dbHostname(Object o) { return null; } + + protected static UTF8BytesString normalizedQuery(String sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, NORMALIZE); + } } diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java index ac3b0e20b40..11d452bf45d 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/main/java/datadog/trace/instrumentation/couchbase_32/client/DatadogRequestSpan.java @@ -77,7 +77,11 @@ public void attribute(String key, String value) { // TODO when `db.statement` is set here it will be intercepted by the TagInterceptor, so any // sort of obfuscation should go in there, preferably as a lazy sort of Utf8String that does // the actual work at the end - span.setTag(key, value); + if ("db.statement".equals(key)) { + span.setTag(key, CouchbaseClientDecorator.normalizedQuery(value)); + } else { + span.setTag(key, value); + } } @Override diff --git a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy index 7fb5feba1fd..d692cbe2077 100644 --- a/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy +++ b/dd-java-agent/instrumentation/couchbase/couchbase-3.2/src/test/groovy/CouchbaseClient32Test.groovy @@ -149,7 +149,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { assertTraces(1) { sortSpansByStart() trace(2) { - assertCouchbaseCall(it, 'select * from `test-bucket` limit 1', [ + assertCouchbaseCall(it, 'select * from `test-bucket` limit ?', [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ]) @@ -161,7 +161,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check query spans with parent"() { setup: def query = 'select * from `test-bucket` limit 1' - + def normalizedQuery = 'select * from `test-bucket` limit ?' when: runUnderTrace('query.parent') { cluster.query(query) @@ -172,7 +172,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(3) { basicSpan(it, 'query.parent') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) @@ -184,6 +184,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check async query spans with parent and adhoc #adhoc"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "else") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "else") limit ?' int count = 0 when: @@ -202,12 +203,12 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(adhoc ? 3 : 4) { basicSpan(it, 'async.parent') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) if (!adhoc) { - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true) @@ -223,6 +224,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check multiple async query spans with parent and adhoc false"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limit ?' int count1 = 0 int count2 = 0 def extraPrepare = isLatestDepTest @@ -249,26 +251,26 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(extraPrepare ? 8 : 7) { basicSpan(it, 'async.multiple') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true) assertCouchbaseDispatchCall(it, span(2)) - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0)) if (extraPrepare) { - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true) } - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true) @@ -280,12 +282,13 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check error query spans with parent"() { setup: def query = 'select * from `test-bucket` limeit 1' + def normalizedQuery = "select * from `test-bucket` limeit ?" Throwable ex = null when: runUnderTrace('query.failure') { try { - cluster.query('select * from `test-bucket` limeit 1') + cluster.query(query) } catch (ParsingFailureException expected) { ex = expected } @@ -297,7 +300,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(3) { basicSpan(it, 'query.failure') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query', 'db.system' : 'couchbase', @@ -310,6 +313,7 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { def "check multiple async error query spans with parent and adhoc false"() { setup: def query = 'select count(1) from `test-bucket` where (`something` = "wonderful") limeit 1' + def normalizedQuery = 'select count(?) from `test-bucket` where (`something` = "wonderful") limeit ?' int count1 = 0 int count2 = 0 Throwable ex1 = null @@ -347,20 +351,20 @@ abstract class CouchbaseClient32Test extends VersionedNamingTestBase { sortSpansByStart() trace(7) { basicSpan(it, 'async.failure') - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0), false, ex1) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(1), true, ex1) assertCouchbaseDispatchCall(it, span(2)) - assertCouchbaseCall(it, query, [ + assertCouchbaseCall(it, normalizedQuery, [ 'db.couchbase.retries' : { Long }, 'db.couchbase.service' : 'query' ], span(0), false, ex2) - assertCouchbaseCall(it, "PREPARE $query", [ + assertCouchbaseCall(it, "PREPARE $normalizedQuery", [ 'db.couchbase.retries': { Long }, 'db.couchbase.service': 'query' ], span(4), true, ex2) diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy index 8775d27baec..946e1b762a5 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-3.8/src/test/groovy/CassandraClientTest.groovy @@ -134,11 +134,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "async_test" | true } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable exception = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java b/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java index 28d5b32eddf..ffe7c81fb25 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java +++ b/dd-java-agent/instrumentation/datastax-cassandra-3/src/main/java/datadog/trace/instrumentation/datastax/cassandra/CassandraClientDecorator.java @@ -3,11 +3,15 @@ import com.datastax.driver.core.Host; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Session; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.bootstrap.instrumentation.decorator.DBTypeProcessingDatabaseClientDecorator; +import java.util.function.ToIntFunction; public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "cassandra"; @@ -19,6 +23,15 @@ public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDeco public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator(); + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // chars + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + + protected static UTF8BytesString normalizedQuery(CharSequence sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, SQLNormalizer::normalizeCharSequence); + } + @Override protected String[] instrumentationNames() { return new String[] {"cassandra"}; @@ -60,6 +73,11 @@ protected String dbHostname(Session session) { return null; } + public AgentSpan onStatement(final AgentSpan span, final CharSequence statement) { + span.setResourceName(normalizedQuery(statement)); + return span; + } + public AgentSpan onResponse(final AgentSpan span, final ResultSet result) { if (result != null) { final Host host = result.getExecutionInfo().getQueriedHost(); diff --git a/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy index 8775d27baec..946e1b762a5 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-3/src/test/groovy/CassandraClientTest.groovy @@ -134,11 +134,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "async_test" | true } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable exception = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java b/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java index 98817ca2b5d..dc1cb7d5b31 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java +++ b/dd-java-agent/instrumentation/datastax-cassandra-4/src/main/java/datadog/trace/instrumentation/datastax/cassandra4/CassandraClientDecorator.java @@ -5,7 +5,10 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.session.Session; +import datadog.trace.api.cache.DDCache; +import datadog.trace.api.cache.DDCaches; import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.normalize.SQLNormalizer; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; @@ -13,6 +16,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Objects; +import java.util.function.ToIntFunction; public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDecorator { private static final String DB_TYPE = "cassandra"; @@ -24,6 +28,15 @@ public class CassandraClientDecorator extends DBTypeProcessingDatabaseClientDeco public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator(); + private static final int COMBINED_STATEMENT_LIMIT = 2 * 1024 * 1024; // chars + private static final ToIntFunction STATEMENT_WEIGHER = UTF8BytesString::length; + private static final DDCache CACHED_STATEMENTS = + DDCaches.newFixedSizeWeightedCache(512, STATEMENT_WEIGHER, COMBINED_STATEMENT_LIMIT); + + protected static UTF8BytesString normalizedQuery(CharSequence sql) { + return CACHED_STATEMENTS.computeIfAbsent(sql, SQLNormalizer::normalizeCharSequence); + } + @Override protected String[] instrumentationNames() { return new String[] {"cassandra"}; @@ -64,6 +77,11 @@ protected String dbHostname(Session session) { return null; } + public AgentSpan onStatement(final AgentSpan span, final CharSequence statement) { + span.setResourceName(normalizedQuery(statement)); + return span; + } + public AgentSpan onResponse(final AgentSpan span, final ResultSet result) { if (result != null) { return onResponse(span, result.getExecutionInfo().getCoordinator()); diff --git a/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy b/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy index bdafd0f4a1f..21bb4bf3ff3 100644 --- a/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy +++ b/dd-java-agent/instrumentation/datastax-cassandra-4/src/test/groovy/CassandraClientTest.groovy @@ -210,11 +210,15 @@ abstract class CassandraClientTest extends VersionedNamingTestBase { .withConfigLoader(configLoader) } + String normalize(String statement){ + return statement.replaceAll("'alice'", "?") + } + def cassandraSpan(TraceAssert trace, String statement, String keyspace, boolean renameService, Object parentSpan = null, Throwable throwable = null) { trace.span { serviceName renameService && keyspace ? keyspace : service() operationName operation() - resourceName statement + resourceName normalize(statement) spanType DDSpanTypes.CASSANDRA if (parentSpan == null) { parent() diff --git a/dd-java-agent/instrumentation/exception-profiling/src/main/java/datadog/exceptions/instrumentation/ThrowableInstrumentation.java b/dd-java-agent/instrumentation/exception-profiling/src/main/java/datadog/exceptions/instrumentation/ThrowableInstrumentation.java index 08a9b11997c..8f37f033e23 100644 --- a/dd-java-agent/instrumentation/exception-profiling/src/main/java/datadog/exceptions/instrumentation/ThrowableInstrumentation.java +++ b/dd-java-agent/instrumentation/exception-profiling/src/main/java/datadog/exceptions/instrumentation/ThrowableInstrumentation.java @@ -1,21 +1,15 @@ package datadog.exceptions.instrumentation; -import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresField; -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.api.Platform; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; -/** Provides instrumentation of {@linkplain Throwable} constructor. */ +/** Provides instrumentation of {@linkplain Exception} and {@linkplain Error} constructors. */ @AutoService(Instrumenter.class) public final class ThrowableInstrumentation extends Instrumenter.Profiling - implements Instrumenter.ForBootstrap, - Instrumenter.ForSingleType, - Instrumenter.WithTypeStructure { + implements Instrumenter.ForBootstrap, Instrumenter.ForKnownTypes { public ThrowableInstrumentation() { super("throwables"); @@ -27,17 +21,14 @@ public boolean isEnabled() { } @Override - public String instrumentedType() { - return "java.lang.Throwable"; - } - - @Override - public ElementMatcher structureMatcher() { - return declaresField(named("stackTrace")); + public void adviceTransformations(AdviceTransformation transformation) { + transformation.applyAdvice(isConstructor(), packageName + ".ThrowableInstanceAdvice"); } @Override - public void adviceTransformations(AdviceTransformation transformation) { - transformation.applyAdvice(isConstructor(), packageName + ".ThrowableInstanceAdvice"); + public String[] knownMatchingTypes() { + return new String[] { + "java.lang.Exception", "java.lang.Error", "kotlin.Exception", "kotlin.Error" + }; } } diff --git a/dd-java-agent/instrumentation/exception-profiling/src/main/java11/datadog/exceptions/instrumentation/ThrowableInstanceAdvice.java b/dd-java-agent/instrumentation/exception-profiling/src/main/java11/datadog/exceptions/instrumentation/ThrowableInstanceAdvice.java index 32072e031ed..3c2623f5a48 100644 --- a/dd-java-agent/instrumentation/exception-profiling/src/main/java11/datadog/exceptions/instrumentation/ThrowableInstanceAdvice.java +++ b/dd-java-agent/instrumentation/exception-profiling/src/main/java11/datadog/exceptions/instrumentation/ThrowableInstanceAdvice.java @@ -11,12 +11,7 @@ public class ThrowableInstanceAdvice { @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.This final Throwable t, - @Advice.FieldValue("stackTrace") StackTraceElement[] stackTrace) { - if (t.getClass().getName().endsWith(".ResourceLeakDetector$TraceRecord")) { - return; - } + public static void onExit(@Advice.This final Object t) { /* * This instrumentation handler is sensitive to any throwables thrown from its body - * it will go into infinite loop of trying to handle the new throwable instance and generating @@ -31,24 +26,23 @@ public static void onExit( } try { /* - * Exclude internal agent threads from exception profiling. + * We may get into a situation when this is called before exception sampling is active. */ - if (Config.get().isProfilingExcludeAgentThreads() - && AGENT_THREAD_GROUP.equals(Thread.currentThread().getThreadGroup())) { + if (!InstrumentationBasedProfiling.isJFRReady()) { return; } /* - * We may get into a situation when this is called before exception sampling is active. + * Exclude internal agent threads from exception profiling. */ - if (!InstrumentationBasedProfiling.isJFRReady()) { + if (Config.get().isProfilingExcludeAgentThreads() + && AGENT_THREAD_GROUP.equals(Thread.currentThread().getThreadGroup())) { return; } /* * JFR will assign the stacktrace depending on the place where the event is committed. * Therefore we need to commit the event here, right in the 'Exception' constructor */ - final ExceptionSampleEvent event = - ExceptionProfiling.getInstance().process(t, stackTrace == null ? 0 : stackTrace.length); + final ExceptionSampleEvent event = ExceptionProfiling.getInstance().process((Throwable) t); if (event != null && event.shouldCommit()) { event.commit(); } diff --git a/dd-java-agent/instrumentation/freemarker/src/main/java/datadog/trace/instrumentation/freemarker/StringUtilCallSite.java b/dd-java-agent/instrumentation/freemarker/src/main/java/datadog/trace/instrumentation/freemarker/StringUtilCallSite.java index 8ee9bc4ea2a..391339f528b 100644 --- a/dd-java-agent/instrumentation/freemarker/src/main/java/datadog/trace/instrumentation/freemarker/StringUtilCallSite.java +++ b/dd-java-agent/instrumentation/freemarker/src/main/java/datadog/trace/instrumentation/freemarker/StringUtilCallSite.java @@ -29,7 +29,7 @@ public static String afterEscape( final PropagationModule module = InstrumentationBridge.PROPAGATION; if (module != null) { try { - module.taintIfInputIsTaintedWithMarks(result, input, VulnerabilityMarks.XSS_MARK); + module.taintIfTainted(result, input, false, VulnerabilityMarks.XSS_MARK); } catch (final Throwable e) { module.onUnexpectedException("afterEscape threw", e); } diff --git a/dd-java-agent/instrumentation/freemarker/src/test/groovy/datadog/trace/instrumentation/freemarker/StringUtilCallSiteTest.groovy b/dd-java-agent/instrumentation/freemarker/src/test/groovy/datadog/trace/instrumentation/freemarker/StringUtilCallSiteTest.groovy index 51b0512a5e3..18d86d6aef5 100644 --- a/dd-java-agent/instrumentation/freemarker/src/test/groovy/datadog/trace/instrumentation/freemarker/StringUtilCallSiteTest.groovy +++ b/dd-java-agent/instrumentation/freemarker/src/test/groovy/datadog/trace/instrumentation/freemarker/StringUtilCallSiteTest.groovy @@ -23,17 +23,17 @@ class StringUtilCallSiteTest extends AgentTestRunner { then: result == expected - 1 * module.taintIfInputIsTaintedWithMarks(_ as String, args[0], VulnerabilityMarks.XSS_MARK) + 1 * module.taintIfTainted(_ as String, args[0], false, VulnerabilityMarks.XSS_MARK) 0 * _ where: - method | args | expected - 'HTMLEnc' | ['"escape this < '] | '<htmlTag>"escape this < </htmlTag>' - 'XMLEnc' | ['"escape this < '] | '<xmlTag>"escape this < </xmlTag>' - 'XHTMLEnc' | ['"escape this < '] | '<htmlTag>"escape this < </htmlTag>' - 'javaStringEnc' | ['