forked from kherud/java-llama.cpp
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathspotbugs-exclude.xml
More file actions
363 lines (330 loc) · 16.6 KB
/
spotbugs-exclude.xml
File metadata and controls
363 lines (330 loc) · 16.6 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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2026 Bernard Ladenthin <bernard.ladenthin@gmail.com>
SPDX-License-Identifier: MIT
-->
<FindBugsFilter
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/4.8.6/spotbugs/etc/findbugsfilter.xsd">
<!--
OSInfo is vendored verbatim from xerial/sqlite-jdbc (originally @author leo).
See SPDX-FileCopyrightText header in src/main/java/net/ladenthin/llama/OSInfo.java.
Excluding the class (and its inner classes) from spotbugs analysis since
upstream fixes should land in xerial/sqlite-jdbc rather than be patched here.
-->
<Match>
<Class name="~net\.ladenthin\.llama\.loader\.OSInfo(\$.*)?"/>
</Match>
<!--
ProcessRunner is an internal package-private helper used solely by OSInfo
to invoke platform-detection commands ("uname -o", "uname -m"). It is
already hardened against shell-tokenisation by calling Runtime.exec(String[])
rather than the shell-parsing Runtime.exec(String). findsecbugs still flags
every non-literal Runtime.exec call as COMMAND_INJECTION regardless of which
overload is used; this exclusion documents the assessment that the finding
is theoretical only — there is no public API path that lets an external
caller reach this code, and the two call sites in OSInfo pass hardcoded
literal commands.
-->
<Match>
<Class name="net.ladenthin.llama.loader.ProcessRunner"/>
<Bug pattern="COMMAND_INJECTION"/>
</Match>
<!--
LlamaModel deliberately wraps low-level Jackson IOException into the
project's LlamaException (a RuntimeException subclass) at three JSON-parsing
boundary methods: completeAsJson, getMetricsTyped, getModelMeta. This is the
intended public-API design — IOException is an implementation detail of the
JSON parser, not something callers of a JNI bridge should be forced to handle.
The exception cause is already chained through so the original stack trace
is preserved (see f8c11b0). Spotbugs flags this as
EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS because the resulting RuntimeException
leaves no compile-time hint that an I/O failure happened; we accept that
tradeoff at these three boundary points.
-->
<Match>
<Class name="net.ladenthin.llama.LlamaModel"/>
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
<Or>
<Method name="completeAsJson"/>
<Method name="getMetricsTyped"/>
<Method name="getModelMeta"/>
</Or>
</Match>
<!--
ModelParameters intentionally types each enum-valued fluent setter
to its specific enum (CacheType, MiroStat, NumaStrategy,
ReasoningFormat, RopeScalingType, GpuSplitMode) rather than the
shared CliArg interface that those enums all implement. The narrow
type is the API contract:
params.setMirostat(MiroStat.V1) accepted
params.setMirostat(NumaStrategy.DISTRIBUTE) rejected by compiler
If we widened to CliArg as spotbugs OCP suggests, the second call
would silently compile and emit a nonsense CLI value that the
native code would reject at runtime. IDE autocomplete
also relies on the narrow type to surface the right enum
constants. Same design-intent rationale as the STT and EXS
suppressions above.
-->
<Match>
<Class name="net.ladenthin.llama.parameters.ModelParameters"/>
<Bug pattern="OCP_OVERLY_CONCRETE_PARAMETER"/>
<Or>
<Method name="setCacheTypeK"/>
<Method name="setCacheTypeV"/>
<Method name="setMirostat"/>
<Method name="setNuma"/>
<Method name="setReasoningFormat"/>
<Method name="setRopeScaling"/>
<Method name="setSplitMode"/>
</Or>
</Match>
<!--
Same design-intent rationale as the ModelParameters OCP block above:
InferenceParameters.withReasoningFormat(ReasoningFormat) intentionally
types its parameter to the specific ReasoningFormat enum rather than
the shared CliArg interface. The narrow type is the API contract;
widening it would silently accept any CliArg-implementing enum and
emit a nonsense JSON value the native code would reject.
-->
<Match>
<Class name="net.ladenthin.llama.parameters.InferenceParameters"/>
<Bug pattern="OCP_OVERLY_CONCRETE_PARAMETER"/>
<Method name="withReasoningFormat"/>
</Match>
<!--
InferenceParameters and ModelParameters are fluent builders whose
parameters field is a Map<String, String> serving as the CLI / JSON
wire-format passed across JNI to nlohmann/json. Every setter
deliberately serializes its argument (Jackson ArrayNode/ObjectNode
via .toString(), or scalar concat) and stores the string into the
map. Spotbugs STT_TOSTRING_STORED_IN_FIELD flags every site because
a String pinned by toString() cannot be reformatted with a different
locale/encoding later — that pin is the whole point of the
serialization step. Same design rationale as the EXS suppression on
LlamaModel above.
-->
<Match>
<Or>
<Class name="net.ladenthin.llama.parameters.InferenceParameters"/>
<Class name="net.ladenthin.llama.parameters.ModelParameters"/>
</Or>
<Bug pattern="STT_TOSTRING_STORED_IN_FIELD"/>
</Match>
<!--
LlamaLoader is the native-library bootstrap. It resolves the path
to libjllama.{so,dylib,dll} from three operator-controlled inputs:
1. -Dnet.ladenthin.llama.lib.path=<dir> (line 94)
2. java.library.path entries (line 119)
3. java.io.tmpdir + hardcoded basename (lines 133, 171, 215)
findsecbugs PATH_TRAVERSAL_IN flags every non-literal argument to
Paths.get, treating "user input" syntactically as "any non-literal
string". The threat-model reality is different: all three sources
are JVM properties set at process launch by whoever started the
process. An attacker who can set JVM properties has already won;
there is no untrusted end-user input reaching these paths.
Canonicalize-and-restrict-to-root mitigation is not applicable
because the whole purpose of the .lib.path property is to let the
operator point at any directory containing the native library;
there is no meaningful "allowed root" to validate against.
-->
<Match>
<Class name="net.ladenthin.llama.loader.LlamaLoader"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>
<!--
LlamaIterator and LlamaModel form a deliberate producer/consumer
cycle: LlamaModel.generate(...) returns a LlamaIterable that
yields LlamaIterator instances; each LlamaIterator calls back
into LlamaModel to fetch the next token via the native bridge.
This is the standard shape of a streaming iterator that drives
a backend (java.util.Iterator semantics require it). The static
class cycle is a side effect of the Iterator/Iterable API
contract, not a design defect.
Breaking the static cycle with an interface (e.g. a TokenSource
that LlamaModel implements and LlamaIterator depends on) would
add a fake abstraction with one implementer and no decoupling
value; the runtime coupling is identical either way.
-->
<Match>
<Or>
<Class name="net.ladenthin.llama.LlamaIterator"/>
<Class name="net.ladenthin.llama.LlamaModel"/>
</Or>
<Bug pattern="FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY"/>
</Match>
<!--
Session is a thin non-owning wrapper around a LlamaModel: one LlamaModel
wraps a native llama.cpp context that is intentionally shared across
multiple Session instances (one per slot id). The model owns native
memory and must NOT be defensively copied — Session deliberately holds
the same reference the caller passed in, and Session.close() calls
model.eraseSlot(slotId), never model.close(). This is the documented
dependency-injection contract; spotbugs flags it as EI_EXPOSE_REP2
because the constructor stores an externally-mutable object, which is
true but by design.
-->
<Match>
<Class name="net.ladenthin.llama.Session"/>
<Bug pattern="EI_EXPOSE_REP2"/>
<Method name="<init>"/>
</Match>
<!--
USBR_UNNECESSARY_STORE_BEFORE_RETURN on Lombok-generated equals / hashCode /
canEqual / toString.
Lombok's @EqualsAndHashCode and @ToString annotation processors inject the
textbook polynomial-hash bytecode pattern (and lombok.config already emits
@lombok.Generated on every synthetic member via
lombok.addLombokGeneratedAnnotation = true):
int result = 1;
result = result * 59 + ($field == null ? 43 : $field.hashCode());
...
return result; // USBR fires here, on the istore_N / iload_N / ireturn triplet
SpotBugs core honours @lombok.Generated and skips its own detectors on those
members, but the fb-contrib plugin's USBR detector does NOT — fb-contrib
is a separate plugin family with its own filter pipeline. Suppressing USBR on
equals / hashCode / canEqual / toString matches every method name Lombok can
emit. The collateral cost is small: any handwritten member of those four names
that genuinely stores-then-immediately-returns is either a debugger-friendly
local-variable pattern or a micro-optimisation, both intentional here.
Cross-repo invariant — see `../workspace/policies/lombok-config.md`.
-->
<Match>
<Or>
<Method name="equals"/>
<Method name="hashCode"/>
<Method name="canEqual"/>
<Method name="toString"/>
</Or>
<Bug pattern="USBR_UNNECESSARY_STORE_BEFORE_RETURN"/>
</Match>
<!--
fb-contrib OPM_OVERLY_PERMISSIVE_METHOD ("Method is declared more
permissively than is used in the code base") suppressed PROJECT-WIDE.
Rationale (kept aligned cross-repo with BitcoinAddressFinder):
- Current package layout groups most production Java in
net.ladenthin.llama + a thin sibling package set. Any method
called only from same-package callers is flagged as "could be
package-private". Those answers are correct today but unstable:
once the planned package-architecture refactor splits the root
package into proper layers, methods that today are correctly
package-private will need to become public to cross the new
boundaries. Tightening now produces mechanical churn highly
likely to be reverted by the refactor.
- Cross-repo decision + per-category breakdown recorded in
workspace/crossrepostatus.md ("OPM scope-tightening — after
package refactor"). The same rule is suppressed in BAF.
TODO: re-enable this rule (delete this Match block) once the
package refactor has settled — at that point genuine "method
exposed beyond its actual call site" findings become stable
signals worth fixing.
-->
<Match>
<Bug pattern="OPM_OVERLY_PERMISSIVE_METHOD"/>
</Match>
<!--
ChatRequest is an immutable value class. Its messages/tools fields
are stored as Collections.unmodifiableList(...) views, so the
getters CANNOT actually leak the internal representation: any
attempt to mutate the returned list throws UnsupportedOperationException
(covered by ChatRequestTest.messagesAccessorIsUnmodifiable /
toolsAccessorIsUnmodifiable). SpotBugs flags every "return this.field"
from a non-array reference field as EI_EXPOSE_REP without tracking
whether the field was unmodifiable-wrapped at construction time;
the wrapping is verified by tests, so the finding is a false positive.
-->
<Match>
<Class name="net.ladenthin.llama.parameters.ChatRequest"/>
<Bug pattern="EI_EXPOSE_REP"/>
<Or>
<Method name="getMessages"/>
<Method name="getTools"/>
</Or>
</Match>
<!--
LlamaModel.ctx is the per-instance native handle: a long pointer
into the llama.cpp context owned by THIS LlamaModel instance.
fb-contrib's SPP_FIELD_COULD_BE_STATIC detector observes that
the field is only assigned inside loadModel (called from the
constructor) and never reassigned, and concludes the field could
be promoted to static. That is incorrect: every LlamaModel wraps
its OWN native context, and making ctx static would cause every
instance to share one handle — corrupting state across parallel
inference calls and double-freeing on close().
-->
<Match>
<Class name="net.ladenthin.llama.LlamaModel"/>
<Bug pattern="SPP_FIELD_COULD_BE_STATIC"/>
<Field name="ctx"/>
</Match>
<!--
CancellationToken and ChatTranscript are lifecycle handles managed by
identity, not value: a CancellationToken owns a mutable cancellation
flag observed across threads, and ChatTranscript is an append-only
transcript owned by a single Session and never compared by value.
Both classes deliberately do NOT generate Lombok @EqualsAndHashCode
(documented in their Javadocs) — fb-contrib's IMC_NO_EQUALS check
is therefore a false positive for both.
-->
<Match>
<Or>
<Class name="net.ladenthin.llama.callback.CancellationToken"/>
<Class name="net.ladenthin.llama.value.ChatTranscript"/>
</Or>
<Bug pattern="IMC_IMMATURE_CLASS_NO_EQUALS"/>
</Match>
<!--
TimingsLogger emits its events under the documented public logger name
"net.ladenthin.llama.timings" (see CLAUDE.md > System Properties Reference
and the README), NOT the FQN of the TimingsLogger class. That separation
lets operators raise/lower the per-run-timing line independently of
application logs. fb-contrib's LO_SUSPECT_LOG_CLASS detector flags any
logger whose name does not match the enclosing class FQN; here the
mismatch is the public contract.
-->
<Match>
<Class name="net.ladenthin.llama.json.TimingsLogger"/>
<Bug pattern="LO_SUSPECT_LOG_CLASS"/>
</Match>
<!--
Java8CompatibilityHelper.formatted is a thin wrapper around
String.format that intentionally accepts runtime-supplied format
strings — the helper exists precisely so that Java 11+'s
String#formatted() can be used uniformly on the Java 8 baseline.
fb-contrib's FORMAT_STRING_MANIPULATION fires on any non-literal
format argument; the wrapper is the documented escape hatch.
-->
<Match>
<Class name="net.ladenthin.llama.loader.Java8CompatibilityHelper"/>
<Bug pattern="FORMAT_STRING_MANIPULATION"/>
<Method name="formatted"/>
</Match>
<!--
ToolHandler.invoke is the functional-interface contract for caller-
supplied tool handlers. `throws Exception` is the right shape because
the handler body is user code that can throw anything; LlamaModel's
chatWithTools agent loop catches the broad Exception and reports it
back to the model as a {"error":"..."} tool result rather than
aborting the request. Narrowing the throws clause would force every
handler implementation to wrap arbitrary checked exceptions for
no behavioural benefit.
-->
<Match>
<Class name="net.ladenthin.llama.callback.ToolHandler"/>
<Bug pattern="THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION"/>
<Method name="invoke"/>
</Match>
<!--
ChatMessage.requireNonNull is a precondition guard whose only
meaningful state to report is the parameter name itself (the value
is null by definition at the throw point). fb-contrib's WEM detector
recognises the static-string IllegalArgumentException as "weak", but
there is no additional state-dependent context to add at this guard.
-->
<Match>
<Class name="net.ladenthin.llama.value.ChatMessage"/>
<Bug pattern="WEM_WEAK_EXCEPTION_MESSAGING"/>
<Method name="requireNonNull"/>
</Match>
</FindBugsFilter>