Skip to content

SONARJAVA-6870: Fix FP in S9342 when zip stream is wrapped in custom OutputStream - #6056

Merged
romainbrenguier merged 4 commits into
masterfrom
romain/sonarjava-6870
Sep 2, 2026
Merged

SONARJAVA-6870: Fix FP in S9342 when zip stream is wrapped in custom OutputStream#6056
romainbrenguier merged 4 commits into
masterfrom
romain/sonarjava-6870

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Summary

  • Add test example for S9342 where ObjectMapper.writeValue writes to a custom OutputStream wrapping a ZipOutputStream
  • Fix clearTrackedSymbolsUsedAsArguments to deeply scan non-identifier arguments (e.g., new CustomOutputStream(zos)) for tracked symbol usage, preventing false positives when the zip stream is nested inside a constructor call within a method argument

Test plan

  • Existing EmptyArchiveEntryCheckTest passes (both with and without semantic)
  • CI passes

🤖 Generated with Claude Code

…utputStream

Add test example where ObjectMapper writes to a custom OutputStream
wrapping a ZipOutputStream. Fix clearTrackedSymbolsUsedAsArguments to
deeply scan non-identifier arguments for tracked symbol usage, preventing
false positives when the zip stream is passed to a constructor nested
inside a method call argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6870

Unwrap casts and parenthesized expressions before checking if an
argument is a tracked symbol, fixing FPs like `(OutputStream) zos`.
Extract a shared removeUsedSymbols helper to eliminate duplicated
visitor logic between scanForTrackedSymbolUsage and
clearTrackedSymbolsUsedAsArguments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romainbrenguier
romainbrenguier marked this pull request as ready for review September 1, 2026 12:21

@nathsou nathsou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, approved with a minor observation regarding cast/parenthesis unwrapping in TrackedSymbolVisitor.

if (unwrapped.is(Tree.Kind.IDENTIFIER)) {
pendingEntries.remove(((IdentifierTree) unwrapped).symbol());
} else {
removeUsedSymbols(unwrapped, pendingEntries);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While unwrapping casts and parentheses here covers direct arguments of the method invocation, TrackedSymbolVisitor (visitNewClass and visitMethodInvocation) still checks arg.is(Tree.Kind.IDENTIFIER) directly without unwrapping casts or parentheses.

Since TrackedSymbolVisitor.visitIdentifier is an intentional no-op, tracked symbols nested inside a constructor/method call argument behind a cast or parentheses (e.g., new CustomOutputStream((OutputStream) zos)) won't be detected during the AST traversal.

Extracting a shared helper (e.g. extractIdentifier(ExpressionTree)) to unwrap casts and parentheses and reusing it in both clearTrackedSymbolsUsedAsArguments and TrackedSymbolVisitor would ensure nested cases are handled consistently.

… in S9342

Ensure TrackedSymbolVisitor handles casts and parentheses consistently
with clearTrackedSymbolsUsedAsArguments by extracting extractIdentifierSymbol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…esized

Reuse extractIdentifierSymbol in getReceiverSymbol so that expressions
like ((OutputStream) zos).write(...) or (zos).write(...) correctly
resolve the receiver symbol instead of returning null, which was leaving
the stream in pendingEntries and causing a false positive on closeEntry().

Also remove redundant cast/parentheses unwrapping loop in
clearTrackedSymbolsUsedAsArguments since BaseTreeVisitor traversal
already handles nested expressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Fixes false positives in S9342 when ObjectMapper.writeValue writes to a custom OutputStream wrapping a ZipOutputStream. Deep argument scanning now correctly detects tracked symbols behind casts and parentheses, and shared unwrapping logic eliminates duplicated visitor code.

✅ 3 resolved
Edge Case: Deep argument scan misses symbols behind casts or parentheses

📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:167-177 📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:231-235
The new else branch delegates to TrackedSymbolVisitor, but that visitor's visitIdentifier is a deliberate no-op, so a tracked symbol is only detected when it is a direct argument of a nested method invocation or new expression. An argument such as objectMapper.writeValue((OutputStream) zos, new Object()), objectMapper.writeValue((zos), obj) or a conditional flag ? zos : other reaches arg.accept(visitor), traverses down to the zos identifier, hits the no-op visitIdentifier, and leaves zos in pendingEntries — so the following zos.closeEntry() still raises the same false positive this PR is meant to fix. Handling identifiers found while scanning an argument subtree (or unwrapping casts/parentheses via ExpressionUtils.skipParentheses/cast before the identifier test) would close the gap; a sample with a cast argument would pin the behaviour.

Quality: Duplicated visitor/removal logic and per-argument visitor allocation

📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:109-118 📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:163-177
Lines 171-175 repeat verbatim the body of scanForTrackedSymbolUsage (lines 113-117), and a fresh TrackedSymbolVisitor is allocated for every non-identifier argument even though one instance could scan all of them. Extracting a single removeUsedSymbols(Tree, Map) helper used by both call sites keeps the two scanning paths from drifting apart and avoids the redundant allocations.

Edge Case: Cast/parenthesized write receiver still yields FP in S9342

📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:196-206 📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:217-221 📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:166-176 📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:182-193
This commit teaches argument scanning to unwrap casts and parentheses via extractIdentifierSymbol, but the receiver path (getReceiverSymbol, used both from handleMethodInvocation and from TrackedSymbolVisitor.visitMethodInvocation) still requires a bare IDENTIFIER, so ((OutputStream) zos).write("data".getBytes()); or (zos).write(...) returns null; clearTrackedSymbolsUsedAsArguments then only inspects the arguments, zos stays in pendingEntries, and the following zos.closeEntry() is flagged — a false positive of exactly the family this PR fixes (verified by tracing both the top-level statement path and the nested-statement visitor path, where visitIdentifier is a no-op so the cast's inner identifier is never credited). Reuse the new helper in getReceiverSymbol so casted/parenthesized receivers are resolved too, and add a sample case such as ((OutputStream) zos).write("data".getBytes()); followed by a compliant zos.closeEntry();.

Implementation Status ◻️ 2 of 3 objectives covered
◻️ SONARJAVA-6870 - 2 of 3 objectives covered

This PR covers investigating the FP feedback, fixing false positives in S9342 when zip streams are wrapped in custom OutputStreams or use casts and parentheses, but does not add an example in rspec.

Other objectives on this issue, possibly covered elsewhere:

  • ◻️ Add example in rspec with empty file
✅ 2 covered here
  • ✅ Investigate FP feedback for S9342
  • ✅ Fix FP in S9342 when zip stream is wrapped in custom OutputStream
Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

sonarqube-next Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier merged commit 242ee1c into master Sep 2, 2026
16 checks passed
@romainbrenguier
romainbrenguier deleted the romain/sonarjava-6870 branch September 2, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants