Commit 625ca103 authored by Jan Kiene's avatar Jan Kiene
Browse files

add ASAN support

for now tested against AddressSanitizer and LeakSanitizer cases
parent 39763446
Loading
Loading
Loading
Loading
+65 −8
Original line number Diff line number Diff line
#!/usr/env python3

from numpy import trace
import pandas as pd
from xml.etree import ElementTree
import argparse
@@ -78,6 +79,44 @@ class MsanError(SanitizerError):
    SUMMARY_ID = "MemorySanitizer"


class AsanError(SanitizerError):
    SUMMARY_ID = "AddressSanitizer"

    def parse_type_and_location(self, traceback, cwd) -> Tuple[str, str]:
        first_line = traceback.split("\n")[0].strip()

        type = ""
        location = ""
        if "AddressSanitizer" in first_line:
            last_line = traceback.split("\n")[-1].strip()
            assert last_line.startswith(f"SUMMARY: {self.SUMMARY_ID}")
            m = re.match(
                r"SUMMARY: "
                + self.SUMMARY_ID
                + r": ([a-z-]*) (.*\/.*\.[ch]:\d+:\d+) in",
                last_line,
            )
            assert m is not None

            type, location = m.groups()
        elif "LeakSanitizer" in first_line:
            type = "memory leaks"

            # for location, we just pick from the first leak, even if there are more in there
            # perfect accurac not needed here
            for line in traceback.split("\n"):
                # this assumes that number #0 always is the executable itself and has no file associated
                if line.strip().startswith("#1"):
                    location = line.split()[-1]
                    break
        else:
            raise NotImplementedError("Unknown Asan type")

        if Path(location).is_absolute():
            location = str(Path(location).relative_to(cwd))
        return type, location


def parse_commandlines_from_sysout(sysout: str, cwd: Path) -> dict:
    commandlines = {
        "IVAS_cod": "",
@@ -92,17 +131,19 @@ def parse_commandlines_from_sysout(sysout: str, cwd: Path) -> dict:
            # search for name of executable in line
            # it is repeated in the sanitizer traceback, hence the "not in" part
            # the "not at the start" condition is for eid-xor (there are also lines like this: "eid-xor command:")
            # the "does not contain CalledProcessError" is for the renderer tests
            if (
                re.search(exe, line) is not None
                and " in _start " not in line
                and not line.strip().startswith(exe)
                and "CalledProcessError" not in line
            ):
                if commandlines[exe] != "":
                    logging.debug(
                        f"Commandline for {exe} already found, skip second one."
                    )
                else:
                    commandlines[exe] = postprocess_cmdline(line.strip(), cwd)
                    commandlines[exe] = postprocess_cmdline(line.strip(), cwd, exe)

                # assumption: only one commandline per line
                break
@@ -110,8 +151,15 @@ def parse_commandlines_from_sysout(sysout: str, cwd: Path) -> dict:
    return commandlines


def postprocess_cmdline(cmdline: str, cwd: Path) -> str:
    cmdline_split = cmdline.split()
def postprocess_cmdline(cmdline: str, cwd: Path, exe: str) -> str:
    # only use line with commandline from the token that includes the exe name
    # reason again the renderer tests...
    idx = 0
    for elem in cmdline.split():
        if exe in elem:
            idx = cmdline.index(elem)

    cmdline_split = cmdline[idx:].split()
    cmdline_proc = []

    # change absolute paths into relative ones
@@ -151,6 +199,7 @@ def parse_errors_from_sysout(

    pattern_usan = re.compile(r"(lib_.+|apps)\/(.*\.[ch]):(\d+):(\d+): runtime error:")
    pattern_msan = re.compile(r" MemorySanitizer: ")
    pattern_asan = re.compile(r"==\d+==ERROR: .+Sanitizer: ")

    state = ParserState.OUT
    accu = []
@@ -163,18 +212,26 @@ def parse_errors_from_sysout(

        m_usan = re.search(pattern_usan, line)
        m_msan = re.search(pattern_msan, line)
        m_asan = re.search(pattern_asan, line)

        usan_start_found = m_usan is not None
        msan_start_found = m_msan is not None and not line.startswith("SUMMARY:")
        asan_start_found = m_asan is not None

        assert usan_start_found != msan_start_found or (
            not usan_start_found and not msan_start_found
        )
        if usan_start_found or msan_start_found:
        matches_found = sum([usan_start_found, msan_start_found, asan_start_found])
        assert matches_found <= 1

        if matches_found > 0:
            assert state == ParserState.OUT
            state = ParserState.IN
            accu = []
            err_cls = UsanError if m_usan is not None else MsanError
            err_cls = (
                UsanError
                if m_usan is not None
                else MsanError
                if m_msan is not None
                else AsanError
            )

        if state == ParserState.IN:
            accu.append(line)