Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion analyzer/codechecker_analyzer/analyzer_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def __init__(self):
lcfg_dict = self.__get_package_layout()
self.pckg_layout = lcfg_dict['runtime']

self._checker_labels = CheckerLabels(labels_dir)
self._guidelines = Guidelines(guidelines_dir)
self._checker_labels = CheckerLabels(labels_dir, self._guidelines)
self.__package_version = None
self.__package_build_date = None
self.__package_git_hash = None
Expand Down
37 changes: 22 additions & 15 deletions analyzer/codechecker_analyzer/cli/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,27 +190,32 @@ def __uglify(text: str) -> str:


def __guideline_to_label(
args: argparse.Namespace,
cl: CheckerLabels
args: argparse.Namespace
) -> str:
"""
Transforms --guideline parameter as if they were given through --label.
For example "--guideline sei-cert-c" is equivalent with
"--label guideline:sei-cert-c" and "--guideline sei-cert-c:str38-c" is the
same as "--label sei-cert-c:str38-c".
same as "--label rule:str38-c".

A guideline is derived in memory from the "rule:<rule_id>" labels of a
checker, so a whole-guideline query is expressed as a "guideline:<name>"
label and a single-rule query as a "rule:<rule_id>" label.
"""
guidelines = []
for analyzer in args.analyzers:
guidelines.extend(cl.occurring_values('guideline', analyzer))
guidelines = analyzer_context.get_context().guideline
all_guidelines = list(guidelines.all_guidelines())

if args.guideline in guidelines:
if args.guideline in all_guidelines:
return f'guideline:{args.guideline}'
elif args.guideline.find(':') == -1:
LOG.error('--guideline parameter is either <guideline> or '
'<guideline>:<rule>')
sys.exit(1)

return args.guideline
# "<guideline>:<rule>" form -> query by the rule only, since the guideline
# is derived from the rule in memory.
_, rule = args.guideline.split(':', 1)
return f'rule:{rule}'


def __get_detailed_checker_info(
Expand Down Expand Up @@ -256,7 +261,8 @@ def __get_detailed_checker_info(
profile_checkers.append((f'severity:{args.severity}', True))

if 'guideline' in args:
profile_checkers.append((__guideline_to_label(args, cl), True))
profile_checkers.append(
(__guideline_to_label(args), True))

config_handler.initialize_checkers(checkers, profile_checkers)

Expand Down Expand Up @@ -312,18 +318,19 @@ def __print_severities(args: argparse.Namespace, cl: CheckerLabels):
print(twodim.to_str(args.output_format, header, rows))


def __print_guidelines(args: argparse.Namespace, cl: CheckerLabels):
def __print_guidelines(args: argparse.Namespace):
"""
Print guidelines according to the command line arguments to the standard
output.
"""
if args.output_format == 'custom':
args.output_format = 'rows'

result = {}
guidelines = analyzer_context.get_context().guideline

for guideline in cl.get_description('guideline'):
result[guideline] = set(cl.occurring_values(guideline))
result = {}
for guideline in guidelines.all_guidelines():
result[guideline] = set(guidelines.rules_of_guideline(guideline))

header = ['Guideline', 'Rules']
if args.output_format in ['csv', 'json']:
Expand Down Expand Up @@ -468,7 +475,7 @@ def __print_checkers(args: argparse.Namespace, cl: CheckerLabels):
labels.append(f'profile:{args.profile}')

if 'guideline' in args:
labels.append(__guideline_to_label(args, cl))
labels.append(__guideline_to_label(args))

if 'severity' in args:
labels.append(f'severity:{args.severity}')
Expand Down Expand Up @@ -594,7 +601,7 @@ def main(args):
return

if 'guideline' in args and not args.guideline:
__print_guidelines(args, cl)
__print_guidelines(args)
return

if 'label' in args and not args.label:
Expand Down
71 changes: 50 additions & 21 deletions analyzer/tests/unit/test_checker_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
from codechecker_common.checker_labels import CheckerLabels


class _FakeGuidelines:
"""
Minimal stand-in for the Guidelines object. Maps rule ids to the
guidelines that contain them.
"""
def __init__(self, rule_to_guidelines):
self._rule_to_guidelines = rule_to_guidelines

def guidelines_of_rule(self, rule_id):
return list(self._rule_to_guidelines.get(rule_id, []))


class TestCheckerLabels(unittest.TestCase):
def setUp(self) -> None:
self.labels_dir = tempfile.TemporaryDirectory()
Expand All @@ -38,8 +50,9 @@ def initialize_labels_dir(self):
"MEDIUM": "Medium documentation",
"UNSPECIFIED": "Unspecified documentation"
},
"guideline": {
"sei-cert-c": "SEI-CERT C documentation"
"profile-containment": {
"extreme": ["sensitive"],
"sensitive": ["default"]
}
}

Expand All @@ -58,18 +71,14 @@ def initialize_labels_dir(self):
"severity:HIGH"
],
"core.DivideZero": [
"profile:default",
"profile:sensitive",
"severity:HIGH"
],
"core.NonNullParamChecker": [
"profile:default",
"profile:sensitive",
"severity:HIGH"
],
"core.builtin.NoReturnFunctions": [
"profile:default",
"profile:sensitive",
"profile:extreme",
"severity:MEDIUM"
],
Expand All @@ -89,20 +98,16 @@ def initialize_labels_dir(self):
"severity:HIGH"
],
"bugprone-undelegated-constructor": [
"profile:default",
"profile:sensitive",
"profile:extreme",
"severity:MEDIUM"
],
"google-objc-global-variable-declaration": [
"profile:extreme"
],
"cert-err34-c": [
"profile:sensitive",
"profile:security",
"profile:extreme",
"guideline:sei-cert-c",
"sei-cert-c:err34-c",
"profile:security",
"rule:err34-c",
"severity:LOW"
]
}
Expand All @@ -114,7 +119,8 @@ def initialize_labels_dir(self):
json.dump(labels, f)

