def to_report(report: ReportData, get_file: Callable[[int, str],
                                                     File]) -> Report:
    """ Create a Report object from the given thrift report data. """
    severity = Severity._VALUES_TO_NAMES[report.severity] \
        if report.severity else 'UNSPECIFIED'

    bug_path_events: List[BugPathEvent] = []
    bug_path_positions: List[BugPathPosition] = []
    notes: List[BugPathEvent] = []
    macro_expansions: List[MacroExpansion] = []

    details = report.details
    if details:
        for e in details.pathEvents:
            bug_path_events.append(
                BugPathEvent(
                    e.msg, get_file(e.fileId, e.filePath), e.startLine,
                    e.startCol,
                    Range(e.startLine, e.startCol, e.endLine, e.endCol)))

        for p in details.executionPath:
            bug_path_positions.append(
                BugPathPosition(
                    get_file(p.fileId, p.filePath),
                    Range(p.startLine, p.startCol, p.endLine, p.endCol)))

        for e in details.extendedData:
            if e.type == ExtendedReportDataType.NOTE:
                notes.append(
                    BugPathEvent(
                        e.message, get_file(e.fileId, e.filePath), e.startLine,
                        e.startCol,
                        Range(e.startLine, e.startCol, e.endLine, e.endCol)))

            if e.type == ExtendedReportDataType.MACRO:
                name = ''
                macro_expansions.append(
                    MacroExpansion(
                        e.message, name, get_file(e.fileId, e.filePath),
                        e.startLine, e.startCol,
                        Range(e.startLine, e.startCol, e.endLine, e.endCol)))

    return Report(get_file(report.fileId, report.checkedFile),
                  report.line,
                  report.column,
                  report.checkerMsg,
                  report.checkerId,
                  severity,
                  report.bugHash,
                  report.analyzerName,
                  bug_path_events=bug_path_events or None,
                  bug_path_positions=bug_path_positions,
                  notes=notes,
                  macro_expansions=macro_expansions)
Beispiel #2
0
    def __parse_report(self, bug) -> Optional[Report]:
        """ Parse the given report and create a message from them. """
        report_hash = bug['hash']
        checker_name = bug['bug_type']

        message = bug['qualifier']
        line = int(bug['line'])
        col = int(bug['column'])
        if col < 0:
            col = 0

        source_path = self.__get_abs_path(bug['file'])
        if not source_path:
            return None

        report = Report(
            get_or_create_file(
                os.path.abspath(source_path), self.__file_cache),
            line, col, message, checker_name,
            report_hash=report_hash,
            bug_path_events=[])

        for bug_trace in bug['bug_trace']:
            event = self.__parse_bug_trace(bug_trace)

            if event:
                report.bug_path_events.append(event)

        report.bug_path_events.append(BugPathEvent(
            report.message, report.file, report.line, report.column))

        return report
Beispiel #3
0
    def _parse_line(self, it: Iterator[str],
                    line: str) -> Tuple[List[Report], str]:
        """ Parse the given line. """
        match = self.message_line_re.match(line)

        if (match is None):
            return [], next(it)

        file_path = os.path.normpath(
            os.path.join(os.path.dirname(self.analyzer_result),
                         match.group('path')))

        report = Report(get_or_create_file(file_path, self._file_cache),
                        int(match.group('line')),
                        int(match.group('column')),
                        match.group('message').strip(),
                        '',
                        bug_path_events=[])

        line = ''
        try:
            line = next(it)
            note_match = self.note_line_re.match(line)
            while note_match:
                file_path = os.path.normpath(
                    os.path.join(os.path.dirname(self.analyzer_result),
                                 note_match.group('path')))

                report.bug_path_events.append(
                    BugPathEvent(
                        note_match.group('message').strip(),
                        get_or_create_file(file_path, self._file_cache),
                        int(note_match.group('line')),
                        int(note_match.group('column'))))

                line = next(it)
                note_match = self.note_line_re.match(line)
        except StopIteration:
            line = ''
        finally:
            report.bug_path_events.append(
                BugPathEvent(report.message, report.file, report.line,
                             report.column))

            return [report], line
