Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate "unknown" checker state #3949

Merged
merged 3 commits into from
Sep 8, 2023

Conversation

bruntib
Copy link
Contributor

@bruntib bruntib commented Jul 10, 2023

[cmd] Eliminate default checker status

Currently there are 3 kind of checkers regarding whether they are
running during analysis: enabled, disabled, unknown. Checkers can be
enabled if they are explicitly given to --enable flag, or they are the
member of "default" profile. Checkers can also be explicitly disabled.
The 3rd status is tricky: CodeChecker doesn't explicitly enable or
disable them, but gives the choice to the analyzer tool.

This behavior is bad, because a user cannot tell exactly which checkers
were executed during analysis. It is impossible to determine in case of
no error if a checker was disabled or just simply didn't report any
issue.

The goal in this commit is that every checker is either enabled or
disabled. It is the analyzer module's responsibility to assemble an
analysis command which executes at least the enabled checkers and turns
off or hides the disabled ones.

[feat] ClangTidy enables only selected checkers

Due to the previous commit every checker is either enabled or disabled.
This resulted very long analyzer commands in ClangTidy because every
checker was explicitly enabled or disabled in the clang-tidy invocation.
ClangTidy has a lot of checkers and also all compiler warnings listed by
diagtool are also considered as checkers.

The goal of this commit is to simplify the clang-tidy invocation command.
First we disable all checkers and enable only those that are necessary to
run:

clang-tidy -checks=-*,enabled1,enabled2...

This patch takes into account that clang-diagnostic-... checkers don't
turn on the corresponding warnings. They have to be enabled by -W...
compiler flag.

[feat] Hide disabled ClangSA checkers in post-processing

ClangSA checkers may rely on each other. For example unix.Malloc checker
may prevent a report from unix.MismatchedDeallocator. It is possible that
after disabling unix.Malloc the underlying analysis continues giving a
chance for unix.MismatchedDeallocator to emit a report.

In order to avoid this noisy behavior, ClangSA checkers are never
disabled during the analysis invocation. If CodeChecker wants to disable
a checker then its result should be hidden in a post-processing step.

@bruntib bruntib added enhancement 🌟 CLI 💻 Related to the command-line interface, such as the cmd, store, etc. commands analyzer 📈 Related to the analyze commands (analysis driver) clang sa 🐉 The Clang Static Analyzer is a source code analysis tool that finds bugs in C-family programs. clang-tidy 🐉 clang-tidy is a clang-based C++ “linter” tool. cppcheck 🐞 CppCheck is a static analysis tool for C++ code labels Jul 10, 2023
@bruntib bruntib added this to the release 6.23.0 milestone Jul 10, 2023
@bruntib bruntib requested a review from Szelethus July 10, 2023 10:57
@bruntib bruntib requested a review from vodorok as a code owner July 10, 2023 10:57
@@ -165,13 +165,51 @@ def get_warnings(env=None):
raise