def test_checker_labels(self):
cl = CheckerLabels(self.labels_dir.name)
guidelines = _FakeGuidelines({"err34-c": ["sei-cert-c"]})
cl = CheckerLabels(self.labels_dir.name, guidelines)

self.assertEqual(
sorted(cl.get_analyzers()),
Expand All @@ -123,15 +129,34 @@ def test_checker_labels(self):
"clangsa"
]))

# Query "extreme" expands to {extreme, sensitive, default}, so it
# matches every checker with any containment tier.
self.assertEqual(
sorted(cl.checkers_by_labels([
'profile:extreme'])),
sorted([
'core.DivideZero',
'core.NonNullParamChecker',
'core.builtin.NoReturnFunctions',
'bugprone-undelegated-constructor',
'google-objc-global-variable-declaration',
'cert-err34-c']))

# Query "sensitive" expands to {sensitive, default}: extreme-labeled
# checkers are NOT matched.
self.assertEqual(
sorted(cl.checkers_by_labels([
'profile:sensitive'], 'clangsa')),
sorted([
'core.DivideZero',
'core.NonNullParamChecker']))

# Query "default" matches only default-labeled checkers (smallest
# set). The test data has none in clangsa.
self.assertEqual(
sorted(cl.checkers_by_labels(['profile:default'], 'clangsa')),
[])

self.assertEqual(
sorted(cl.checkers_by_labels([
'profile:extreme',
Expand Down Expand Up @@ -164,6 +189,18 @@ def test_checker_labels(self):
cl.label_of_checker('globalChecker', 'profile'),
['security'])

# label_of_checker returns the checker's single containment tier;
# the containment relation is applied on the query side, not here.
self.assertEqual(
cl.label_of_checker(
'core.builtin.NoReturnFunctions', 'profile', 'clangsa'),
['extreme'])

# Guideline derived from the rule label.
self.assertEqual(
cl.label_of_checker('cert-err34-c', 'guideline', 'clang-tidy'),
['sei-cert-c'])

self.assertEqual(
cl.label_of_checker(
'bugprone-undelegated-constructor', 'severity', 'clang-tidy'),
Expand All @@ -180,14 +217,6 @@ def test_checker_labels(self):
('profile', 'security'),
('severity', 'HIGH')]))

self.assertEqual(
sorted(cl.labels()),
sorted(['guideline', 'profile', 'sei-cert-c', 'severity']))

self.assertEqual(
sorted(cl.occurring_values('profile')),
sorted(['default', 'extreme', 'security', 'sensitive']))

self.assertEqual(
cl.severity('bugprone-undelegated-constructor'),
'MEDIUM')
Expand Down
Loading
Loading