Beispiel #4
0
    def create_report(self, events: List[BugPathEvent], file: File, line: int,
                      column: int, message: str,
                      stack_traces: List[str]) -> Report:
        """ Create a report for the sanitizer output. """
        # The original message should be the last part of the path. This is
        # displayed by quick check, and this is the main event displayed by
        # the web interface.
        events.append(BugPathEvent(message, file, line, column))

        notes = None
        if stack_traces:
            notes = [BugPathEvent(''.join(stack_traces), file, line, column)]

        return Report(file,
                      line,
                      column,
                      message,
                      self.checker_name,
                      bug_path_events=events,
                      notes=notes)
Beispiel #5
0
    def _parse_fixits(self, report: Report, it: Iterator[str],
                      line: str) -> str:
        """ Parses fixit messages. """

        while self.message_line_re.match(line) is None and \
                self.note_line_re.match(line) is None:
            message_text = line.strip()

            if message_text != '':
                report.bug_path_events.append(
                    BugPathEvent(f"{message_text} (fixit)", report.file,
                                 report.line,
                                 line.find(message_text) + 1))

            line = next(it)
        return line
Beispiel #6
0
    def __parse_bug_trace(self, bug_trace) -> Optional[BugPathEvent]:
        """ Creates event from a bug trace element. """
        source_path = self.__get_abs_path(bug_trace['filename'])
        if not source_path:
            return None

        message = bug_trace['description']
        line = int(bug_trace['line_number'])
        col = int(bug_trace['column_number'])
        if col < 0:
            col = 0

        return BugPathEvent(
            message,
            get_or_create_file(source_path, self.__file_cache),
            line,
            col)
Beispiel #7
0
    def __parse_bug(self, bug):
        """ Parse the given bug and create a message from them. """
        report_hash = bug.attrib.get('instanceHash')
        checker_name = bug.attrib.get('type')

        long_message = bug.find('LongMessage').text

        source_line = bug.find('SourceLine')
        source_path = source_line.attrib.get('sourcepath')
        source_path = self.__get_abs_path(source_path)
        if not source_path:
            return

        line = source_line.attrib.get('start')
        col = 0

        events = []
        for element in list(bug):
            event = None
            if element.tag == 'Class':
                event = self.__event_from_class(element)
            elif element.tag == 'Method':
                event = self.__event_from_method(element)

            if event:
                events.append(event)

        # If <SourceLine> did not contain a 'start' attribute, take the last
        # of the events.
        if line is None:
            line = next((e.line for e in reversed(events) if e.line > 0), 0)

        report = Report(get_or_create_file(source_path, self.__file_cache),
                        int(line),
                        col,
                        long_message,
                        checker_name,
                        report_hash=report_hash,
                        bug_path_events=events)

        report.bug_path_events.append(
            BugPathEvent(report.message, report.file, report.line,
                         report.column))

        return report
Beispiel #8
0
    def __get_notes(self, diag, files: Dict[int, File]) -> List[BugPathEvent]:
        """ Get notes. """
        notes = []

        for note in diag.get('notes', []):
            if not note['message']:
                continue

            location, start_loc, end_loc = self.__get_bug_event_locations(note)
            notes.append(
                BugPathEvent(message=note['message'],
                             file=files[location['file']],
                             line=location['line'],
                             column=location['col'],
                             range=Range(start_loc['line'], start_loc['col'],
                                         end_loc['line'], end_loc['col'])))

        return notes
Beispiel #9
0
    def __event_from_method(self, element) -> Optional[BugPathEvent]:
        """ Creates event from a Method element. """
        message = element.find('Message').text

        source_line = element.find('SourceLine')
        if source_line is None:
            return None

        source_path = source_line.attrib.get('sourcepath')
        source_path = self.__get_abs_path(source_path)
        if not source_path:
            return None

        line = int(source_line.attrib.get('start', 0))
        col = 0

        return BugPathEvent(message,
                            get_or_create_file(source_path, self.__file_cache),
                            line, col)
Beispiel #10
0
    def __get_bug_path_events(self, diag,
                              files: Dict[int, File]) -> List[BugPathEvent]:
        """ Get bug path events. """
        events = []

        for item in diag.get('path', []):
            if item.get('kind') != 'event':
                continue

            location, start_loc, end_loc = self.__get_bug_event_locations(item)
            events.append(
                BugPathEvent(message=item['message'],
                             file=files[location['file']],
                             line=location['line'],
                             column=location['col'],
                             range=Range(start_loc['line'], start_loc['col'],
                                         end_loc['line'], end_loc['col'])))

        return events
