Skip to content

Report useless @SuppressFBWarnings annotations#3307

Merged
hazendaz merged 17 commits intomasterfrom
issue-641
Mar 1, 2025
Merged

Report useless @SuppressFBWarnings annotations#3307
hazendaz merged 17 commits intomasterfrom
issue-641

Conversation

@gtoison
Copy link
Copy Markdown
Contributor

@gtoison gtoison commented Feb 7, 2025

I'm not sure why but we never answered #1054 and the PR got abandoned.
Reporting useless @SuppressFBWarnings annotations would be useful indeed and this would fix #641
This PR merges the original work from @mafor with the latest version.

@gtoison gtoison marked this pull request as ready for review February 7, 2025 11:04
Comment thread spotbugs/etc/messages.xml
Comment thread spotbugs-tests/src/test/java/edu/umd/cs/findbugs/ba/Issue641Test.java Outdated
Comment thread spotbugsTestCases/src/java/suppression/Issue641.java
Comment thread spotbugs-tests/src/test/java/edu/umd/cs/findbugs/WarningSuppressorTest.java Outdated
@Test
void shouldNotCallUpstreamReporterIfBugSuppressed() {
// given
BugInstance suppressed = mock(BugInstance.class);
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.

Why mix annotation-based and method-based mocks? This could become another annotated field.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There are three tests, one calls the bug instance suppressed, the second calls it reported and the third one does not create a bug instance, so I think it makes sense that @mafor used a local variable here

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.

There is no need for the tests to use different names for the instance. As I see it, we just have 2/3 tests creating an instance mock, so it makes sense to be consistent with other values by making it an annotated field.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have made the change to create the instance with @Mock

// when
matcher.addSuppressor(suppressor);
// then
assertTrue(matcher.match(bugInstance), "Should match BugInstance");
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.

Error messages should give more detail about the failed value(s) if possible, especially for assertTrue (which doesn't give much detail natively)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've looked into this and do not see a practical way for the matcher to return why the instance wasn't matched.
We're using the term matcher for two separate things:

  • the suppression matchers we're using to filter out detected bugs
  • the hamcrest matchers we're using in unit tests (these matchers do provide messages)

I think it is fine to leave the test like that, it is short enough so we can reason about it

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 message could at least specify which suppression matcher is being applied? It could be deduced anyway by tracing the test, but seeing it at a glance is better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The suppressor is mocked here, so there's no real reason for the instance to be suppressed besides the suppressor being mocked to return false.

void shouldNotMatchBugInstance() {
// given
when(suppressor.match(bugInstance)).thenReturn(false);
when(suppressor.buildUselessSuppressionBugInstance()).thenReturn(uselessSuppressionBugInstance);
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.

Should this be in init?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The two tests mock objects differently:
when(suppressor.match(bugInstance)).thenReturn(true);
and:
when(suppressor.match(bugInstance)).thenReturn(false);

So I think this can't be factored in init

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.

Those lines are different, yes, but making the suppressor return the instance is the same in both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I don't follow: the suppressor does not return the instance and I don't think that it would be clearer to extract this:

OngoingStubbing<Boolean> when = when(suppressor.match(bugInstance));

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.

Line 57, specifically, is duplicated across multiple tests, and could be extracted to the setup method.

when(suppressor.buildUselessSuppressionBugInstance()).thenReturn(uselessSuppressionBugInstance);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry I'm still lost, I don't see duplications for that line in SuppressionMatcherMockedTest

// when
boolean matched = matcher.match(bug);
// then
assertThat("Should match the bug", matched, is(true));
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.

This ultimately doesn't give any more information than using assertTrue. If possible the error message should say more about the bug value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure how to extract the reason why the instance was matched or not, I think the test is fine like that

Comment thread spotbugs/etc/messages.xml
<LongDescription>Suppressing annotation on the class {0} is unnecessary</LongDescription>
<Details>
<![CDATA[<p>
Suppressing annotations <code>&amp;SuppressFBWarnings</code> should be removed from the source code as soon as they are no more needed.
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.

Perhaps this message could be adjusted to make it clear why the annotation is no longer needed, ie the code no longer contains that bug type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Any suggestion? There might be a lot of reason why that suppression annotation was added and why it is now unnecessary

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.

Just something to the effect that the the bug type is no longer raised by that code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've expanded the message for these unnecessary suppressions, let me know if that could be improved!

@sonarqubecloud
Copy link
Copy Markdown

@hazendaz hazendaz self-assigned this Mar 1, 2025
@hazendaz hazendaz added this to the SpotBugs 4.9.2 milestone Mar 1, 2025
@uhafner
Copy link
Copy Markdown

uhafner commented Mar 10, 2025

This is a very nice new feature! Is there a way to disable this for a specific case? I have a case where the new detector does not work correctly (I will create a new bug report tomorrow).

Example:
When I do not remove the suppression in LineRangeList in I get a new warnings of type US_USELESS_SUPPRESSION_ON_METHOD, see
https://github.com/uhafner/codingstyle/actions/runs/13753858193/job/38458244783

But if I remove the suppression I get a warning of IT_NO_SUCH_ELEMENT, see https://github.com/uhafner/codingstyle/actions/runs/13753897007/job/38458341372

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A checker that for unnecessary @SuppressFBWarnings ?

6 participants