Skip to content

SONARJAVA-6769 Implement new rule S9342: Archive entries should not be empty - #5938

Open
romainbrenguier wants to merge 3 commits into
masterfrom
new-rule/SONARJAVA-6769-S9342
Open

SONARJAVA-6769 Implement new rule S9342: Archive entries should not be empty#5938
romainbrenguier wants to merge 3 commits into
masterfrom
new-rule/SONARJAVA-6769-S9342

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Detect empty archive entries where closeEntry() is called on a ZipOutputStream or JarOutputStream after putNextEntry() without any intervening write() call, which creates useless empty entries in the archive.

Detect empty archive entries where closeEntry() is called on a
ZipOutputStream or JarOutputStream after putNextEntry() without any
intervening write() call, which creates useless empty entries in the
archive.
@romainbrenguier romainbrenguier changed the title SONARJAVA-67679 Implement new rule S9342: Archive entries should not be empty SONARJAVA-6769 Implement new rule S9342: Archive entries should not be empty Aug 18, 2026
@hashicorp-vault-sonar-prod

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

Copy link
Copy Markdown
Contributor

SONARJAVA-6769

romainbrenguier and others added 2 commits August 18, 2026 11:53
…tor wrapping

- Fix false positive when stream is passed to a helper method as argument
  (e.g., writeContent(zos)) by checking arguments when receiver is null
- Fix false positive on ZIP directory entries (names ending with "/")
- Fix false positive when stream is wrapped in a constructor (e.g.,
  new PrintWriter(zos)) by adding visitNewClass to TrackedSymbolVisitor
- Skip analysis when semantic model is unavailable to avoid false positives

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

gitar-bot Bot commented Aug 18, 2026

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

Implements the new S9342 rule to detect empty archive entries, while correctly resolving false positives on intentional ZIP directory entries and wrapper streams. No issues found.

✅ 2 resolved
Edge Case: False positive on intentional ZIP directory entries

📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:85-92
ZIP/JAR directory entries are created intentionally with putNextEntry(new ZipEntry("dir/")) followed immediately by closeEntry() and no write() call — an empty entry is the correct and expected representation of a directory. The check flags this common, legitimate pattern as noncompliant. Consider excluding entries whose name ends with "/" (or otherwise detecting directory entries) and add a compliant test case covering directory entries.

Edge Case: FP when stream content written via wrapper stream/writer

📄 java-checks/src/main/java/org/sonar/java/checks/EmptyArchiveEntryCheck.java:143-157
TrackedSymbolVisitor only clears a tracked entry when the stream is used in a method invocation (write(...) or passed as a method argument), but it never overrides visitNewClass. A very common pattern wraps the stream in a constructor — e.g. new PrintWriter(zos), new BufferedOutputStream(zos), new ObjectOutputStream(zos) — and writes through the wrapper before closeEntry(). Since zos is passed to a constructor (NewClassTree), not a method, the entry is never cleared and a false positive is raised. Override visitNewClass to also treat tracked symbols passed as constructor arguments as "used", and add a test case for wrapper-based writing.

Implementation Status ✅ 1 / 1 issues implemented
SONARJAVA-6769 — 1 / 1 objectives

The PR successfully implements the new rule S9342: Archive entries should not be empty, along with comprehensive test cases and rule metadata.

✅ 1 complete
  • ✅ Implement new rule S9342: Archive entries should not be empty
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

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier marked this pull request as ready for review August 18, 2026 11:55

@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.

I found three verified false-positive paths in S9342. The focused submitted test passes (mvn -pl java-checks -Dtest=EmptyArchiveEntryCheckTest test), but each case below produces an unexpected issue when analyzed with the new check.


private void handleMethodInvocation(MethodInvocationTree mit, Map<Symbol, MethodInvocationTree> pendingEntries) {
Symbol receiver = getReceiverSymbol(mit);
if (receiver == null) {

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.

Argument escape handling only runs when the invocation has no receiver. Consequently, this.writeContent(stream) (or helper.writeContent(stream)) leaves the entry pending and closeEntry() is reported even though the helper writes content. Apply the conservative argument clearing for other non-rule invocations too, and add a qualified-helper regression case.

return null;
}

private static boolean isDirectoryEntry(MethodInvocationTree putNextEntry) {

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.

The directory exemption only recognizes an inline new ZipEntry("dir/"). A common equivalent—constructing ZipEntry directory = new ZipEntry("dir/") first and passing directory to putNextEntry()—is reported as an empty file. Either recognize the entry value through the local symbol or conservatively avoid reporting when directory status cannot be established; add this form to the sample.

reportIssue(ExpressionUtils.methodName(mit), "Write content to this archive entry; it is empty.",
Collections.singletonList(new JavaFileScannerContext.Location("Entry opened here", ExpressionUtils.methodName(putNextEntry))), null);
}
} else if (WRITE.matches(mit)) {

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.

Pending entries are keyed only by the ZipOutputStream symbol. If an OutputStream writer = new BufferedOutputStream(stream) is created before putNextEntry(), writer.write(...) removes writer rather than stream, so a subsequent stream.closeEntry() is falsely reported. Track aliases/wrappers or conservatively clear the wrapped stream’s state, including for wrappers created before an entry opens.

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