Repository navigation
Conversation
In order, to anticipate the static analysis skill, let's migrate the spotless and forbidden api script plugins to convention plugins.
2dc368f to
15b256e
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
More details
The migrated conventions retain the existing task filters, detector exclusions, and module overrides. Gradle compilation and task execution remain unverified.
🤖 Bits Code Review · Commit 15b256e
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
dougqh
left a comment
There was a problem hiding this comment.
Looks behavior-preserving to me (ignoreFailures, omitted visitors, HTML report, smoke-test skip, Main/Main_java11 filter, otel-bootstrap override all carry over). A few comments inline — the Java 8 worker one is the only one I'd want settled before merge.
One more that I can't anchor inline since the file isn't in the diff: dd-trace-java.module.instrumentation.gradle.kts still applies de.thetaphi.forbiddenapis itself and re-declares the main.txt signatures path, even though the new forbidden-apis convention already arrives via java_no_deps. Could it rely on the convention and only add instrumentation.txt, so the main.txt config lives in one place?
| dependencies { | ||
| implementation(gradleApi()) | ||
| implementation(libs.forbiddenapis) | ||
| implementation(libs.spotbugs) { |
There was a problem hiding this comment.
Is the daemon really the only JVM this reaches? MuzzleTask.kt (~L147) notes that processIsolation leaks Gradle/buildSrc dependencies into the child process, and InstrumentPostProcessingAction.kt uses processIsolation too. If one of those workers runs on a Java 8 launcher, anything that scans the classpath / hits a ServiceLoader and touches the SpotBugs plugin (or its Java 11+ deps) would throw UnsupportedClassVersionError. Probably fine in practice, but a green muzzle run on a Java 8 launcher would confirm it.
There was a problem hiding this comment.
You're right that process isolation inherits the work implementation's classpath. I removed that justification from the comment locally. Worker isolation is addressed separately in #12922. The existing MuzzleAction already calls the Java 9+ getPlatformClassLoader() before this change, so a green Java 8 Muzzle run remains outstanding.
| implementation(libs.forbiddenapis) | ||
| implementation(libs.spotbugs) { | ||
| // buildSrc targets Java 8 for muzzle workers, but the SpotBugs plugin requires Java 11. | ||
| // This is fine because the spotbugs plugin runs in the Gradle Daemon JVM (Java 25). |
There was a problem hiding this comment.
nit: "Java 25" duplicates gradle/gradle-daemon-jvm.properties and will go stale on the next daemon bump. The actual requirement is just "daemon JVM ≥ 11".
There was a problem hiding this comment.
Removed the daemon version in the comment. The goal of the comment is explain only why we select the plugin's Java 11 variant while buildSrc still targets Java 8.
| ignoreFailures = false | ||
| excludeFilter = rootProject.layout.projectDirectory.file("gradle/spotbugFilters/exclude.xml") | ||
| showProgress = !providers.environmentVariable("CI").isPresent | ||
| omitVisitors = listOf( |
There was a problem hiding this comment.
Subtle change: previously omitVisitors was set per-task, so modules couldn't override it. As an extension convention, a module writing spotbugs { omitVisitors = [...] } to add one detector would replace the shared list and silently re-enable things like FindNullDeref / MutableStaticFields. Worth either keeping it on the task, or adding a comment that overrides should append (omitVisitors.addAll(...)).
There was a problem hiding this comment.
Indeed, I initially did that as an optimization, but having per source set configuration makes sense. I've restored the omitVisitors list on production tasks locally.
Also, currently no module overrides it.
Test analysis is currently disabled and inherits none of these omissions, I've kept it that way in this PR. But the code is now in a bit better place so the static analysis skill can later tune this for test tasks.
|
|
||
| spotbugs { | ||
| ignoreFailures = false | ||
| excludeFilter = rootProject.layout.projectDirectory.file("gradle/spotbugFilters/exclude.xml") |
There was a problem hiding this comment.
nit: the forbidden-apis convention uses rootDir.resolve(...) for its root-relative file; this one goes through rootProject.layout. Would be nice to pick one — and avoiding rootProject from subprojects is friendlier to isolated projects.
There was a problem hiding this comment.
Changed to rootDir.resolve(...) locally. I also checked this with a small isolated-projects build: rootProject.layout fails with a cross-project access violation, while rootDir.resolve(...) passes.
| } | ||
|
|
||
| tasks.withType<SpotBugsTask>().configureEach { | ||
| if (project.path.startsWith(":dd-smoke-tests") || |
There was a problem hiding this comment.
nit: the :dd-smoke-tests check only depends on the project, but it's evaluated per SpotBugs task, and smoke-test modules still get the plugin/tasks configured just to disable them. Checking once at plugin-apply time (or not applying it under dd-smoke-tests) would be cheaper.
There was a problem hiding this comment.
Could we keep the plugin applied for now? Removing it would change the available tasks and configurations. The when checks smoke-test projects first and disables their analysis; configureEach limits the prefix check to realised tasks. We haven't measured a meaningful cost from that check.
| } | ||
|
|
||
| dependencies { | ||
| compileOnly(versionCatalogs.named("libs").findLibrary("forbiddenapis").get()) |
There was a problem hiding this comment.
nit: the spotbugs convention uses a val libs = versionCatalogs.named("libs") holder; this inlines it. Same pattern in both would keep the sibling conventions uniform.
There was a problem hiding this comment.
I kept the local variable where the catalogue is used twice, and the inline lookup where it's used once. I'd prefer to leave these as they are unless we want a project-wide convention.
| gradle-tooling-api = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle-tooling-api" } | ||
| jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } | ||
| spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs_annotations" } | ||
| spotbugs = { module = "com.github.spotbugs.snom:spotbugs-gradle-plugin", version.ref = "spotbugs-gradle-plugin" } |
There was a problem hiding this comment.
nit: spotbugs here is the Gradle plugin, while the neighbouring spotbugs-annotations / spotbugs_annotations refer to the SpotBugs tool. Someone bumping libs.spotbugs expecting the analyzer would change the plugin. spotbugs-gradle-plugin as the alias would match the version key.
There was a problem hiding this comment.
That's a fair point. I originally did that, but other plugins were not identified as such, so I kept the existing naming convention. I'll use the suggested name, but I will update the other names in a later PR.
Enabling SpotBugs on main_java17 exposed two rules: - AT_STALE_THREAD_WRITE_OF_PRIMITIVE: the send and receive buffer sizes are shared without synchronization. Make them volatile so readers observe updates to these independent settings. - NCR_NOT_PROPERLY_CHECKED_READ: read() assumes every result except -1 filled its byte. A zero-byte read on timeout fabricated a NUL byte. Wait for data, EOF or an exception. Throw SocketTimeoutException when the read timeout expires. Retry selector wakeups within the original timeout and stop interrupted reads so the retry loop cannot spin. Inspecting the delegated bulk read also exposed byte loss: it could consume more bytes from the channel than requested and discard the rest. Limit the channel read to the requested length and handle empty reads. Suppressing the rules would retain real visibility and stream-contract bugs. Add coverage for byte preservation, EOF, empty reads, timeout, selector wakeup and interruption while preserving asynchronous close.
Enabling SpotBugs on main_java21 exposed PA_PUBLIC_PRIMITIVE_ATTRIBUTE on TestEventsHandlerHolder.TEST_EVENTS_HANDLER. Only KarateTracingListener and ExecutionContext read this field. Both are helpers in the same package and are injected with the holder into the same class loader. Inlined advice does not access the field, and tests use the public start() and stop() methods. Make the field package-private. Public access is unnecessary, so reduce its visibility without a SuppressFBWarnings annotation or an additional accessor. Keep volatile publication unchanged.
There was a problem hiding this comment.
More details
The convention plugins compiled successfully. Targeted execution checks remain unverified because required dependencies were not cached for offline use.
🤖 Bits Code Review · Commit b34800f
What Does This Do
Move the shared SpotBugs and forbidden-apis configuration from Groovy scripts to Kotlin convention plugins in
buildSrc.Existing task selection, filters, detector exclusions, reports and launchers are preserved.
Motivation
Replace script application with typed, reusable conventions that declare their prerequisites and allow modules to customise the shared defaults.
The main motivation is to prepare support for #12816.
Additional Notes
buildSrcstill target Java 8 for muzzle workers.Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issue