1#!/usr/bin/env python3
2
3import os, re
4from . import settings
5from xml.etree import ElementTree
6
7class CppcheckHTMLReportError(Exception):
8    pass
9
10class CppcheckTXTReportError(Exception):
11    pass
12
13
14def __elements_equal(el1, el2):
15    if type(el1) != type(el2): return False
16
17    if el1.find('location') is None: return False
18    if el2.find('location') is None: return False
19
20    el1_location = str(el1.find('location').attrib)
21    el2_location = str(el2.find('location').attrib)
22
23    if el1_location != el2_location: return False
24
25    return True
26
27
28def __contain_element(new, lst):
29    for elem in lst:
30        if __elements_equal(new, elem):
31            return True
32    return False
33
34
35def __get_xml_root_file(filename):
36    try:
37        result_xml_root = ElementTree.parse(filename).getroot()
38    except ElementTree.ParseError as e:
39        raise CppcheckHTMLReportError(
40                    "XML parsing error in {}: {}".format(filename, e)
41                )
42    return result_xml_root
43
44
45def __sanitize_cppcheck_xml_path(xml_tree, src_path, obj_path):
46    # Some path are relative to the source tree but some others are generated
47    # in the obj tree, for cppcheck when using cppcheck-htmlreport we can pass
48    # only one source tree where the files will be fetched if relative path are
49    # found. So for every path that does not exists in src tree, we guess it
50    # comes from obj tree and we put explicit absolute path to it
51    error_item_root = xml_tree.findall("errors")[0]
52    for error_item in error_item_root:
53        for location_item in error_item.findall("location"):
54            path = location_item.attrib["file"]
55            new_obj_path = obj_path + "/" + path
56            new_src_path = src_path + "/" + path
57            if (path[0] != "/") and (not os.path.isfile(new_src_path)) \
58               and os.path.isfile(new_obj_path):
59                location_item.attrib["file"] = new_obj_path
60
61
62def cppcheck_merge_xml_fragments(fragments_list, out_xml_file, src_path,
63                                 obj_path):
64
65    result_xml = __get_xml_root_file(fragments_list[0])
66    insert_point = result_xml.findall("errors")[0]
67    for xml_file in fragments_list[1:]:
68        xml_root = __get_xml_root_file(xml_file)
69        curr_elem_list = list(insert_point)
70        new_elem_list = list(xml_root.findall("errors")[0])
71        for xml_error_elem in new_elem_list:
72            if not __contain_element(xml_error_elem, curr_elem_list):
73                insert_point.insert(1, xml_error_elem)
74
75    if result_xml is None:
76        return False
77
78    __sanitize_cppcheck_xml_path(result_xml, src_path, obj_path)
79
80    ElementTree.ElementTree(result_xml).write(out_xml_file)
81
82    return True
83
84
85def cppcheck_merge_txt_fragments(fragments_list, out_txt_file, strip_paths):
86    try:
87        with open(out_txt_file, "wt") as outfile:
88            # Using a set will remove automatically the duplicate lines
89            text_report_content = set()
90            for file in fragments_list:
91                try:
92                    with open(file, "rt") as infile:
93                        frag_lines = infile.readlines()
94                except OSError as e:
95                    raise CppcheckTXTReportError(
96                            "Issue with reading file {}: {}"
97                                .format(file, e)
98                            )
99                text_report_content.update(frag_lines)
100
101            # Back to modifiable list
102            text_report_content = list(text_report_content)
103            # Strip path from report lines
104            for i in list(range(0, len(text_report_content))):
105                # Split by : separator
106                text_report_content[i] = text_report_content[i].split(":")
107
108                for path in strip_paths:
109                    text_report_content[i][0] = \
110                        text_report_content[i][0].replace(path + "/", "")
111
112                # When the compilation is in-tree, the makefile places
113                # the directory in /xen/xen, making cppcheck produce
114                # relative path from there, so check if "xen/" is a prefix
115                # of the path and if it's not, check if it can be added to
116                # have a relative path from the repository instead of from
117                # /xen/xen
118                if not text_report_content[i][0].startswith("xen/"):
119                    # cppcheck first entry is in this format:
120                    # path/to/file(line,cols), remove (line,cols)
121                    cppcheck_file = re.sub(r'\(.*\)', '',
122                                           text_report_content[i][0])
123                    if os.path.isfile(settings.xen_dir + "/" + cppcheck_file):
124                        text_report_content[i][0] = \
125                            "xen/" + text_report_content[i][0]
126
127            # sort alphabetically for second field (misra rule) and as second
128            # criteria for the first field (file name)
129            text_report_content.sort(key = lambda x: (x[1], x[0]))
130            # merge back with : separator
131            text_report_content = [":".join(x) for x in text_report_content]
132            # Write the final text report
133            outfile.writelines(text_report_content)
134    except OSError as e:
135        raise CppcheckTXTReportError("Issue with writing file {}: {}"
136                                            .format(out_txt_file, e))
137
138
139def cppcheck_strip_path_html(html_files, strip_paths):
140    for file in html_files:
141        try:
142            with open(file, "rt") as infile:
143                html_lines = infile.readlines()
144        except OSError as e:
145            raise CppcheckHTMLReportError("Issue with reading file {}: {}"
146                                                            .format(file, e))
147        for i in list(range(0, len(html_lines))):
148            for path in strip_paths:
149                html_lines[i] = html_lines[i].replace(path + "/", "")
150        try:
151            with open(file, "wt") as outfile:
152                outfile.writelines(html_lines)
153        except OSError as e:
154            raise CppcheckHTMLReportError("Issue with writing file {}: {}"
155                                                            .format(file, e))
156