1#!/usr/bin/env python
2
3"""
4This script is converting the misra documentation RST file into a text file
5that can be used as text-rules for cppcheck.
6Usage:
7    convert_misra_doc.py -i INPUT [-o OUTPUT] [-j JSON] [-s RULES,[...,RULES]]
8
9    INPUT  - RST file containing the list of misra rules.
10    OUTPUT - file to store the text output to be used by cppcheck.
11             If not specified, the result will be printed to stdout.
12    JSON   - cppcheck json file to be created (optional).
13    RULES  - list of rules to skip during the analysis, comma separated
14             (e.g. 1.1,1.2,1.3,...)
15"""
16
17import sys, getopt, re
18
19# MISRA rule are identified by two numbers, e.g. Rule 1.2, the main rule number
20# and a sub-number. This dictionary contains the number of the MISRA rule as key
21# and the maximum sub-number for that rule as value.
22misra_c2012_rules = {
23    1:4,
24    2:7,
25    3:2,
26    4:2,
27    5:9,
28    6:2,
29    7:4,
30    8:14,
31    9:5,
32    10:8,
33    11:9,
34    12:5,
35    13:6,
36    14:4,
37    15:7,
38    16:7,
39    17:8,
40    18:8,
41    19:2,
42    20:14,
43    21:21,
44    22:10
45}
46
47def main(argv):
48    infile = ''
49    outfile = ''
50    outstr = sys.stdout
51    jsonfile = ''
52    force_skip = ''
53
54    try:
55        opts, args = getopt.getopt(argv,"hi:o:j:s:",
56                                   ["input=","output=","json=","skip="])
57    except getopt.GetoptError:
58        print('convert-misra.py -i <input> [-o <output>] [-j <json>] [-s <rules>]')
59        sys.exit(2)
60    for opt, arg in opts:
61        if opt == '-h':
62            print('convert-misra.py -i <input> [-o <output>] [-j <json>] [-s <rules>]')
63            print('  If output is not specified, print to stdout')
64            sys.exit(1)
65        elif opt in ("-i", "--input"):
66            infile = arg
67        elif opt in ("-o", "--output"):
68            outfile = arg
69        elif opt in ("-s", "--skip"):
70            force_skip = arg
71        elif opt in ("-j", "--json"):
72            jsonfile = arg
73
74    try:
75        file_stream = open(infile, 'rt')
76    except:
77        print('Error opening ' + infile)
78        sys.exit(1)
79
80    if outfile:
81        try:
82            outstr = open(outfile, "w")
83        except:
84            print('Error creating ' + outfile)
85            sys.exit(1)
86
87    # Each rule start with '   * - `[Dir|Rule]' and is followed by the
88    # severity, the summary and then notes
89    # Only the summary can be multi line
90    pattern_dir = re.compile(r'^   \* - `Dir ([0-9]+.[0-9]+).*$')
91    pattern_rule = re.compile(r'^   \* - `Rule ([0-9]+.[0-9]+).*$')
92    pattern_col = re.compile(r'^     - (.*)$')
93    # allow empty notes
94    pattern_notes = re.compile(r'^     -.*$')
95    pattern_cont = re.compile(r'^      (.*)$')
96
97    rule_number = ''
98    rule_severity = ''
99    rule_summary = ''
100    rule_state  = 0
101    rule_list = []
102
103    # Start search by cppcheck misra
104    outstr.write('Appendix A Summary of guidelines\n')
105
106    for line in file_stream:
107
108        line = line.replace('\r', '').replace('\n', '')
109
110        if len(line) == 0:
111            continue
112
113        # New Rule or Directive
114        if rule_state == 0:
115            # new Rule
116            res = pattern_rule.match(line)
117            if res:
118                rule_number = res.group(1)
119                rule_list.append(rule_number)
120                rule_state = 1
121                continue
122
123            # new Directive
124            res = pattern_dir.match(line)
125            if res:
126                rule_number = res.group(1)
127                rule_list.append(rule_number)
128                rule_state = 1
129                continue
130            continue
131
132        # Severity
133        elif rule_state == 1:
134            res =pattern_col.match(line)
135            if res:
136                rule_severity = res.group(1)
137                rule_state = 2
138                continue
139
140            print('No severity for rule ' + rule_number)
141            sys.exit(1)
142
143        # Summary
144        elif rule_state == 2:
145            res = pattern_col.match(line)
146            if res:
147                rule_summary = res.group(1)
148                rule_state = 3
149                continue
150
151            print('No summary for rule ' + rule_number)
152            sys.exit(1)
153
154        # Notes or summary continuation
155        elif rule_state == 3:
156            res = pattern_cont.match(line)
157            if res:
158                rule_summary += res.group(1)
159                continue
160            res = pattern_notes.match(line)
161            if res:
162                outstr.write('Rule ' + rule_number + ' ' + rule_severity
163                             + '\n')
164                outstr.write(rule_summary + ' (Misra rule ' + rule_number
165                             + ')\n')
166                rule_state = 0
167                rule_number = ''
168                continue
169            print('No notes for rule ' + rule_number)
170            sys.exit(1)
171
172        else:
173            print('Impossible case in state machine')
174            sys.exit(1)
175
176    skip_list = []
177
178    # Add rules to be skipped anyway
179    for r in force_skip.split(','):
180        skip_list.append(r)
181
182    # Search for missing rules and add a dummy text with the rule number
183    for i in misra_c2012_rules:
184        for j in list(range(1,misra_c2012_rules[i]+1)):
185            rule_str = str(i) + '.' + str(j)
186            if (rule_str not in rule_list) and (rule_str not in skip_list):
187                outstr.write('Rule ' + rule_str + '\n')
188                outstr.write('No description for rule ' + rule_str + '\n')
189                skip_list.append(rule_str)
190
191    # Make cppcheck happy by starting the appendix
192    outstr.write('Appendix B\n')
193    outstr.write('\n')
194    if outfile:
195        outstr.close()
196
197    if jsonfile:
198        with open(jsonfile, "w") as f:
199            f.write('{\n')
200            f.write('    "script": "misra.py",\n')
201            f.write('    "args": [\n')
202            if outfile:
203                f.write('      "--rule-texts=' + outfile + '",\n')
204
205            f.write('      "--suppress-rules=' + ",".join(skip_list) + '"\n')
206            f.write('    ]\n')
207            f.write('}\n')
208        f.close()
209
210if __name__ == "__main__":
211   main(sys.argv[1:])
212