Beispiel #11
0
    def _parse_notes(self, report: Report, it: Iterator[str],
                     line: str) -> str:
        """ Parses note messages. """

        while self.message_line_re.match(line) is None:
            match = self.note_line_re.match(line)
            if match is None:
                LOG.debug("Unexpected line: %s", line)
                return next(it)

            report.bug_path_events.append(
                BugPathEvent(
                    match.group('message').strip(),
                    get_or_create_file(os.path.abspath(match.group('path')),
                                       self._file_cache),
                    int(match.group('line')), int(match.group('column'))))

            line = next(it)
            line = self._parse_code(it, line)
        return line
Beispiel #12
0
    def _parse_line(self, it: Iterator[str],
                    line: str) -> Tuple[List[Report], str]:
        """ Parse the given line. """
        match = self.message_line_re.match(line)
        if match is None:
            return [], next(it)

        checker_names = match.group('checker').strip().split(",")
        report = Report(get_or_create_file(
            os.path.abspath(match.group('path')), self._file_cache),
                        int(match.group('line')),
                        int(match.group('column')),
                        match.group('message').strip(),
                        checker_names[0],
                        bug_path_events=[])

        try:
            line = next(it)
            line = self._parse_code(it, line)
            line = self._parse_fixits(report, it, line)
            line = self._parse_notes(report, it, line)
        except StopIteration:
            line = ''
        finally:
            report.bug_path_events.append(
                BugPathEvent(report.message, report.file, report.line,
                             report.column))

            # When a checker name and the alias of this checker is turned on,
            # Clang Tidy (>v11) will generate only one report where the checker
            # names are concatenated with ',' mark. With this we will generate
            # multiple reports for each checker name / alias.
            reports = []
            for checker_name in checker_names:
                r = deepcopy(report)
                r.checker_name = checker_name
                r.category = self._get_category(checker_name)

                reports.append(r)

            return reports, line
Beispiel #13
0
    def parse_stack_trace_line(self, line: str) -> Optional[BugPathEvent]:
        """ Parse the given stack trace line.

        Return an event if the file in the stack trace line exists otherwise
        it returns None.
        """
        file_match = self.file_re.search(line)
        if not file_match:
            return None

        file_path = file_match.group('path')
        if file_path and os.path.exists(file_path):
            col = file_match.group('column')
            return BugPathEvent(
                line.rstrip(),
                get_or_create_file(os.path.abspath(file_path),
                                   self._file_cache),
                int(file_match.group('line')),
                int(col) if col else 0)

        return None
Beispiel #14
0
    File(os.path.join(gen_plist_dir_path, 'test.cpp')),
    File(os.path.join(gen_plist_dir_path, 'test.h'))
]

# Base skeletons for reports where the checker name is already available.
div_zero_skel = Report(
    SRC_FILES[1],
    7,
    14,
    'Division by zero',
    'core.DivideZero',
    report_hash='79e31a6ba028f0b7d9779faf4a6cb9cf',
    category='Logic error',
    type='Division by zero',
    bug_path_events=[
        BugPathEvent("'base' initialized to 0", SRC_FILES[0], 20, 5,
                     Range(20, 5, 20, 12)),
        BugPathEvent("Passing the value 0 via 1st parameter 'base'",
                     SRC_FILES[0], 21, 15, Range(21, 15, 21, 18)),
        BugPathEvent("Calling 'test_func'", SRC_FILES[0], 21, 5,
                     Range(21, 5, 21, 19)),
        BugPathEvent("Entered call from 'main'", SRC_FILES[0], 6, 1,
                     Range(6, 1, 6, 1)),
        BugPathEvent("Passing the value 0 via 1st parameter 'num'",
                     SRC_FILES[0], 8, 22, Range(8, 22, 8, 25)),
        BugPathEvent("Calling 'generate_id'", SRC_FILES[0], 8, 10,
                     Range(8, 10, 8, 26)),
        BugPathEvent("Entered call from 'test_func'", SRC_FILES[1], 6, 1,
                     Range(6, 1, 6, 1)),
        BugPathEvent("Division by zero", SRC_FILES[1], 7, 14,
                     Range(7, 12, 7, 17))
    ],