Skip to content

Commit 2a428fa

Browse files
authored
Fix classLoaderMatcher review instructions (#16489)
1 parent f4ccd57 commit 2a428fa

3 files changed

Lines changed: 51 additions & 15 deletions

File tree

.github/agents/code-review-and-fix.agent.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,18 @@ Auto-fix boundaries:
112112
to a library family with sibling version modules, it must list all siblings via
113113
`testInstrumentation`. Check `settings.gradle.kts` for sibling `:javaagent` modules
114114
under the same parent. After adding, verify by running the module's tests.
115-
- missing version comments on `hasClassesNamed()` landmark classes in
116-
`classLoaderMatcher()` — look up the library version that introduced each class (check
117-
muzzle `versions.set(...)` ranges, module directory name, existing code comments, and
118-
Javadoc/release notes) and add a `// added in X.Y` or `// removed in X.Y` comment above
119-
each class name string
115+
- missing version comments on `hasClassesNamed()` landmark classes in existing
116+
`classLoaderMatcher()` overrides (multi-class checks or `.and(not(...))` chains only) —
117+
look up the library version that introduced each class (check muzzle `versions.set(...)`
118+
ranges, module directory name, existing code comments, and Javadoc/release notes) and
119+
add a `// added in X.Y` or `// removed in X.Y` comment above each class name string.
120+
Do NOT add a `classLoaderMatcher()` override where one does not already exist —
121+
this method is only for version-boundary detection when muzzle is insufficient,
122+
not for optimization (use `TypeInstrumentation.classLoaderOptimization()` instead)
123+
- redundant `isMethod()` in method matchers inside `transform()` when the code is
124+
already being modified — `isMethod()` only serves to exclude constructors, but
125+
`named(...)` already excludes them because constructors are named `<init>`
126+
(e.g., `isMethod().and(named("execute"))``named("execute")`)
120127
- singleton-to-instance-creation conversion for stateless telemetry interface
121128
implementations (`TextMapGetter`, `TextMapSetter`, `*AttributesGetter`,
122129
`AttributesExtractor`, `SpanNameExtractor`, `HttpServerResponseMutator`) — replace

.github/agents/knowledge/general-rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ When a "Knowledge File" is listed, load it from `knowledge/` before reviewing th
1919
| Naming | Module/package naming | New or renamed modules/packages | `module-naming.md` |
2020
| Javaagent | Advice patterns | `@Advice` classes | `javaagent-advice-patterns.md` |
2121
| Javaagent | Module structure patterns | `InstrumentationModule`, `TypeInstrumentation`, `Singletons` | `javaagent-module-patterns.md` |
22-
| Javaagent | Missing `classLoaderMatcher()` | `InstrumentationModule` without `classLoaderMatcher()` override | `javaagent-module-patterns.md` |
22+
| Javaagent | Incorrect `classLoaderMatcher()` | `classLoaderMatcher()` override that is redundant (muzzle already handles it) or missing when needed (muzzle cannot distinguish version range) | `javaagent-module-patterns.md` |
2323
| Semconv | Library vs javaagent semconv constant usage | Semconv constants/assertions ||
2424
| Semconv | Dual semconv testing | `SemconvStability`, `maybeStable`, semconv Gradle tasks | `testing-semconv-stability.md` |
2525
| Testing | General test patterns | Test files in scope | `testing-general-patterns.md` |

.github/agents/knowledge/javaagent-module-patterns.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,14 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
140140

141141
#### Rules for `classLoaderMatcher()`
142142

143-
- **Every `InstrumentationModule` should override `classLoaderMatcher()`** with a
144-
`hasClassesNamed(...)` check for a class from the module's target library. This allows
145-
the agent to skip the entire module early — before evaluating type matchers or muzzle
146-
when the target library is absent.
147-
**Exceptions** — do not flag: modules under `instrumentation/internal/` (JDK/agent internals),
148-
modules instrumenting JDK classes on all supported Java versions (executors,
149-
http-url-connection, java-util-logging, rmi, jdbc), config-driven modules with no fixed
150-
target library (`methods`, `external-annotations`), and test-only modules.
143+
- **Do NOT add `classLoaderMatcher()` for optimization.** The "skip when library is absent"
144+
optimization belongs on `TypeInstrumentation.classLoaderOptimization()`, not here.
145+
`classLoaderMatcher()` is only for **version-boundary detection** — cases where muzzle
146+
alone cannot distinguish the target version range. Most modules do not need it.
147+
- **Do NOT flag modules that lack a `classLoaderMatcher()` override.** The default (`any()`)
148+
is correct when muzzle can detect version boundaries on its own (the common case). Only
149+
flag a *missing* override when the module targets a specific version range and the
150+
versioning signal (added/removed landmark class) is not part of the classes muzzle inspects.
151151
- **Version comments on landmark classes.** For multi-class checks, `.and(not(...))`
152152
chains, or cases where the landmark version differs from the module's base version,
153153
each `hasClassesNamed()` call must have a `//` comment stating the library version
@@ -185,7 +185,7 @@ public class MyClassInstrumentation implements TypeInstrumentation {
185185
@Override
186186
public void transform(TypeTransformer transformer) {
187187
transformer.applyAdviceToMethod(
188-
isMethod().and(named("execute")).and(takesArguments(1)),
188+
named("execute").and(takesArguments(1)),
189189
getClass().getName() + "$ExecuteAdvice");
190190
}
191191

@@ -196,6 +196,32 @@ public class MyClassInstrumentation implements TypeInstrumentation {
196196
}
197197
```
198198

199+
### `classLoaderOptimization()` — Skip When Library Absent
200+
201+
Override `classLoaderOptimization()` when the `typeMatcher()` uses expensive matchers
202+
(e.g., `implementsInterface(...)`, `extendsClass(...)`, `isAnnotatedWith(...)`). This is
203+
a fast pre-filter that runs before type matching: if a class from the target library is
204+
absent, the expensive type matcher is skipped entirely.
205+
206+
```java
207+
@Override
208+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
209+
return hasClassesNamed("com.example.library.TargetClass");
210+
}
211+
```
212+
213+
**This is the correct place for "skip when library is absent" optimization** — not
214+
`InstrumentationModule.classLoaderMatcher()`. The module-level `classLoaderMatcher()` is
215+
ANDed with `classLoaderOptimization()` at runtime, so the type-level check alone is
216+
sufficient for optimization.
217+
218+
**When to override:**
219+
220+
- The `typeMatcher()` uses hierarchy matchers (`implementsInterface`, `extendsClass`) or
221+
annotation matchers — these require bytecode inspection of super classes/interfaces.
222+
- The `typeMatcher()` uses `named(...)` or `namedOneOf(...)` — no override needed because
223+
name-only matchers are already fast (they check only the class name, no bytecode).
224+
199225
### Rules
200226

201227
- Do not flag or change the visibility or `final` modifier on `TypeInstrumentation`,
@@ -206,6 +232,9 @@ public class MyClassInstrumentation implements TypeInstrumentation {
206232
`extendsClass(...)` and `implementsInterface(...)` are appropriate when the instrumentation
207233
targets subclasses or implementors of a type.
208234
- `transform()` wires method matchers to advice classes via `applyAdviceToMethod()`.
235+
- Do not add `isMethod()` in method matchers inside `transform()`. `isMethod()` only
236+
serves to exclude constructors, but `named(...)` already excludes them because
237+
constructors are named `<init>`. Remove `isMethod()` when not needed.
209238
- Reference the advice class using `getClass().getName() + "$InnerClassName"` — not
210239
`InnerClassName.class.getName()`, `OuterClass.class.getName()`, or a string literal.
211240
Any `.class.getName()` reference — whether to the inner advice class or the outer

0 commit comments

Comments
 (0)