Repository navigation
Expand file tree
/
Copy pathspotbugs-exclude.xml
More file actions
262 lines (238 loc) · 13.7 KB
/
Copy pathspotbugs-exclude.xml
File metadata and controls
262 lines (238 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
<?xml version="1.0" encoding="UTF-8"?>
<!--
SpotBugs exclude filter for async-test-lib.
URF_UNREAD_FIELD: Detector state-holder inner classes intentionally capture
diagnostic data (timestamps, IDs, counters) for future diagnostic use or for
report construction. These fields are written during instrumentation and read
(transitively) by the report/toString methods that iterate the enclosing
collections. SpotBugs' intra-procedural analysis cannot follow the
ConcurrentHashMap.values() read path back to the individual fields.
The find-sec-bugs section at the bottom was triaged once, finding by finding,
when the plugin was added. Each exclusion says why. The narrow ones (the
deserialization sink, the report writers) are deliberately scoped to a class
or a method so that a NEW instance of the same pattern elsewhere still fails
the build — that is the whole point of running the analyser.
-->
<FindBugsFilter>
<!-- ================================================================
URF_UNREAD_FIELD — intentional instrumentation / state-holder fields
================================================================ -->
<Match>
<Bug pattern="URF_UNREAD_FIELD,URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
<Package name="se.deversity.asynctest.diagnostics"/>
</Match>
<!-- SpinContentionBarrier is the only class in the runner package this fires on, and for the
same reason as the UUF_UNUSED_FIELD entry below: the field is cache-line padding. Scoped to
the class rather than the package so an unread field in any other runner class is still
reported. -->
<Match>
<Bug pattern="URF_UNREAD_FIELD"/>
<Class name="se.deversity.asynctest.runner.SpinContentionBarrier"/>
</Match>
<!-- AsyncTestConfig's public detector flags (detectAll and one per DetectorType) are public
API that consumers read (#383). The library itself reads the enabled set instead: the
registry builds from it (#916) and the SPI bridge that read them is gone (#922), so most flags have no
reader inside this jar. -->
<Match>
<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
<Class name="se.deversity.asynctest.AsyncTestConfig"/>
<Field name="~(detect|validate|monitor)[A-Z].*"/>
</Match>
<!-- ================================================================
THROWS_METHOD_THROWS_CLAUSE_THROWABLE — intentional in runner
runSingleInvocationRound must declare throws Throwable so that
worker-thread failures (AssertionError, OutOfMemoryError, etc.)
propagate unmodified to the test thread. Narrowing to Exception
would silently swallow Errors.
================================================================ -->
<Match>
<Bug pattern="THROWS_METHOD_THROWS_CLAUSE_THROWABLE"/>
<Class name="se.deversity.asynctest.runner.ConcurrencyRunner"/>
</Match>
<!-- AsyncTestRunner is the programmatic front of the same engine: Body.run() and
AsyncTestRunner.run(...) declare Throwable for the same reason, so a body's Error or a
worker's AssertionError reaches the caller exactly as it reaches JUnit. -->
<Match>
<Bug pattern="THROWS_METHOD_THROWS_CLAUSE_THROWABLE"/>
<Class name="~se\.deversity\.asynctest\.AsyncTestRunner(\$.*)?"/>
</Match>
<!-- ================================================================
THROWS_METHOD_THROWS_RUNTIMEEXCEPTION — intentional re-throw pattern
ConcurrencyRunner.invokeLifecycleMethods re-wraps and re-throws
lifecycle failures as RuntimeException so they surface to JUnit.
AsyncAssert.awaitAsync wraps async completion failures into
RuntimeException for clean, unchecked test assertions.
================================================================ -->
<Match>
<Bug pattern="THROWS_METHOD_THROWS_RUNTIMEEXCEPTION"/>
<Class name="se.deversity.asynctest.runner.ConcurrencyRunner"/>
</Match>
<Match>
<Bug pattern="THROWS_METHOD_THROWS_RUNTIMEEXCEPTION"/>
<Class name="se.deversity.asynctest.AsyncAssert"/>
</Match>
<!-- ================================================================
SpinContentionBarrier padding fields — intentional cache-line
isolation to prevent false sharing between arrivalCount and
currentPhase. Removing them would defeat the entire purpose of
the spin barrier.
================================================================ -->
<Match>
<Bug pattern="UUF_UNUSED_FIELD"/>
<Class name="se.deversity.asynctest.runner.SpinContentionBarrier"/>
</Match>
<!-- ================================================================
TelemetryEventBuffer.AccessEvent.sequence — accessed via VarHandle
(SEQ_VH.getAcquire / setRelease). SpotBugs' bytecode analyzer
cannot see through VarHandle indirection to detect the read.
================================================================ -->
<Match>
<Bug pattern="URF_UNREAD_FIELD"/>
<Package name="se.deversity.asynctest.telemetry"/>
</Match>
<!-- ================================================================
TelemetryRegistry static-field null-outs in stop() — guarded by
the RUNNING CAS gate, which serializes start/stop transitions.
SpotBugs flags the read-then-null-assign as classic lazy-init,
but only one thread can ever pass the gate per transition.
================================================================ -->
<Match>
<Bug pattern="LI_LAZY_INIT_STATIC"/>
<Class name="se.deversity.asynctest.telemetry.TelemetryRegistry"/>
</Match>
<!-- ================================================================
find-sec-bugs triage
================================================================ -->
<!-- CRLF_INJECTION_LOGS (36 findings) — the threat model does not apply.
The values reaching these log calls are test method names, thread names,
detector names and timing numbers: all supplied by the developer running
their own suite, written to their own build log, read by them. There is no
second party to deceive by injecting a newline. The library also logs through
SLF4J's parameterised form throughout, which is the mitigation this rule
normally asks for. -->
<Match>
<Bug pattern="CRLF_INJECTION_LOGS"/>
</Match>
<!-- PREDICTABLE_RANDOM — java.util.Random here is the requirement, not a defect.
ConcurrencyRunner draws a per-round replay seed and logs it so a failing run
can be reproduced with @AsyncTest(replaySeed = N). A cryptographically strong
generator would still be predictable once the seed is printed, and swapping it
in would only make the draw slower. Scoped to the one class so a genuine
security use of Random elsewhere is still reported. -->
<Match>
<Bug pattern="PREDICTABLE_RANDOM"/>
<Class name="se.deversity.asynctest.runner.ConcurrencyRunner"/>
</Match>
<!-- OBJECT_DESERIALIZATION — a real Java native deserialization sink, already
hardened. readStore installs BASELINE_FILTER, a strict ObjectInputFilter
allow-list ending in !* so every class in the stream must be named or the
read is rejected (CWE-502). find-sec-bugs flags the readObject call without
seeing setObjectInputFilter. Scoped to the single method: any other
deserialization sink introduced anywhere in the library must fail the build
and be argued for on its own terms. See docs/QUALITY_GATES.md and the
@AIStrictClasspath note on the method. -->
<Match>
<Bug pattern="OBJECT_DESERIALIZATION"/>
<Class name="se.deversity.asynctest.benchmark.BenchmarkComparator"/>
<Method name="readStore"/>
</Match>
<!-- POTENTIAL_XML_INJECTION — two different false positives.
Nine of the eleven are detector toString() methods that build a plain-text
report with a StringBuilder. There is no XML anywhere in them; the heuristic
fires on angle brackets in prose.
The other two are JUnitXmlReportListener.writeXml, which does write XML and
does escape: attributes through xmlEscape (& < > ") and the report body
through cdataEscape, which splits the ]]> terminator across two CDATA
sections. find-sec-bugs cannot follow the escaping helper. Scoped to the
writer method rather than the class, so a new unescaped append in a different
method is still reported. -->
<Match>
<Bug pattern="POTENTIAL_XML_INJECTION"/>
<Package name="se.deversity.asynctest.diagnostics"/>
</Match>
<Match>
<Bug pattern="POTENTIAL_XML_INJECTION"/>
<Class name="se.deversity.asynctest.report.JUnitXmlReportListener"/>
<Method name="writeXml"/>
</Match>
<!-- PATH_TRAVERSAL_IN — the "user input" is the developer's own system property
(async-test.report.dir and friends) choosing where their build writes its
report. The path is supplied by the person running the build, resolved in
their own process, and used to write a file they asked for; there is no
privilege boundary to traverse. Scoped to ReportSink, the one class both report
writers resolve their output directory through (#701). -->
<Match>
<Bug pattern="PATH_TRAVERSAL_IN"/>
<Class name="se.deversity.asynctest.report.ReportSink"/>
</Match>
<!-- PATH_TRAVERSAL_IN - the same reasoning as ReportSink, for the licence validation cache.
record() creates its temp file in the directory license.cache.dir names, a system
property of the person running the build, who can equally pass
-Dlicense.mock.mode=true. There is no privilege boundary to traverse. Scoped to
record(), the one method that creates a file there. -->
<Match>
<Bug pattern="PATH_TRAVERSAL_IN"/>
<Class name="se.deversity.asynctest.runner.LicenseValidationCache"/>
<Method name="record"/>
</Match>
<!-- IMPROPER_UNICODE — the mitigation this rule asks for is already present.
AgentOptions lowercases an agent-argument key with toLowerCase(Locale.ROOT),
which is the locale-independent form; the rule fires on toLowerCase regardless
of the argument. Nor is the result a security decision: it selects between the
"includes" and "excludes" option names when parsing -javaagent arguments the
developer wrote themselves. Scoped to the one class. -->
<Match>
<Bug pattern="IMPROPER_UNICODE"/>
<Class name="se.deversity.asynctest.agent.AgentOptions"/>
</Match>
<!-- IMPROPER_UNICODE - the same reasoning for the runner's side of the agent arguments.
AgentAutoAttach.plan compares -Dasynctest.agent to "off" and "false" with
equalsIgnoreCase, and the rule fires on equalsIgnoreCase whatever it compares. The
result only decides whether the developer's own test JVM attaches the agent; no
privilege depends on it. Scoped to the one method. -->
<Match>
<Bug pattern="IMPROPER_UNICODE"/>
<Class name="se.deversity.asynctest.runner.AgentAutoAttach"/>
<Method name="plan"/>
</Match>
<!-- INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE — printing the failure is the
method's entire job. Byte Buddy calls onError when it cannot instrument a
class, and the agent's contract is to say which class and why on the
developer's own stderr rather than fail their JVM silently. There is no
remote party and no response to leak into. Scoped to the listener. -->
<Match>
<Bug pattern="INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE"/>
<Class name="se.deversity.asynctest.agent.AsyncTestAgent$DiagnosticListener"/>
</Match>
<!-- Same reasoning one layer out: this listener hears about the retransformClasses call
rather than the transformation, and naming the class the JVM refused to re-weave is
the whole point of it existing. A swallowed failure here costs a user every already
loaded class's weaving with no way to find out why. Scoped to the listener. -->
<Match>
<Bug pattern="INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE"/>
<Class name="se.deversity.asynctest.agent.AsyncTestAgent$RetransformDiagnosticListener"/>
</Match>
<!-- AgentMonitorHooks stands in for the Object.wait/notify call the agent substituted (#694),
so each hook makes exactly that call: a wait with no loop of its own, because the loop,
or its absence, belongs to the woven caller and is what MissedSignalDetector judges, and a
notify where the caller wrote notify. Turning either into the better idiom would change
the behaviour of the code under test. Scoped to the class. -->
<Match>
<Or>
<Bug pattern="WA_NOT_IN_LOOP"/>
<Bug pattern="NO_NOTIFY_NOT_NOTIFYALL"/>
</Or>
<Class name="se.deversity.asynctest.AgentMonitorHooks"/>
</Match>
<!-- The same for AgentLockHooks' Condition.await hooks (#1048): each stands in for the one
await the agent substituted, and the loop around it belongs to the woven caller. Scoped
to the hooks that substitute an await. -->
<Match>
<Bug pattern="WA_AWAIT_NOT_IN_LOOP"/>
<Class name="se.deversity.asynctest.AgentLockHooks"/>
<Or>
<Method name="conditionAwait"/>
<Method name="conditionAwaitUninterruptibly"/>
</Or>
</Match>
</FindBugsFilter>