1#!/usr/bin/env python3
2# coding: utf-8
3#
4# © 2023 Qualcomm Innovation Center, Inc. All rights reserved.
5#
6# SPDX-License-Identifier: BSD-3-Clause
7
8"""
9Run as a part of gitlab CI, after Parasoft reports have been generated.
10
11This script converts the Parasoft XML-format report to a Code Climate
12compatible json file, that gitlab code quality can interpret.
13"""
14
15import xml.etree.ElementTree as ET
16import json
17import argparse
18import sys
19import os
20import re
21
22argparser = argparse.ArgumentParser(
23    description="Convert Parasoft XML to Code Climate JSON")
24argparser.add_argument('input', type=argparse.FileType('r'), nargs='?',
25                       default=sys.stdin, help="the Parasoft XML input")
26argparser.add_argument('--output', '-o', type=argparse.FileType('w'),
27                       default=sys.stdout, help="the Code Climate JSON output")
28args = argparser.parse_args()
29
30tree = ET.parse(args.input)
31
32parasoft_viols = tree.findall(".//StdViol") + tree.findall(".//FlowViol")
33
34cc_viols = []
35
36severity_map = {
37    1: "blocker",
38    2: "critical",
39    3: "major",
40    4: "minor",
41    5: "info",
42}
43
44deviation_map = {
45    # Deviation because the behaviour proscribed by the rule is exactly the
46    # intended behaviour of assert(): it prints the unexpanded expression.
47    'MISRAC2012-RULE_20_12-a': [
48        (None, re.compile(r"parameter of potential macro 'assert'")),
49    ],
50    # False positives due to __c11 builtins taking int memory order arguments
51    # instead of enum in the Clang implementation.
52    'MISRAC2012-RULE_10_3-b': [
53        (None, re.compile(r"number '2'.*'essentially Enum'.*"
54                          r"'__c11_atomic_load'.*'essentially signed'")),
55        (None, re.compile(r"number '3'.*'essentially Enum'.*"
56                          r"'__c11_atomic_(store'|exchange'|fetch_).*"
57                          r"'essentially signed'")),
58        (None, re.compile(r"number '[45]'.*'essentially Enum'.*"
59                          r"'__c11_atomic_compare_exchange_(strong|weak)'.*"
60                          r"'essentially signed'")),
61    ],
62    # False positives with unknown cause: the return value of assert_if_const()
63    # is always used, to determine whether to call assert_failed()
64    'MISRAC2012-RULE_17_7-b': [
65        (None, re.compile(r'"assert_if_const"')),
66    ],
67    'MISRAC2012-RULE_8_7-a': [
68        # The could-be-static advisory rule is impractical to enforce for
69        # generated accessors, since the type system has no information about
70        # which accessors are used.
71        (re.compile(r'^build/.*/accessors\.c$'), None),
72        # The smccc module specifically has events that are only triggered by
73        # handlers for other events.
74        (re.compile(r'^build/.*/events/src/smccc\.c$'), None),
75        # The object module has type-specific APIs that are only used directly
76        # for some specific object types, and otherwise are called only by the
77        # type-generic APIs defined in the same file.
78        (re.compile(r'^build/.*/objects/.*\.c$'), None),
79    ],
80    # Invariant expressions are expected and unavoidable in generated event
81    # triggers because it is not possible to remove error result types from
82    # handlers that never return errors.
83    'MISRAC2012-RULE_14_3-ac': [
84        (re.compile(r'^build/.*/events/src/.*\.c$'), None),
85    ],
86    # Could-be-const pointers are expected and unavoidable in generated event
87    # triggers because the object may or may not be modified depending on the
88    # handlers and the module configuration. The const qualifier is used to
89    # specify whether the handlers are allowed to modify the objects, rather
90    # than whether they actually do.
91    'MISRAC2012-RULE_8_13-a': [
92        (re.compile(r'^build/.*/events/src/.*\.c$'), None),
93    ],
94    # The generated type-generic object functions terminate non-empty default
95    # clauses with a _Noreturn function, panic(), to indicate that the object
96    # type is invalid. There is an approved deviation for this, and in any
97    # case these rules are downgraded to advisory in generated code.
98    'MISRAC2012-RULE_16_1-d': [
99        (re.compile(r'^build/.*/objects/.*\.c$'), None),
100    ],
101    'MISRAC2012-RULE_16_3-b': [
102        (re.compile(r'^build/.*/objects/.*\.c$'), None),
103    ],
104    # False positive due to a builtin sizeof variant that does not evaluate its
105    # argument, so there is no uninitialised use.
106    'MISRAC2012-RULE_9_1-a': [
107        (None, re.compile(r'passed to "__builtin_object_size"')),
108    ],
109    'MISRAC2012-RULE_1_3-b': [
110        (None, re.compile(r'passed to "__builtin_object_size"')),
111    ],
112    # Deviation because casting a pointer to _Atomic to a pointer that can't be
113    # dereferenced at all (const void *) is reasonably safe, and is needed for
114    # certain builtin functions where the compiler knows the real underlying
115    # object type anyway (e.g. __builtin_object_size) or where the object type
116    # does not matter (e.g. __builtin_prefetch).
117    'MISRAC2012-RULE_11_8-a': [
118        (None, re.compile(r"to the 'const void \*' type which removes the "
119                          r"'_Atomic' qualifiers")),
120    ],
121    # Compliance with rule 21.25 would have a significant performance impact.
122    # All existing uses have been thoroughly analysed and tested, so we will
123    # seek a project-wide deviation for this rule.
124    'MISRAC2012-RULE_21_25-a': [
125        (None, None),
126    ],
127}
128
129
130def matches_deviation(v):
131    rule = v.attrib['rule']
132    if rule not in deviation_map:
133        return False
134
135    msg = v.attrib['msg']
136    path = v.attrib['locFile'].split(os.sep, 2)[2]
137
138    def check_constraint(constraint, value):
139        if constraint is None:
140            return True
141        try:
142            return constraint.search(value)
143        except AttributeError:
144            return constraint == value
145
146    for d_path, d_msg in deviation_map[rule]:
147        if check_constraint(d_path, path) and check_constraint(d_msg, msg):
148            return True
149
150    return False
151
152
153cc_viols = [
154    ({
155        "type": "issue",
156        "categories": ["Bug Risk"],
157        "severity": ('info' if matches_deviation(v)
158                     else severity_map[int(v.attrib['sev'])]),
159        "check_name": v.attrib['rule'],
160        "description": (v.attrib['msg'] + '. ' +
161                        v.attrib['rule.header'] + '. (' +
162                        v.attrib['rule'] + ')'),
163        "fingerprint": v.attrib['unbViolId'],
164        "location": {
165            "path": v.attrib['locFile'].split(os.sep, 2)[2],
166            "lines": {
167                "begin": int(v.attrib['locStartln']),
168                "end": int(v.attrib['locEndLn'])
169            }
170        }
171    })
172    for v in parasoft_viols]
173
174args.output.write(json.dumps(cc_viols))
175args.output.close()
176