Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
cppcheck-htmlreport1158 linesDownload Raw Back to bin
1#!/usr/bin/env python32 3from __future__ import unicode_literals4 5from datetime import date6import argparse7import io8import locale9import operator10import os11import re12import sys13import subprocess14import time15 16from collections import Counter17from pygments import highlight18from pygments.lexers import guess_lexer, guess_lexer_for_filename19from pygments.formatters import HtmlFormatter  # pylint: disable=no-name-in-module20from pygments.util import ClassNotFound21from xml.sax import parse as xml_parse22from xml.sax import SAXParseException as XmlParseException23from xml.sax.handler import ContentHandler as XmlContentHandler24from xml.sax.saxutils import escape25"""26Turns a cppcheck xml file into a browsable html report along27with syntax highlighted source code.28"""29 30STYLE_FILE = """31body {32    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;33    font-size: 13px;34    line-height: 1.5;35    height: 100%;36    margin: 0;37}38 39#wrapper {40    position: fixed;41    height: 100vh;42    width: 100vw;43    display: grid;44    grid-template-rows: fit-content(8rem) auto fit-content(8rem);45    grid-template-columns: fit-content(25%) 1fr;46    grid-template-areas:47      "header header"48      "menu content"49      "footer footer";50}51 52h1 {53    margin: 0 0 8px -2px;54    font-size: 175%;55}56 57.header {58    padding: 0 0 5px 15px;59    grid-area: header;60    border-bottom: thin solid #aaa;61}62 63.footer {64    grid-area: footer;65    border-top: thin solid #aaa;66    font-size: 85%;67 68}69 70.footer > p {71    margin: 4px;72}73 74#menu,75#menu_index {76    grid-area: menu;77    text-align: left;78    overflow: auto;79    padding: 0 23px 15px 15px;80    border-right: thin solid #aaa;81    min-width: 200px;82}83 84#menu > a {85    display: block;86    margin-left: 10px;87    font-size: 12px;88}89 90#content,91#content_index {92    grid-area: content;93    padding: 0px 5px 15px 15px;94    overflow: auto;95}96 97label {98    white-space: nowrap;99}100 101label.checkBtn.disabled {102    color: #606060;103    background: #e0e0e0;104    font-style: italic;105}106 107label.checkBtn, input[type="text"] {108    border: 1px solid grey;109    border-radius: 4px;110    box-shadow: 1px 1px inset;111    padding: 1px 5px;112}113 114label.checkBtn {115    white-space: nowrap;116    background: #ccddff;117}118 119label.unchecked {120    background: #eff8ff;121    box-shadow: 1px 1px 1px;122}123 124label.checkBtn:hover, label.unchecked:hover{125    box-shadow: 0 0 2px;126}127 128label.disabled:hover {129    box-shadow: 1px 1px inset;130}131 132label.checkBtn > input {133    display:none;134}135 136.summaryTable {137    width: 100%;138}139 140table.summaryTable td { padding: 0 5px 0 5px; }141 142.statHeader, .severityHeader, .classificationHeader {143    font-weight: bold;144}145 146.warning {147    background-color: #ffffa7;148}149 150.error {151    background-color: #ffb7b7;152}153 154.error2 {155    background-color: #faa;156    display: inline-block;157    margin-left: 4px;158}159 160.inconclusive {161    background-color: #b6b6b4;162}163 164.inconclusive2 {165    background-color: #b6b6b4;166    display: inline-block;167    margin-left: 4px;168}169 170.verbose {171    display: inline-block;172    vertical-align: top;173    cursor: help;174}175 176.verbose .content {177    display: none;178    position: absolute;179    padding: 10px;180    margin: 4px;181    max-width: 40%;182    white-space: pre-wrap;183    border: 1px solid #000;184    background-color: #ffffcc;185    cursor: auto;186}187 188.highlight .hll {189    padding: 1px;190}191 192.highlighttable {193    background-color: #fff;194    position: relative;195    margin: -10px;196}197 198.linenos {199    border-right: thin solid #aaa;200    color: #d3d3d3;201    padding-right: 6px;202}203 204.id-filtered, .severity-filtered, .classification-filtered, .file-filtered, .tool-filtered, .text-filtered {205    visibility: collapse;206}207"""208 209HTML_HEAD = """210<!doctype html>211<html lang="en">212  <head>213    <meta charset="utf-8">214    <title>Cppcheck - HTML report - %s</title>215    <link rel="stylesheet" href="style.css">216    <style>217%s218    </style>219    <script>220      function getStyle(el, styleProp) {221        var y;222 223        if (el.currentStyle) {224          y = el.currentStyle[styleProp];225        } else if (window.getComputedStyle) {226          y = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);227        }228 229        return y;230      }231 232      function toggle() {233        var el = this.expandable_content;234        var mark = this.expandable_marker;235 236        if (el.style.display === "block") {237          el.style.display = "none";238          mark.textContent = "[+]";239        } else {240          el.style.display = "block";241          mark.textContent = "[-]";242        }243      }244 245      function initExpandables() {246        var elements = document.querySelectorAll(".expandable");247 248        for (var i = 0, len = elements.length; i < len; i++) {249          var el = elements[i];250          var clickable = el.querySelector("span");251          var marker = clickable.querySelector(".marker");252          var content = el.querySelector(".content");253          var width = clickable.clientWidth - parseInt(getStyle(content, "padding-left")) - parseInt(getStyle(content, "padding-right"));254          content.style.width = width + "px";255          clickable.expandable_content = content;256          clickable.expandable_marker = marker;257          clickable.addEventListener("click", toggle);258        }259      }260 261      function toggleDisplay(cb) {262        var elements = document.querySelectorAll("." + cb.id);263 264        for (var i = 0, len = elements.length; i < len; i++) {265          elements[i].classList.toggle("id-filtered", !cb.checked);266        }267 268        updateFileRows();269      }270 271      function toggleSeverity(cb) {272        cb.parentElement.classList.toggle("unchecked", !cb.checked);273        var elements = document.querySelectorAll(".sev_" + cb.id);274 275        for (var i = 0, len = elements.length; i < len; i++) {276          elements[i].classList.toggle("severity-filtered", !cb.checked);277        }278 279        updateFileRows();280      }281      282      function toggleClassification(cb) {283        cb.parentElement.classList.toggle("unchecked", !cb.checked);284        var elements = document.querySelectorAll(".class_" + cb.id);285 286        for (var i = 0, len = elements.length; i < len; i++) {287          elements[i].classList.toggle("classification-filtered", !cb.checked);288        }289 290        updateFileRows();291      }292    293      function toggleTool(cb) {294        cb.parentElement.classList.toggle("unchecked", !cb.checked);295 296        var elements;297        if (cb.id == "clang-tidy")298            elements = document.querySelectorAll("[class^=clang-tidy-]");299        else300            elements = document.querySelectorAll(".issue:not([class^=clang-tidy-])");301 302        for (var i = 0, len = elements.length; i < len; i++) {303          elements[i].classList.toggle("tool-filtered", !cb.checked);304        }305 306        updateFileRows();307      }308 309      function toggleAll() {310        var elements = document.querySelectorAll(".idToggle");311 312        // starting from 1 since 0 is the "toggle all" input313        for (var i = 1, len = elements.length; i < len; i++) {314          var changed = elements[i].checked != elements[0].checked;315          if (changed) {316            elements[i].checked = elements[0].checked;317            toggleDisplay(elements[i]);318          }319        }320      }321 322      function filterFile(filter) {323        var elements = document.querySelectorAll(".fileEntry");324 325        for (var i = 0, len = elements.length; i < len; i++) {326          var visible = elements[i].querySelector("tr").querySelector("td").textContent.toLowerCase().includes(filter.toLowerCase());327          elements[i].classList.toggle("text-filtered", !visible);328        }329      }330 331      function filterText(text) {332        filter = text.toLowerCase();333        var elements = document.querySelectorAll(".issue");334 335        for (var i = 0, len = elements.length; i < len; i++) {336          var visible = false;337          var fields = elements[i].querySelectorAll("td");338          for (var n = 0, num = fields.length; n < num; n++) {339            if (fields[n].textContent.toLowerCase().includes(filter)) {340              visible = true;341              break;342            }343          }344          elements[i].classList.toggle("text-filtered", !visible);345        }346 347        updateFileRows();348      }349 350      function updateFileRows(element) {351        var elements = document.querySelectorAll(".fileEntry");352 353        for (var i = 0, len = elements.length; i < len; i++) {354          var visible = elements[i].querySelector(".issue:not(.id-filtered):not(.severity-filtered):not(.classification-filtered):not(.tool-filtered):not(.text-filtered)");355          elements[i].classList.toggle("file-filtered", !visible);356        }357      }358 359      function reapplyFilters() {360        // Reapply ID filters361        var idToggles = document.querySelectorAll(".idToggle");362        for (var i = 1; i < idToggles.length; i++) {363          var cb = idToggles[i];364          var elements = document.querySelectorAll("." + cb.id);365          for (var j = 0; j < elements.length; j++) {366            elements[j].classList.toggle("id-filtered", !cb.checked);367          }368        }369 370        // Reapply severity filters371        var sevToggles = document.querySelectorAll(".sev_toggle");372        for (var i = 0; i < sevToggles.length; i++) {373          var cb = sevToggles[i];374            var elements = document.querySelectorAll(".sev_" + cb.id);375          for (var j = 0; j < elements.length; j++) {376            elements[j].classList.toggle("severity-filtered", !cb.checked);377          }378        }379 380        // Reapply classification filters381        var classToggles = document.querySelectorAll(".class_toggle");382        for (var i = 0; i < classToggles.length; i++) {383          var cb = classToggles[i];384          var elements = document.querySelectorAll(".class_" + cb.id);385          for (var j = 0; j < elements.length; j++) {386            elements[j].classList.toggle("classification-filtered", !cb.checked);387          }388        }389 390        // Reapply tool filters391        var toolToggles = document.querySelectorAll(".tool_toggle");392        for (var i = 0; i < toolToggles.length; i++) {393          var cb = toolToggles[i];394          var elements;395          if (cb.id == "clang-tidy")396            elements = document.querySelectorAll("[class^=clang-tidy-]");397          else398            elements = document.querySelectorAll(".issue:not([class^=clang-tidy-])");399 400          for (var j = 0; j < elements.length; j++) {401            elements[j].classList.toggle("tool-filtered", !cb.checked);402          }403        }404 405        // Update file rows406        updateFileRows();407      }408 409      window.addEventListener("pageshow", reapplyFilters);410      window.addEventListener("load", initExpandables);411    </script>412  </head>413  <body>414    <div id="wrapper">415    <div id="header" class="header">416      <h1>Cppcheck report - %s%s</h1>417"""418 419HTML_HEAD_END = """420    </div>421"""422 423HTML_MENU = """424    <div id="menu">425     <p><a href="index.html">Defects:</a> %s</p>426"""427 428HTML_MENU_END = """429    </div>430    <div id="content">431"""432 433HTML_FOOTER = """434    </div>435    <div id="footer" class="footer">436      <p>437        Created by Cppcheck %s (<a href="https://cppcheck.sourceforge.io">Sourceforge</a>, <a href="irc://irc.libera.chat/#cppcheck">IRC</a>)438      </p>439    </div>440    </div>441  </body>442</html>443"""444 445HTML_ERROR = "<span class=\"error2\">&lt;--- %s</span>\n"446HTML_INCONCLUSIVE = "<span class=\"inconclusive2\">&lt;--- %s</span>\n"447 448HTML_EXPANDABLE_ERROR = "<div class=\"verbose expandable\"><span class=\"error2\">&lt;--- %s <span class=\"marker\">[+]</span></span><div class=\"content\">%s</div></div>\n"449HTML_EXPANDABLE_INCONCLUSIVE = "<div class=\"verbose expandable\"><span class=\"inconclusive2\">&lt;--- %s <span class=\"marker\">[+]</span></span><div class=\"content\">%s</div></div>\n"450 451# escape() and unescape() takes care of &, < and >.452html_escape_table = {453    '"': "&quot;",454    "'": "&apos;"455}456html_unescape_table = {v: k for k, v in html_escape_table.items()}457 458 459def html_escape(text):460    return escape(text, html_escape_table)461 462def filter_button(enabled_filters, id, function):463    enabled = enabled_filters.get(id, False)464    if not id:465        id = 'None'466    return '\n        <label class="checkBtn%s"><input type="checkbox" onclick="%s(this)" id="%s" %s>%s</label>'\467          % (' disabled' if not enabled else '', function, id, 'checked' if enabled else 'disabled', id)468 469def filter_bar(enabled):470    severity_bar = ''.join([filter_button(enabled, severity, 'toggleSeverity') for severity in ['error', 'warning', 'portability', 'performance', 'style', 'information']]) + '\n        | '471    classification_bar = ''.join([filter_button(enabled, _class, 'toggleClassification') for _class in ['Mandatory', 'Required', 'Advisory', 'Document', 'Disapplied', 'L1','L2','L3','']]) + '\n        | '472    if "checked/>" not in severity_bar:473        severity_bar = ''474    if "checked/>" not in classification_bar:475        classification_bar = ''476    return ''.join([477     '      <div id="filters">\n'478    ,severity_bar479    , classification_bar480    ,''.join([filter_button(enabled, tool, 'toggleTool') for tool in ['cppcheck', 'clang-tidy']])481    ,'\n        | '482    ,'\n        <label class="severityHeader">File: <input type="search" oninput="filterFile(this.value)"></label>'483    ,'\n        <label class="severityHeader">Filter: <input type="search" oninput="filterText(this.value)"></label>'484    ,'\n      </div>\n'])485def git_blame(errors, path, file, blame_options):486    last_line= errors[-1]['line']487    if last_line == 0:488        return {}489 490    first_line = next((error for error in errors if error['line'] > 0))['line']491 492    full_path = os.path.join(path, file)493    path, filename = os.path.split(full_path)494 495    if not path:496        path = None  # make sure this is None497 498    cmd_args = ['git', 'blame', '-L %d,%d' % (first_line, last_line)]499    if '-w' in blame_options:500        cmd_args.append('-w')501    if '-M' in blame_options:502        cmd_args.append('-M')503    cmd_args = cmd_args + ['--porcelain', '--incremental', '--', filename]504 505    try:506        result = subprocess.check_output(cmd_args, cwd=path)507        result = result.decode(locale.getpreferredencoding())508    except:509        return []510 511    if result.startswith('fatal'):512        return []513 514    blame_data = []515    line_from = 0516    line_to = 0517    blame_info = {}518 519    for line_str in result.splitlines():520        field, _, value = line_str.partition(' ')521        if len(field) == 40:522            line_nr, count = value.split(' ')[1:]523            line_from = int(line_nr)524            line_to = line_from + int(count)525        elif field.startswith('author'):526            blame_info[field] = value527            if field == 'author-time':528                author_date = date.fromtimestamp(int(blame_info['author-time']))529                blame_info['author-time'] = author_date.strftime("%d/%m/%Y")530        elif field == 'filename':531            blame_data.append([line_from, line_to, blame_info.copy()])532 533    return blame_data534 535 536def blame_lookup(blame_data, line):537    return next((data for start, end, data in blame_data if line >= start and line < end), {})538 539 540def tr_str(td_th, line, id, cwe, severity, classification, guideline, message, timestamp, author, author_mail, date, add_author, tr_class=None, htmlfile=None, message_class=None):541    ret = ''542    items = [id, cwe]543    if severity:544        items.append(severity)545    if classification:546        items.extend([classification, guideline])547    if htmlfile:548        if htmlfile.startswith("http://") or htmlfile.startswith("https://"):549            # GitHub/GitLab style line anchor550            href = f"{htmlfile.rstrip('#L1')}#L{line}"551            # Emit **line number with link**552            ret += f'<{td_th}><a href="{href}" target="_blank" rel="noopener noreferrer">{line}</a></{td_th}>'553        else:554            # local HTML annotated555            href = f"{htmlfile}#line-{line}"556            # Emit **line number with link**557            ret += f'<{td_th}><a href="{href}">{line}</a></{td_th}>'558 559        # Emit id, cwe, severity, classification, ...560        for item in items:561            ret += f'<{td_th}>{item}</{td_th}>'562    else:563        items.insert(0,line)564        for item in items:565            ret += '<%s>%s</%s>' % (td_th, item, td_th)566    if message_class:567        message_attribute = ' class="%s"' % message_class568    else:569        message_attribute = ''570    ret += '<%s%s>%s</%s>' % (td_th, message_attribute, html_escape(message), td_th)571 572    if timestamp:573        ret += '<%s>%s</%s>' % (td_th, timestamp, td_th)574 575    for field in  add_author:576        if field == 'name':577            ret += '<%s>%s</%s>' % (td_th, html_escape(author), td_th)578        elif field == 'email':579            ret += '<%s>%s</%s>' % (td_th, html_escape(author_mail), td_th)580        elif field == 'date':581            ret += '<%s>%s</%s>' % (td_th, date, td_th)582 583    if tr_class:584        tr_attributes = ' class="%s"' % tr_class585    else:586        tr_attributes = ''587    return '<tr%s>%s</tr>' % (tr_attributes, ret)588 589 590def to_css_selector(tag):591    # https://www.w3.org/TR/CSS2/syndata.html#characters592    # Note: for our usage, we don't consider escaped characters593    invalid_css_chars = re.compile(r"[^-_a-zA-Z0-9\u00A0-\uFFFF]")594    invalid_start = re.compile(r"^([0-9]|-[0-9]|--)")595    # Replace forbidden characters by an hyphen596    valid_chars = invalid_css_chars.sub("-", tag)597    # Check that the start of the tag doesn't break the rules598    start_invalid = invalid_start.match(valid_chars)599    if start_invalid:600        # otherwise, append a token to make it valid601        valid_chars_valid_start = "cpp" + valid_chars602    else:603        valid_chars_valid_start = valid_chars604    return valid_chars_valid_start605 606class AnnotateCodeFormatter(HtmlFormatter):607    errors = []608 609    def wrap(self, *args, **kwargs):610        line_no = 1611        for i, t in HtmlFormatter.wrap(self, *args, **kwargs):612            # If this is a source code line we want to add a span tag at the613            # end.614            if i == 1:615                for error in self.errors:616                    if error['line'] == line_no:617                        try:618                            if error['inconclusive'] == 'true':619                                # only print verbose msg if it really differs620                                # from actual message621                                if error.get('verbose') and (error['verbose'] != error['msg']):622                                    index = t.rfind('\n')623                                    t = t[:index] + HTML_EXPANDABLE_INCONCLUSIVE % (error['msg'], html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:]624                                else:625                                    t = t.replace('\n', HTML_INCONCLUSIVE % error['msg'])626                        except KeyError:627                            if error.get('verbose') and (error['verbose'] != error['msg']):628                                index = t.rfind('\n')629                                t = t[:index] + HTML_EXPANDABLE_ERROR % (error['msg'], html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:]630                            else:631                                t = t.replace('\n', HTML_ERROR % error['msg'])632 633                line_no = line_no + 1634            yield i, t635 636 637class CppCheckHandler(XmlContentHandler):638 639    """Parses the cppcheck xml file and produces a list of all its errors."""640 641    def __init__(self):642        XmlContentHandler.__init__(self)643        self.errors = []644        self.version = '1'645        self.versionCppcheck = ''646        self.timestamp = ''647        self.report_type = False648 649    def startElement(self, name, attributes):650        if name == 'results':651            self.version = attributes.get('version', self.version)652 653        if self.version == '1':654            self.handleVersion1(name, attributes)655        else:656            self.handleVersion2(name, attributes)657 658    def handleVersion1(self, name, attributes):659        if name != 'error':660            return661        att_class = attributes.get('classification', '')662        att_guide = attributes.get('guideline', '')663        if att_class and not self.report_type:664            self.report_type = True665        self.errors.append({666            'file': attributes.get('file', ''),667            'line': int(attributes.get('line', 0)),668            'locations': [{669                'file': attributes.get('file', ''),670                'line': int(attributes.get('line', 0)),671            }],672            'id': attributes['id'],673            'severity': attributes['severity'],674            'classification': att_class,675            'guideline': att_guide,676            'timestamp': self.timestamp,677            'msg': attributes['msg']678        })679 680    def handleVersion2(self, name, attributes):681        if name == 'cppcheck':682            self.versionCppcheck = attributes['version']683        if name == 'error':684            att_class = attributes.get('classification', '')685            att_guide = attributes.get('guideline', '')686            if att_class and not self.report_type:687                self.report_type = True688            error = {689                'locations': [],690                'file': '',691                'line': 0,692                'id': attributes['id'],693                'classification': att_class,694                'severity': attributes['severity'],695                'guideline': att_guide,696                'timestamp': self.timestamp,697                'msg': attributes['msg'],698                'verbose': attributes.get('verbose')699            }700 701            if 'inconclusive' in attributes:702                error['inconclusive'] = attributes['inconclusive']703            if 'cwe' in attributes:704                error['cwe'] = attributes['cwe']705 706            self.errors.append(error)707        elif name == 'location':708            assert self.errors709            error = self.errors[-1]710            locations = error['locations']711            file = attributes['file']712            line = int(attributes['line'])713            if not locations:714                error['file'] = file715                error['line'] = line716            locations.append({717                'file': file,718                'line': line,719                'info': attributes.get('info')720            })721def main() -> None:722    # Configure all the options this little utility is using.723    parser = argparse.ArgumentParser()724    parser.add_argument('--title', dest='title',725                        help='The title of the project.',726                        default='[project name]')727    parser.add_argument('--file', dest='file', action="append",728                        help='The cppcheck xml output file to read defects '729                             'from. You can combine results from several '730                             'xml reports i.e. "--file file1.xml --file file2.xml ..". '731                             'Default is reading from stdin.')732    parser.add_argument('--checkers-report-file', dest='checkers_report',733                      help='The cppcheck checkers report file as produced '734                           'with the "--checkers-report" option of cppcheck.')735    parser.add_argument('--report-dir', dest='report_dir',736                         help='The directory where the HTML report content is '737                              'written.')738    parser.add_argument('--source-dir', dest='source_dir',739                         help='Base directory where source code files can be '740                              'found, or a URL to a remote GitHub/GitLab '741                              'repository including a branch, e.g. '742                              '--source-dir=https://github.com/<username>/<repo>/blob/<branch>/')743    parser.add_argument('--add-author-information', dest='add_author_information',744                        help='Blame information to include. '745                             'Adds specified author information. '746                             'Specify as comma-separated list of either "name", "email", "date" or "n","e","d". '747                             'Default: "n,e,d"')748    parser.add_argument('--source-encoding', dest='source_encoding',749                      help='Encoding of source code.', default='utf-8')750    parser.add_argument('--blame-options', dest='blame_options',751                        help='[-w, -M] blame options which you can use to get author and author mail  '752                             '-w --> not including white spaces and returns original author of the line  '753                             '-M --> not including moving of lines and returns original author of the line')754 755    # Parse options and make sure that we have an output directory set.756    options = parser.parse_args()757 758    try:759        sys.argv[1]760    except IndexError:  # no arguments give, print --help761        parser.print_help()762        sys.exit(0)763 764    if not options.report_dir:765        parser.error('No report directory set.')766 767    # Get the directory where source code files are located.768    source_dir = os.getcwd()769    if options.source_dir:770        source_dir = options.source_dir771 772    is_remote = False773    if source_dir.startswith("http://") or source_dir.startswith("https://"):774        is_remote = True775 776    add_author_information = []777    if options.add_author_information:778        fields = [x.strip() for x in options.add_author_information.split(',')]779        for x in fields:780            if x.lower() in ['n', 'name']:781                add_author_information.append('name')782            elif x.lower() in ['e', 'email']:783                add_author_information.append('email')784            elif x.lower() in ['d', 'date']:785                add_author_information.append('date')786            else:787                print('Unrecognized value "%s" for author information, using default (name, email, date)' % x)788                add_author_information = ['name', 'email', 'date']789                break790 791    blame_options = ''792    if options.blame_options:793        blame_options = options.blame_options794        add_author_information = add_author_information or ['name', 'email', 'date']795 796    # Parse the xml from all files defined in file argument797    # or from stdin. If no input is provided, stdin is used798    # Produce a simple list of errors.799    print('Parsing xml report.')800    try:801        contentHandler = CppCheckHandler()802        for fname in options.file or [sys.stdin]:803            if options.file is not None:804                t = os.path.getmtime(fname)805            else:806                t = time.time()807            t_s = time.ctime(t)808            if t_s is not None:809                contentHandler.timestamp = t_s810            xml_parse(fname, contentHandler)811    except (XmlParseException, ValueError) as msg:812        print('Failed to parse cppcheck xml file: %s' % msg)813        sys.exit(1)814 815    # We have a list of errors. But now we want to group them on816    # each source code file. Lets create a files dictionary that817    # will contain a list of all the errors in that file. For each818    # file we will also generate a HTML filename to use.819    files = {}820    file_no = 0821    for error in contentHandler.errors:822        filename = error['file']823        if filename not in files:824            if is_remote:825                # Construct remote URL for GitHub/GitLab826                # tr_str() will use the actual line number, so we can just start with line 1827                remote_url = source_dir.rstrip('/') + '/' + filename + '#L1'828                files[filename] = {'errors': [], 'htmlfile': remote_url}829            else:830                files[filename] = {'errors': [], 'htmlfile': str(file_no) + '.html'}831                file_no += 1832        files[filename]['errors'].append(error)833 834    # Make sure that the report directory is created if it doesn't exist.835    print('Creating %s directory' % options.report_dir)836    if not os.path.exists(options.report_dir):837        os.makedirs(options.report_dir)838 839    # Generate a HTML file with syntax highlighted source code for each840    # file that contains one or more errors.841    print('Processing errors')842 843    decode_errors = []844    for filename, data in sorted(files.items()):845        htmlfile = data['htmlfile']846        errors = []847 848        for error in data['errors']:849            for location in error['locations']:850                if filename == location['file']:851                    newError = dict(error)852 853                    del newError['locations']854                    newError['line'] = location['line']855                    if location.get('info'):856                        newError['msg'] = location['info']857                        newError['severity'] = 'information'858                        newError['classification'] = ''859                        newError['guideline'] = ''860                        del newError['verbose']861 862                    errors.append(newError)863 864        lines = []865        for error in errors:866            lines.append(error['line'])867 868        if filename == '':869            continue870 871        if is_remote:872            # Remote source: do NOT generate local annotated HTML files.873            # The index will still point directly to GitHub/GitLab URLs.874            continue875 876        source_filename = os.path.join(source_dir, filename)877        try:878            with io.open(source_filename, 'r', encoding=options.source_encoding) as input_file:879                content = input_file.read()880        except IOError:881            if error['id'] != 'unmatchedSuppression':882                sys.stderr.write("ERROR: Source file '%s' not found.\n" %883                                 source_filename)884            continue885        except UnicodeDecodeError:886            sys.stderr.write("WARNING: Unicode decode error in '%s'.\n" %887                             source_filename)888            decode_errors.append(source_filename[2:])  # "[2:]" gets rid of "./" at beginning889            continue890 891        htmlFormatter = AnnotateCodeFormatter(linenos=True,892                                              style='colorful',893                                              hl_lines=lines,894                                              lineanchors='line',895                                              encoding=options.source_encoding)896        htmlFormatter.errors = errors897 898        with io.open(os.path.join(options.report_dir, htmlfile), 'w', encoding='utf-8') as output_file:899            output_file.write(HTML_HEAD %900                              (options.title,901                               htmlFormatter.get_style_defs('.highlight'),902                               options.title,903                               ': ' + filename))904            output_file.write(HTML_HEAD_END)905 906            output_file.write(HTML_MENU % (filename.split('/')[-1]))907            for error in sorted(errors, key=lambda k: k['line']):908                output_file.write("<a href=\"%s#line-%d\"> %s %s</a>" % (data['htmlfile'], error['line'], error['id'], error['line']))909            output_file.write(HTML_MENU_END)910 911            try:912                lexer = guess_lexer_for_filename(source_filename, '', stripnl=False)913            except ClassNotFound:914                try:915                    lexer = guess_lexer(content, stripnl=False)916                except ClassNotFound:917                    sys.stderr.write("ERROR: Couldn't determine lexer for the file' " + source_filename + " '. Won't be able to syntax highlight this file.")918                    output_file.write("\n <tr><td colspan=\"6\"> Could not generate content because pygments failed to determine the code type.</td></tr>")919                    output_file.write("\n <tr><td colspan=\"6\"> Sorry about this.</td></tr>")920                    continue921 922            if options.source_encoding:923                lexer.encoding = options.source_encoding924 925            output_file.write(926                highlight(content, lexer, htmlFormatter).decode(927                    options.source_encoding))928 929            output_file.write(HTML_FOOTER % contentHandler.versionCppcheck)930 931        print('  ' + filename)932 933    # Generate a master index.html file that will contain a list of934    # all the errors created.935    print('Creating index.html')936 937    with io.open(os.path.join(options.report_dir, 'index.html'),938                 'w') as output_file:939 940        stats_count = 0941        stats = []942        filter_enabled = {}943        for filename, data in sorted(files.items()):944            for error in data['errors']:945                stats.append(error['id'])  # get the stats946                if contentHandler.report_type:947                    filter_enabled[error['severity']] = False948                    filter_enabled[error['classification']] = True949                else:950                    filter_enabled[error['severity']] = True951                    filter_enabled[error['classification']] = False952                filter_enabled['clang-tidy' if error['id'].startswith('clang-tidy-') else 'cppcheck'] = True953                stats_count += 1954 955        counter = Counter(stats)956 957        stat_html = []958        # the following lines sort the stat primary by value (occurrences),959        # but if two IDs occur equally often, then we sort them alphabetically by warning ID960        try:961            cnt_max = counter.most_common()[0][1]962        except IndexError:963            cnt_max = 0964 965        try:966            cnt_min = counter.most_common()[-1][1]967        except IndexError:968            cnt_min = 0969 970        stat_fmt = "\n            <tr><td><input type=\"checkbox\" class=\"idToggle\" onclick=\"toggleDisplay(this)\" id=\"{}\" name=\"{}\" checked></td><td>{}</td><td>{}</td></tr>"971        for occurrences in reversed(range(cnt_min, cnt_max + 1)):972            for _id in [k for k, v in sorted(counter.items()) if v == occurrences]:973                stat_html.append(stat_fmt.format(974                    to_css_selector(_id),975                    to_css_selector(_id),976                    dict(counter.most_common())[_id],977                    _id978                ))979 980        output_file.write(HTML_HEAD % (options.title, '', options.title, ''))981        output_file.write(filter_bar(filter_enabled))982        output_file.write(HTML_HEAD_END)983        output_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Defect summary", 1) % (''))984        output_file.write('\n       <label><input type="checkbox" class=\"idToggle\" onclick="toggleAll()" checked> Toggle all</label>')985        output_file.write('\n       <table>')986        output_file.write('\n           <tr><th>Show</th><th>#</th><th>Defect ID</th></tr>')987        output_file.write(''.join(stat_html))988        output_file.write('\n           <tr><td></td><td>' + str(stats_count) + '</td><td>total</td></tr>')989        output_file.write('\n       </table>')990        output_file.write('\n       <p><a href="stats.html">Statistics</a></p>')991        if options.checkers_report:992            output_file.write('\n       <p><a href="checkers.html">Checkers</a></p>')993        output_file.write(HTML_MENU_END.replace("content", "content_index", 1))994 995        output_file.write('\n       <table class=\"summaryTable\">')996        if contentHandler.report_type:997            output_file.write(998                '\n       %s' %999                tr_str('th', 'Line', 'Id', 'CWE', '', 'Classification', 'Guideline', 'Message', 'Timestamp',1000                       'Author', 'Author mail', 'Date (DD/MM/YYYY)', add_author=add_author_information))1001        else:1002            output_file.write(1003                '\n       %s' %1004                tr_str('th', 'Line', 'Id', 'CWE', 'Severity', '', '', 'Message', 'Timestamp', 'Author', 'Author mail', 'Date (DD/MM/YYYY)', add_author=add_author_information))1005 1006        for filename, data in sorted(files.items()):1007            file_error = filename in decode_errors or filename.endswith('*')1008            is_file = filename != '' and not file_error1009            row_content = filename if file_error else "<a href=\"%s\">%s</a>" % (data['htmlfile'], filename)1010            htmlfile = data.get('htmlfile') if is_file else None1011 1012            output_file.write("\n      <tbody class=\"fileEntry\">")1013            output_file.write("\n       <tr><td colspan=\"6\">%s</td></tr>" % row_content)1014 1015            if filename in decode_errors:1016                output_file.write("\n       <tr><td colspan=\"6\">Could not generated due to UnicodeDecodeError</td></tr>")1017 1018            sorted_errors = sorted(data['errors'], key=lambda k: k['line'])1019            blame_data = git_blame(sorted_errors, source_dir, filename, blame_options) if add_author_information else []1020            for error in sorted_errors:1021                git_blame_dict = blame_lookup(blame_data, error['line'])1022                message_class = None1023                try:1024                    if error['inconclusive'] == 'true':1025                        message_class = 'inconclusive'1026                        error['severity'] += ", inconcl."1027                except KeyError:1028                    pass1029 1030                cwe_url = ""1031                try:1032                    if error['cwe']:1033                        cwe_url = "<a href=\"https://cwe.mitre.org/data/definitions/" + error['cwe'] + ".html\">" + error['cwe'] + "</a>"1034                except KeyError:1035                    pass1036 1037                if error['severity'] in ['error', 'warning']:1038                    message_class = error['severity']1039 1040                line = error["line"] if is_file else ""1041                _severity = error.get('severity', '')1042                _classification = error.get('classification', '')1043                _guideline = error.get('guideline', '')1044                if contentHandler.report_type:1045                    _severity = ''1046                    if not _classification:1047                        _classification = 'None'1048                        _guideline = 'None'1049                output_file.write(1050                    '\n         %s' %1051                    tr_str('td', line, error["id"], cwe_url, _severity, _classification, _guideline, error["msg"], error["timestamp"],1052                           git_blame_dict.get('author', 'Unknown'), git_blame_dict.get('author-mail', '---'),1053                           git_blame_dict.get('author-time', '---'),1054                           tr_class=to_css_selector(error["id"]) + ' sev_' + _severity  +' class_' + _classification + ' issue',1055                           message_class=message_class,1056                           add_author=add_author_information,1057                           htmlfile=htmlfile))1058        output_file.write("\n      </tbody>")1059        output_file.write('\n       </table>')1060        output_file.write(HTML_FOOTER % contentHandler.versionCppcheck)1061 1062    if decode_errors:1063        sys.stderr.write("\nGenerating html failed for the following files: " + ' '.join(decode_errors))1064        sys.stderr.write("\nConsider changing source-encoding (for example: \"htmlreport ... --source-encoding=\"iso8859-1\"\"\n")1065 1066    print('Creating style.css file')1067    with io.open(os.path.join(options.report_dir, 'style.css'), 'w') as css_file:1068        css_file.write(STYLE_FILE)1069 1070    print("Creating stats.html (statistics)\n")1071    stats_countlist = {}1072 1073    for filename, data in sorted(files.items()):1074        if filename == '':1075            continue1076        stats_tmplist = []1077        for error in sorted(data['errors'], key=lambda k: k['line']):1078            stats_tmplist.append(error['severity'])1079 1080        stats_countlist[filename] = dict(Counter(stats_tmplist))1081 1082    # get top ten for each severity1083    SEVERITIES = "error", "warning", "portability", "performance", "style", "unusedFunction", "information", "missingInclude", "internal"1084 1085    with io.open(os.path.join(options.report_dir, 'stats.html'), 'w') as stats_file:1086 1087        stats_file.write(HTML_HEAD % (options.title, '', options.title, ': Statistics'))1088        stats_file.write(HTML_HEAD_END)1089 1090        stats_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Back to summary", 1) % (''))1091        stats_file.write(HTML_MENU_END.replace("content", "content_index", 1))1092 1093 1094        for sev in SEVERITIES:1095            _sum = 01096            stats_templist = {}1097 1098            # if the we have an style warning but we are checking for1099            # portability, we have to skip it to prevent KeyError1100            try:1101                for filename in stats_countlist:1102                    try:  # also bail out if we have a file with no sev-results1103                        _sum += stats_countlist[filename][sev]1104                        stats_templist[filename] = int(stats_countlist[filename][sev])  # file : amount,1105                    except KeyError:1106                        continue1107                # don't print "0 style" etc, if no style warnings were found1108                if _sum == 0:1109                    continue1110            except KeyError:1111                continue1112            stats_file.write("<p><span class=\"statHeader\">Top 10 files for " + sev + " severity, total findings: " + str(_sum) + "</span><br>\n")1113 1114            # sort, so that the file with the most severities per type is first1115            stats_list_sorted = sorted(stats_templist.items(), key=operator.itemgetter(1, 0), reverse=True)1116            it = 01117            LENGTH = 01118 1119            for i in stats_list_sorted:  # printing loop1120                # for aesthetics: if it's the first iteration of the loop, get1121                # the max length of the number string1122                if it == 0:1123                    LENGTH = len(str(i[1]))  # <- length of longest number, now get the difference and try to  make other numbers align to it1124 1125                stats_file.write("&#160;" * 3 + str(i[1]) + "&#160;" * (1 + LENGTH - len(str(i[1]))) + "<a href=\"" + files[i[0]]['htmlfile'] + "\">  " + i[0] + "</a><br>\n")1126                it += 11127                if it == 10:  # print only the top 101128                    break1129            stats_file.write("</p>\n")1130 1131        stats_file.write(HTML_FOOTER % contentHandler.versionCppcheck)1132 1133    if options.checkers_report:1134        print("Creating checkers.html (checkers report)\n")1135 1136        with io.open(os.path.join(options.report_dir, 'checkers.html'), 'w') as checkers_file:1137 1138            checkers_file.write(HTML_HEAD % (options.title, '', options.title, ': Checkers'))1139            checkers_file.write(HTML_HEAD_END)1140 1141            checkers_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Back to summary", 1) % (''))1142            checkers_file.write(HTML_MENU_END.replace("content", "content_index", 1))1143 1144            with io.open(options.checkers_report, 'r', encoding=options.source_encoding) as checkers_report:1145                content = checkers_report.read()1146 1147                checkers_file.write("<pre>\n")1148                checkers_file.write(html_escape(content))1149                checkers_file.write("</pre>\n")1150 1151            checkers_file.write(HTML_FOOTER % contentHandler.versionCppcheck)1152 1153    print("\nOpen '" + options.report_dir + "/index.html' to see the results.")1154 1155 1156if __name__ == "__main__":1157    main()1158 
codekingpro/portable-devtools · Team Ai