def _add_star_for_group(
checkers: Iterable[str],
Copy link
Collaborator

Choose a reason for hiding this comment

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

checkers and all_checkers? Since this is a single purpose function, how about we call this enabled_checkers, or subset_checkers?

@@ -278,14 +330,18 @@ def test_disable_clangsa_checkers(self):

for arg in analyzer.construct_analyzer_cmd(result_handler):
if arg.startswith('-checks='):
self.assertIn('-clang-analyzer-*', arg)
self.assertLess(*self._enable_disable_pos(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still a chore to digest. Can't you reduce it down to something easier to read? Like, is_disabled?

@Szelethus
Copy link
Collaborator

Also, my usual complaint. You explain in the summary what this patch does, but not why this is any good.

@bruntib
Copy link
Contributor Author

bruntib commented Jul 17, 2023

I fixed the things according to the comments, thank you!
As for the last one, I believe the commit messages answer the question of "what". If it doesn't then let's elaborate on it, I would appreciate if you could help to make comments as clear as possible. However, I think some comments have to describe "why" something happens, because many times it is hard to figure it out if something is done consciously or inherited from some outdated legacy.

@bruntib bruntib requested a review from Szelethus July 17, 2023 13:03
@Szelethus
Copy link
Collaborator

Here is what I mean under the why missing from the summary.

Ideally, I would like to learn the following:

  • How the program works now
  • Why is this behaviour undesirable or incorrect, and what it should be instead
  • What was the intent behind this behaviour back in the day (or at least an educated guess)
  • If you considered multiple high level approaches to fix this, what were they, why did you pick the one found in the patch
  • Briefly describe what the actual patch is about, how it fixed the problem.

Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

If I never had an issue with wanting to disable a checker but couldn't, would I notice any change in the new behaviour? Moving on, would I need to manually enable/disable clang-diagnostics to restore the pre-patch result set?

except plistlib.InvalidFileException:
# It may happen that a compilation database contains a build action
# multiple times. For further details see analyzer_action_str() in
# ResultHandler. If this happens then the analysis of the same
Copy link
Collaborator

Choose a reason for hiding this comment

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

That comment says nothing about multiple build command occurances.

# This warning must be given a parameter separated by either '=' or
# space. This warning is not supported as a checker name so its
# special usage is avoided.
if warning_name and warning_name.startswith('frame-larger-than'):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to this in this patch? Seems to be a new addition. If we must, we could at least emit a curtesy warning that this won't be enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a function which returns the available checkers. By this if statement this "checker" will not be displayed for the users, do they don't even know about the existence of it. They cant provide it in any analyzer command.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't explain my question. Can this not be a separate patch? If not, why is it justified here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might have misunderstood, but in your original comment you suggested to provide a "courtesy warning" for the users which says that this checker/warning will not be enabled. That's why I answered that there is no need for such a warning, because CodeChecker users will not even know about the existence of this "checker" if we exclude it in this get_checker_list() function.

And we have to exclude it, because otherwise an invalid clang-tidy invocation will be assembled: clang-tidy ... main.c -- -Wframe-larger-than=. This is invalid, because this warning requires an integer parameter after =. So this is why I decided to hide this checker/warning at all. If I don't exclude this warning then one of our tests fail which enable all "clang-diagnostic-" checkers:
CodeChecker analyze -e clang-diagnostic ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's why I answered that there is no need for such a warning, because CodeChecker users will not even know about the existence of this "checker" if we exclude it in this get_checker_list() function.

This would be valid if CodeChecker was the entity that released this warning, but that is not the case, its perfectly reasonable for a Clang user to expect this warning to just work,

Copy link
Collaborator

Choose a reason for hiding this comment

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

Aha, I understand now that this function asks clang-tidy for the list of recognized checks, and removed this particular one, so it will act as if the requested didn't exist, resulting in a warning.

But not the right warning. It will say that the check doesn't exist, which is not true -- it does exist, we just don't don't allow running it under CodeChecker. Lets save the user some headache, his Clang binary is not broken -- please emit a warning about this.

warning: CodeChecker is incompatible with compiler warning 'frame-larger-than'. Please don't enable it.

analyzer/tests/unit/test_checker_handling.py Outdated Show resolved Hide resolved
self.assertIn('-clang-analyzer-*', arg)
self.assertTrue(self._is_disabled(
'clang-analyzer',
arg[len('-checks='):].split(',')))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, this isn't any better. It should look like this:

self.assertTrue(self._is_disabled('clang-analyzer', arg))

and other nasties should be inside _is_disabled. Do you see my point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I do it this way then test_default_checkers_are_not_disabled() and test_clangsa_analyzer_checks_are_disabled() will be ugly. Which one should we save? :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this really a decision to make? :D Can you not make a function for this case and one for the another so that both of them are legible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, restructured.

Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

This is getting close!

# This warning must be given a parameter separated by either '=' or
# space. This warning is not supported as a checker name so its
# special usage is avoided.
if warning_name and warning_name.startswith('frame-larger-than'):
Copy link
Collaborator

Choose a reason for hiding this comment

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

That's why I answered that there is no need for such a warning, because CodeChecker users will not even know about the existence of this "checker" if we exclude it in this get_checker_list() function.

This would be valid if CodeChecker was the entity that released this warning, but that is not the case, its perfectly reasonable for a Clang user to expect this warning to just work,

Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

Ugh, sorry, after taking another look at the tests, I'm still a little confused.

@@ -77,15 +77,16 @@ def setup_class(self):
'skip_list_file': skip_list_file,
'check_env': test_env,
'workspace': TEST_WORKSPACE,
'checkers': [],
'checkers': ['-d', 'clang-diagnostic'],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, it seems like you have to beat this into me. Don't we disable all compiler warnings out of the box? Why is this needed? Why did you need to change almost all invocations of CodeChecker in these tests? Its a mess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some clang-diagnostic-... checkers have been added to the "default" profile (see the changes of clang-tidy.json), that's why some of them appeared in tests files. So either I rewrite all test files to eliminate the source of the report, or I disable clang-diagnostic-... checkers in order to remain conform with the previous behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Aha, I see. In that case, can we not move the profile changed to its own PR?

'-d', 'unix.Malloc'
'-d', 'unix.Malloc',
'-d', 'clang-diagnostic',
'-e', 'clang-diagnostic-division-by-zero'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this needed? I am more interested in the design reason, not the technical one. Suppose that you are new to this test file, and stumble upon this invocation -- what does it convey to you? We manually disable all warnings for some reason, but enable a specific one after that?

Was this an attempt to reduce the amount of changes needed in the tests, otherwise everything broke?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same answer as above. As for the design reason: in this test we have multiple analyses with different configurations. Here we want to test which checkers' reports have appeared and disappeared. We control this by explicitly enabling and disabling them in different analysis actions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you explain in the comments why we have this particular checker configuration?

@@ -114,7 +123,11 @@ def setup_class_common(workspace_name):
print("Third analysis of the test project was successful.")

# Let's run the second analysis and updat the same run.
codechecker_cfg['checkers'] = ['-d', 'core.StackAddressEscape']
codechecker_cfg['checkers'] = ['-d', 'core.StackAddressEscape',
'-d', 'unix.Malloc',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Whats the reason behind this? Why did you disable MallocChecker?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Earlier the meaning of --disable flag was to actually disable the checker in the clang invocation. Now the meaning of --disable flag is to hide the results of the given checker. So after this patch core.StackAddressEscape is actually running, even if disabled, but its reports are hidden by post-processing. This caused unix.Malloc checker to report its own diagnostics.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This sounds very fishy. I'd be shocked if not properly disabling core.StackAddressEscape would result in the sudden enabling of MallocChecker. This should only happen with modeling checkers that by design don't emit diagnostics.

edit: I see, this breaks test_fix_date_filters, where we explicitly check whether a check was disabled by CodeChecker's understanding. I again suggest that you document it somewhere why we have this nonsensical looking, but perfectly fine config.

@@ -382,7 +382,7 @@ def test_detection_date_filters(self):
def test_fix_date_filters(self):
""" Filter by fix dates. """
report_filter = ReportFilter(
detectionStatus=[DetectionStatus.RESOLVED])
detectionStatus=[DetectionStatus.OFF])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you provide more context for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar issue than the previous one. It comes from the "disable vs. hide" semantic change. The "OFF" status means that the unix.Malloc checker has been disabled and that's the reason for the report being resolved. Earlier there was a unix.MismatchedDeallocator checker report which simply became resolved without the checker being disabled. I agree if it seems a little fuzzy. If you need more precise elaboration about the exact reasons, then I could take some more effort in its inspection once... maybe sometime... in the future... :)

Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

I would like to see the 'frame-larger-than' warning having its own patch, as well as the profile changes.

I recall a long while, Artem gave me a stern talk about too large patches. They are sometimes unavoidable (like, you need to simultaneously patch clang-tidy AND clang-sa here), but those should be the exception, rather than the rule. A good rule of thumb might be the following -- each change that deserves its own discussion should deserver its own patch.

Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

I still strongly disagree with not having a curtesy warning for larger-than-frame. Its a piece of information we immediately possess and are choosing to withhold for no reason, or at least it sure seems to have no good reason in my view.

I will accept that this is not the time or place to divide this PR into smaller chunks, but am rather unhappy about it. Its possible that we would have landed parts of this pach much sooner if it was more managable in scope (this last part alone consumed ~2 weeks). Having separate commits is nice (and helped me greatly!), but some parts of this PR would have definitely deserved its own PR as well.

I come from a different reviewer environment, so please let me know if I'm the one that needs to change to workflow, or at least lets try better to find a common ground. Also, I understand that 90-95% of this patch did indeed need to land in a single piece.

Granted that @dkrupp makes the call on the larger-than-frame discussion and my last few nits are addressed, this PR LGTM.

'-d', 'unix.Malloc'
'-d', 'unix.Malloc',
'-d', 'clang-diagnostic',
'-e', 'clang-diagnostic-division-by-zero'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you explain in the comments why we have this particular checker configuration?

@@ -114,7 +123,11 @@ def setup_class_common(workspace_name):
print("Third analysis of the test project was successful.")

# Let's run the second analysis and updat the same run.
codechecker_cfg['checkers'] = ['-d', 'core.StackAddressEscape']
codechecker_cfg['checkers'] = ['-d', 'core.StackAddressEscape',
'-d', 'unix.Malloc',
Copy link
Collaborator

Choose a reason for hiding this comment

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

This sounds very fishy. I'd be shocked if not properly disabling core.StackAddressEscape would result in the sudden enabling of MallocChecker. This should only happen with modeling checkers that by design don't emit diagnostics.

edit: I see, this breaks test_fix_date_filters, where we explicitly check whether a check was disabled by CodeChecker's understanding. I again suggest that you document it somewhere why we have this nonsensical looking, but perfectly fine config.

Currently there are 3 kind of checkers regarding whether they are
running during analysis: enabled, disabled, unknown. Checkers can be
enabled if they are explicitly given to --enable flag, or they are the
member of "default" profile. Checkers can also be explicitly disabled.
The 3rd status is tricky: CodeChecker doesn't explicitly enable or
disable them, but gives the choice to the analyzer tool.

This behavior is bad, because a user cannot tell exactly which checkers
were executed during analysis. It is impossible to determine in case of
no error if a checker was disabled or just simply didn't report any
issue.

The goal in this commit is that every checker is either enabled or
disabled. It is the analyzer module's responsibility to assemble an
analysis command which executes at least the enabled checkers and turns
off or hides the disabled ones.
Due to the previous commit every checker is either enabled or disabled.
This resulted very long analyzer commands in ClangTidy because every
checker was explicitly enabled or disabled in the clang-tidy invocation.
ClangTidy has a lot of checkers and also all compiler warnings listed by
diagtool are also considered as checkers.

The goal of this commit is to simplify the clang-tidy invocation command.
First we disable all checkers and enable only those that are necessary to
run:

clang-tidy -checks=-*,enabled1,enabled2...

This patch takes into account that clang-diagnostic-... checkers don't
turn on the corresponding warnings. They have to be enabled by -W...
compiler flag.
ClangSA checkers may rely on each other. For example unix.Malloc checker
may prevent a report from unix.MismatchedDeallocator. It is possible that
after disabling unix.Malloc the underlying analysis continues giving a
chance for unix.MismatchedDeallocator to emit a report.

In order to avoid this noisy behavior, ClangSA checkers are never
disabled during the analysis invocation. If CodeChecker wants to disable
a checker then its result should be hidden in a post-processing step.
Copy link
Collaborator

@Szelethus Szelethus left a comment

Choose a reason for hiding this comment

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

LGTM! While I've made my fair share of criticisms on some of the circumstances around this patch, overall, I'm satisfied with how it turned out. Great work!

@bruntib bruntib merged commit 27fac93 into Ericsson:master Sep 8, 2023
8 of 9 checks passed
@bruntib
Copy link
Contributor Author

bruntib commented Sep 8, 2023

Thank you so much for the valuable review!

@bruntib bruntib deleted the no_unknown_checker_state branch September 8, 2023 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analyzer 📈 Related to the analyze commands (analysis driver) clang sa 🐉 The Clang Static Analyzer is a source code analysis tool that finds bugs in C-family programs. clang-tidy 🐉 clang-tidy is a clang-based C++ “linter” tool. CLI 💻 Related to the command-line interface, such as the cmd, store, etc. commands cppcheck 🐞 CppCheck is a static analysis tool for C++ code enhancement 🌟
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants