From f12b7a2a5abb64e70d5696e91b5c248a4b6059c9 Mon Sep 17 00:00:00 2001 From: Tobias Riemenschneider Date: Tue, 3 Nov 2026 12:02:36 +0100 Subject: [PATCH] Java: Exclude unnamed variables from java/local-variable-is-never-read An unnamed variable (`_`, Java 22) cannot be read, so reporting it is always a false positive. Fixes #22993. Co-Authored-By: Claude Opus 5.5 --- .../Dead Code/UnreadLocal.ql | 2 + ...26-11-03-unread-local-unnamed-variables.md | 4 ++ .../UnreadLocal/UnnamedVariables.java | 45 +++++++++++++++++++ java/ql/test/query-tests/UnreadLocal/options | 1 + 4 files changed, 52 insertions(+) create mode 100644 java/ql/src/change-notes/2026-11-03-unread-local-unnamed-variables.md create mode 100644 java/ql/test/query-tests/UnreadLocal/UnreadLocal/UnnamedVariables.java create mode 100644 java/ql/test/query-tests/UnreadLocal/options diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql index ae85f5466132..e7a6f63c2858 100644 --- a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql @@ -32,5 +32,7 @@ where f.getVariable().getVariable() = v and f.getExpr().getType().(RefType).hasQualifiedName("kotlin.ranges", ["IntRange", "LongRange"]) ) and + // Unnamed variables (`_`) cannot be read. + not v.getDeclExpr().isAnonymous() and not readImplicitly(v) select v, "Variable '" + v + "' is never read." diff --git a/java/ql/src/change-notes/2026-11-03-unread-local-unnamed-variables.md b/java/ql/src/change-notes/2026-11-03-unread-local-unnamed-variables.md new file mode 100644 index 000000000000..8cb1aececb4e --- /dev/null +++ b/java/ql/src/change-notes/2026-11-03-unread-local-unnamed-variables.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/local-variable-is-never-read` query no longer reports unnamed variables (`_`), for example in `case Foo _ ->` patterns, `for (var _ : items)` loops, and `var _ = ...;` declarations. diff --git a/java/ql/test/query-tests/UnreadLocal/UnreadLocal/UnnamedVariables.java b/java/ql/test/query-tests/UnreadLocal/UnreadLocal/UnnamedVariables.java new file mode 100644 index 000000000000..887e14a19168 --- /dev/null +++ b/java/ql/test/query-tests/UnreadLocal/UnreadLocal/UnnamedVariables.java @@ -0,0 +1,45 @@ +package test; + +import java.util.List; + +public class UnnamedVariables +{ + sealed interface Shape permits Circle, Square {} + record Circle(double radius) implements Shape {} + record Square(double side) implements Shape {} + record Pair(Object left, Object right) {} + + // Not flagged: unnamed variables cannot be read + static int side(Shape shape) + { + return switch (shape) { + case Circle _ -> 0; + case Square s -> (int) s.side(); + }; + } + + static Object left(Object o) + { + if (o instanceof Pair(var left, _)) { + return left; + } + return switch (o) { + case Pair(Object left, Object _) -> left; + default -> null; + }; + } + + static int count(List items) + { + int n = 0; + for (String _ : items) { + n++; + } + return n; + } + + static void removeFirst(List items) + { + var _ = items.remove(0); + } +} diff --git a/java/ql/test/query-tests/UnreadLocal/options b/java/ql/test/query-tests/UnreadLocal/options new file mode 100644 index 000000000000..5011824a5d7a --- /dev/null +++ b/java/ql/test/query-tests/UnreadLocal/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args --release 22