brintos

brintos / llvm-project-archived public Read only

0
0
Text · 25.0 KiB · 9c11013 Raw
839 lines · python
1from http.server import HTTPServer, SimpleHTTPRequestHandler2import os3import sys4from urllib.parse import urlparse, unquote5import posixpath6from io import StringIO, BytesIO7import re8import shutil9import threading10import time11import socket12import itertools13import configparser14 15import Reporter16 17###18# Various patterns matched or replaced by server.19 20kReportFileRE = re.compile("(.*/)?report-(.*)\\.html")21 22kBugKeyValueRE = re.compile("<!-- BUG([^ ]*) (.*) -->")23 24#  <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->25 26kReportCrashEntryRE = re.compile("<!-- REPORTPROBLEM (.*?)-->")27kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')28 29kReportReplacements = []30 31# Add custom javascript.32kReportReplacements.append(33    (34        re.compile("<!-- SUMMARYENDHEAD -->"),35        """\36<script language="javascript" type="text/javascript">37function load(url) {38  if (window.XMLHttpRequest) {39    req = new XMLHttpRequest();40  } else if (window.ActiveXObject) {41    req = new ActiveXObject("Microsoft.XMLHTTP");42  }43  if (req != undefined) {44    req.open("GET", url, true);45    req.send("");46  }47}48</script>""",49    )50)51 52# Insert additional columns.53kReportReplacements.append((re.compile("<!-- REPORTBUGCOL -->"), "<td></td><td></td>"))54 55# Insert report bug and open file links.56kReportReplacements.append(57    (58        re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),59        (60            '<td class="Button"><a href="report/\\1">Report Bug</a></td>'61            + '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>'62        ),63    )64)65 66kReportReplacements.append(67    (68        re.compile("<!-- REPORTHEADER -->"),69        '<h3><a href="/">Summary</a> > Report %(report)s</h3>',70    )71)72 73kReportReplacements.append(74    (75        re.compile("<!-- REPORTSUMMARYEXTRA -->"),76        '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>',77    )78)79 80# Insert report crashes link.81 82# Disabled for the time being until we decide exactly when this should83# be enabled. Also the radar reporter needs to be fixed to report84# multiple files.85 86# kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),87#                            '<br>These files will automatically be attached to ' +88#                            'reports filed here: <a href="report_crashes">Report Crashes</a>.'))89 90###91# Other simple parameters92 93kShare = posixpath.join(posixpath.dirname(__file__), "../share/scan-view")94kConfigPath = os.path.expanduser("~/.scanview.cfg")95 96###97 98__version__ = "0.1"99 100__all__ = ["create_server"]101 102 103class ReporterThread(threading.Thread):104    def __init__(self, report, reporter, parameters, server):105        threading.Thread.__init__(self)106        self.report = report107        self.server = server108        self.reporter = reporter109        self.parameters = parameters110        self.success = False111        self.status = None112 113    def run(self):114        result = None115        try:116            if self.server.options.debug:117                print("%s: SERVER: submitting bug." % (sys.argv[0],), file=sys.stderr)118            self.status = self.reporter.fileReport(self.report, self.parameters)119            self.success = True120            time.sleep(3)121            if self.server.options.debug:122                print(123                    "%s: SERVER: submission complete." % (sys.argv[0],), file=sys.stderr124                )125        except Reporter.ReportFailure as e:126            self.status = e.value127        except Exception as e:128            s = StringIO()129            import traceback130 131            print("<b>Unhandled Exception</b><br><pre>", file=s)132            traceback.print_exc(file=s)133            print("</pre>", file=s)134            self.status = s.getvalue()135 136 137class ScanViewServer(HTTPServer):138    def __init__(self, address, handler, root, reporters, options):139        HTTPServer.__init__(self, address, handler)140        self.root = root141        self.reporters = reporters142        self.options = options143        self.halted = False144        self.config = None145        self.load_config()146 147    def load_config(self):148        self.config = configparser.RawConfigParser()149 150        # Add defaults151        self.config.add_section("ScanView")152        for r in self.reporters:153            self.config.add_section(r.getName())154            for p in r.getParameters():155                if p.saveConfigValue():156                    self.config.set(r.getName(), p.getName(), "")157 158        # Ignore parse errors159        try:160            self.config.read([kConfigPath])161        except:162            pass163 164        # Save on exit165        import atexit166 167        atexit.register(lambda: self.save_config())168 169    def save_config(self):170        # Ignore errors (only called on exit).171        try:172            f = open(kConfigPath, "w")173            self.config.write(f)174            f.close()175        except:176            pass177 178    def halt(self):179        self.halted = True180        if self.options.debug:181            print("%s: SERVER: halting." % (sys.argv[0],), file=sys.stderr)182 183    def serve_forever(self):184        while not self.halted:185            if self.options.debug > 1:186                print("%s: SERVER: waiting..." % (sys.argv[0],), file=sys.stderr)187            try:188                self.handle_request()189            except OSError as e:190                print("OSError", e.errno)191 192    def finish_request(self, request, client_address):193        if self.options.autoReload:194            import ScanView195 196            self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler197        HTTPServer.finish_request(self, request, client_address)198 199    def handle_error(self, request, client_address):200        # Ignore socket errors201        info = sys.exc_info()202        if info and isinstance(info[1], socket.error):203            if self.options.debug > 1:204                print(205                    "%s: SERVER: ignored socket error." % (sys.argv[0],),206                    file=sys.stderr,207                )208            return209        HTTPServer.handle_error(self, request, client_address)210 211 212# Borrowed from Quixote, with simplifications.213def parse_query(qs, fields=None):214    if fields is None:215        fields = {}216    for chunk in (_f for _f in qs.split("&") if _f):217        if "=" not in chunk:218            name = chunk219            value = ""220        else:221            name, value = chunk.split("=", 1)222        name = unquote(name.replace("+", " "))223        value = unquote(value.replace("+", " "))224        item = fields.get(name)225        if item is None:226            fields[name] = [value]227        else:228            item.append(value)229    return fields230 231 232class ScanViewRequestHandler(SimpleHTTPRequestHandler):233    server_version = "ScanViewServer/" + __version__234    dynamic_mtime = time.time()235 236    def do_HEAD(self):237        try:238            SimpleHTTPRequestHandler.do_HEAD(self)239        except Exception as e:240            self.handle_exception(e)241 242    def do_GET(self):243        try:244            SimpleHTTPRequestHandler.do_GET(self)245        except Exception as e:246            self.handle_exception(e)247 248    def do_POST(self):249        """Serve a POST request."""250        try:251            length = self.headers.getheader("content-length") or "0"252            try:253                length = int(length)254            except:255                length = 0256            content = self.rfile.read(length)257            fields = parse_query(content)258            f = self.send_head(fields)259            if f:260                self.copyfile(f, self.wfile)261                f.close()262        except Exception as e:263            self.handle_exception(e)264 265    def log_message(self, format, *args):266        if self.server.options.debug:267            sys.stderr.write(268                "%s: SERVER: %s - - [%s] %s\n"269                % (270                    sys.argv[0],271                    self.address_string(),272                    self.log_date_time_string(),273                    format % args,274                )275            )276 277    def load_report(self, report):278        path = os.path.join(self.server.root, "report-%s.html" % report)279        data = open(path).read()280        keys = {}281        for item in kBugKeyValueRE.finditer(data):282            k, v = item.groups()283            keys[k] = v284        return keys285 286    def load_crashes(self):287        path = posixpath.join(self.server.root, "index.html")288        data = open(path).read()289        problems = []290        for item in kReportCrashEntryRE.finditer(data):291            fieldData = item.group(1)292            fields = dict(293                [i.groups() for i in kReportCrashEntryKeyValueRE.finditer(fieldData)]294            )295            problems.append(fields)296        return problems297 298    def handle_exception(self, exc):299        import traceback300 301        s = StringIO()302        print("INTERNAL ERROR\n", file=s)303        traceback.print_exc(file=s)304        f = self.send_string(s.getvalue(), "text/plain")305        if f:306            self.copyfile(f, self.wfile)307            f.close()308 309    def get_scalar_field(self, name):310        if name in self.fields:311            return self.fields[name][0]312        else:313            return None314 315    def submit_bug(self, c):316        title = self.get_scalar_field("title")317        description = self.get_scalar_field("description")318        report = self.get_scalar_field("report")319        reporterIndex = self.get_scalar_field("reporter")320        files = []321        for fileID in self.fields.get("files", []):322            try:323                i = int(fileID)324            except:325                i = None326            if i is None or i < 0 or i >= len(c.files):327                return (False, "Invalid file ID")328            files.append(c.files[i])329 330        if not title:331            return (False, "Missing title.")332        if not description:333            return (False, "Missing description.")334        try:335            reporterIndex = int(reporterIndex)336        except:337            return (False, "Invalid report method.")338 339        # Get the reporter and parameters.340        reporter = self.server.reporters[reporterIndex]341        parameters = {}342        for o in reporter.getParameters():343            name = "%s_%s" % (reporter.getName(), o.getName())344            if name not in self.fields:345                return (346                    False,347                    'Missing field "%s" for %s report method.'348                    % (name, reporter.getName()),349                )350            parameters[o.getName()] = self.get_scalar_field(name)351 352        # Update config defaults.353        if report != "None":354            self.server.config.set("ScanView", "reporter", reporterIndex)355            for o in reporter.getParameters():356                if o.saveConfigValue():357                    name = o.getName()358                    self.server.config.set(reporter.getName(), name, parameters[name])359 360        # Create the report.361        bug = Reporter.BugReport(title, description, files)362 363        # Kick off a reporting thread.364        t = ReporterThread(bug, reporter, parameters, self.server)365        t.start()366 367        # Wait for thread to die...368        while t.isAlive():369            time.sleep(0.25)370        submitStatus = t.status371 372        return (t.success, t.status)373 374    def send_report_submit(self):375        report = self.get_scalar_field("report")376        c = self.get_report_context(report)377        if c.reportSource is None:378            reportingFor = "Report Crashes > "379            fileBug = (380                """\381<a href="/report_crashes">File Bug</a> > """382                % locals()383            )384        else:385            reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource, report)386            fileBug = '<a href="/report/%s">File Bug</a> > ' % report387        title = self.get_scalar_field("title")388        description = self.get_scalar_field("description")389 390        res, message = self.submit_bug(c)391 392        if res:393            statusClass = "SubmitOk"394            statusName = "Succeeded"395        else:396            statusClass = "SubmitFail"397            statusName = "Failed"398 399        result = (400            """401<head>402  <title>Bug Submission</title>403  <link rel="stylesheet" type="text/css" href="/scanview.css" />404</head>405<body>406<h3>407<a href="/">Summary</a> > 408%(reportingFor)s409%(fileBug)s410Submit</h3>411<form name="form" action="">412<table class="form">413<tr><td>414<table class="form_group">415<tr>416  <td class="form_clabel">Title:</td>417  <td class="form_value">418    <input type="text" name="title" size="50" value="%(title)s" disabled>419  </td>420</tr>421<tr>422  <td class="form_label">Description:</td>423  <td class="form_value">424<textarea rows="10" cols="80" name="description" disabled>425%(description)s426</textarea>427  </td>428</table>429</td></tr>430</table>431</form>432<h1 class="%(statusClass)s">Submission %(statusName)s</h1>433%(message)s434<p>435<hr>436<a href="/">Return to Summary</a>437</body>438</html>"""439            % locals()440        )441        return self.send_string(result)442 443    def send_open_report(self, report):444        try:445            keys = self.load_report(report)446        except IOError:447            return self.send_error(400, "Invalid report.")448 449        file = keys.get("FILE")450        if not file or not posixpath.exists(file):451            return self.send_error(400, 'File does not exist: "%s"' % file)452 453        import startfile454 455        if self.server.options.debug:456            print('%s: SERVER: opening "%s"' % (sys.argv[0], file), file=sys.stderr)457 458        status = startfile.open(file)459        if status:460            res = 'Opened: "%s"' % file461        else:462            res = 'Open failed: "%s"' % file463 464        return self.send_string(res, "text/plain")465 466    def get_report_context(self, report):467        class Context(object):468            pass469 470        if report is None or report == "None":471            data = self.load_crashes()472            # Don't allow empty reports.473            if not data:474                raise ValueError("No crashes detected!")475            c = Context()476            c.title = "clang static analyzer failures"477 478            stderrSummary = ""479            for item in data:480                if "stderr" in item:481                    path = posixpath.join(self.server.root, item["stderr"])482                    if os.path.exists(path):483                        lns = itertools.islice(open(path), 0, 10)484                        stderrSummary += "%s\n--\n%s" % (485                            item.get("src", "<unknown>"),486                            "".join(lns),487                        )488 489            c.description = """\490The clang static analyzer failed on these inputs:491%s492 493STDERR Summary494--------------495%s496""" % (497                "\n".join([item.get("src", "<unknown>") for item in data]),498                stderrSummary,499            )500            c.reportSource = None501            c.navMarkup = "Report Crashes > "502            c.files = []503            for item in data:504                c.files.append(item.get("src", ""))505                c.files.append(posixpath.join(self.server.root, item.get("file", "")))506                c.files.append(507                    posixpath.join(self.server.root, item.get("clangfile", ""))508                )509                c.files.append(posixpath.join(self.server.root, item.get("stderr", "")))510                c.files.append(posixpath.join(self.server.root, item.get("info", "")))511            # Just in case something failed, ignore files which don't512            # exist.513            c.files = [f for f in c.files if os.path.exists(f) and os.path.isfile(f)]514        else:515            # Check that this is a valid report.516            path = posixpath.join(self.server.root, "report-%s.html" % report)517            if not posixpath.exists(path):518                raise ValueError("Invalid report ID")519            keys = self.load_report(report)520            c = Context()521            c.title = keys.get("DESC", "clang error (unrecognized")522            c.description = """\523Bug reported by the clang static analyzer.524 525Description: %s526File: %s527Line: %s528""" % (529                c.title,530                keys.get("FILE", "<unknown>"),531                keys.get("LINE", "<unknown>"),532            )533            c.reportSource = "report-%s.html" % report534            c.navMarkup = """<a href="/%s">Report %s</a> > """ % (535                c.reportSource,536                report,537            )538 539            c.files = [path]540        return c541 542    def send_report(self, report, configOverrides=None):543        def getConfigOption(section, field):544            if (545                configOverrides is not None546                and section in configOverrides547                and field in configOverrides[section]548            ):549                return configOverrides[section][field]550            return self.server.config.get(section, field)551 552        # report is None is used for crashes553        try:554            c = self.get_report_context(report)555        except ValueError as e:556            return self.send_error(400, e.message)557 558        title = c.title559        description = c.description560        reportingFor = c.navMarkup561        if c.reportSource is None:562            extraIFrame = ""563        else:564            extraIFrame = """\565<iframe src="/%s" width="100%%" height="40%%"566        scrolling="auto" frameborder="1">567  <a href="/%s">View Bug Report</a>568</iframe>""" % (569                c.reportSource,570                c.reportSource,571            )572 573        reporterSelections = []574        reporterOptions = []575 576        try:577            active = int(getConfigOption("ScanView", "reporter"))578        except:579            active = 0580        for i, r in enumerate(self.server.reporters):581            selected = i == active582            if selected:583                selectedStr = " selected"584            else:585                selectedStr = ""586            reporterSelections.append(587                '<option value="%d"%s>%s</option>' % (i, selectedStr, r.getName())588            )589            options = "\n".join(590                [o.getHTML(r, title, getConfigOption) for o in r.getParameters()]591            )592            display = ("none", "")[selected]593            reporterOptions.append(594                """\595<tr id="%sReporterOptions" style="display:%s">596  <td class="form_label">%s Options</td>597  <td class="form_value">598    <table class="form_inner_group">599%s600    </table>601  </td>602</tr>603"""604                % (r.getName(), display, r.getName(), options)605            )606        reporterSelections = "\n".join(reporterSelections)607        reporterOptionsDivs = "\n".join(reporterOptions)608        reportersArray = "[%s]" % (609            ",".join([repr(r.getName()) for r in self.server.reporters])610        )611 612        if c.files:613            fieldSize = min(5, len(c.files))614            attachFileOptions = "\n".join(615                [616                    """\617<option value="%d" selected>%s</option>"""618                    % (i, v)619                    for i, v in enumerate(c.files)620                ]621            )622            attachFileRow = """\623<tr>624  <td class="form_label">Attach:</td>625  <td class="form_value">626<select style="width:100%%" name="files" multiple size=%d>627%s628</select>629  </td>630</tr>631""" % (632                min(5, len(c.files)),633                attachFileOptions,634            )635        else:636            attachFileRow = ""637 638        result = (639            """<html>640<head>641  <title>File Bug</title>642  <link rel="stylesheet" type="text/css" href="/scanview.css" />643</head>644<script language="javascript" type="text/javascript">645var reporters = %(reportersArray)s;646function updateReporterOptions() {647  index = document.getElementById('reporter').selectedIndex;648  for (var i=0; i < reporters.length; ++i) {649    o = document.getElementById(reporters[i] + "ReporterOptions");650    if (i == index) {651      o.style.display = "";652    } else {653      o.style.display = "none";654    }655  }656}657</script>658<body onLoad="updateReporterOptions()">659<h3>660<a href="/">Summary</a> > 661%(reportingFor)s662File Bug</h3>663<form name="form" action="/report_submit" method="post">664<input type="hidden" name="report" value="%(report)s">665 666<table class="form">667<tr><td>668<table class="form_group">669<tr>670  <td class="form_clabel">Title:</td>671  <td class="form_value">672    <input type="text" name="title" size="50" value="%(title)s">673  </td>674</tr>675<tr>676  <td class="form_label">Description:</td>677  <td class="form_value">678<textarea rows="10" cols="80" name="description">679%(description)s680</textarea>681  </td>682</tr>683 684%(attachFileRow)s685 686</table>687<br>688<table class="form_group">689<tr>690  <td class="form_clabel">Method:</td>691  <td class="form_value">692    <select id="reporter" name="reporter" onChange="updateReporterOptions()">693    %(reporterSelections)s694    </select>695  </td>696</tr>697%(reporterOptionsDivs)s698</table>699<br>700</td></tr>701<tr><td class="form_submit">702  <input align="right" type="submit" name="Submit" value="Submit">703</td></tr>704</table>705</form>706 707%(extraIFrame)s708 709</body>710</html>"""711            % locals()712        )713 714        return self.send_string(result)715 716    def send_head(self, fields=None):717        if self.server.options.onlyServeLocal and self.client_address[0] != "127.0.0.1":718            return self.send_error(401, "Unauthorized host.")719 720        if fields is None:721            fields = {}722        self.fields = fields723 724        o = urlparse(self.path)725        self.fields = parse_query(o.query, fields)726        path = posixpath.normpath(unquote(o.path))727 728        # Split the components and strip the root prefix.729        components = path.split("/")[1:]730 731        # Special case some top-level entries.732        if components:733            name = components[0]734            if len(components) == 2:735                if name == "report":736                    return self.send_report(components[1])737                elif name == "open":738                    return self.send_open_report(components[1])739            elif len(components) == 1:740                if name == "quit":741                    self.server.halt()742                    return self.send_string("Goodbye.", "text/plain")743                elif name == "report_submit":744                    return self.send_report_submit()745                elif name == "report_crashes":746                    overrides = {"ScanView": {}, "Radar": {}, "Email": {}}747                    for i, r in enumerate(self.server.reporters):748                        if r.getName() == "Radar":749                            overrides["ScanView"]["reporter"] = i750                            break751                    overrides["Radar"]["Component"] = "llvm - checker"752                    overrides["Radar"]["Component Version"] = "X"753                    return self.send_report(None, overrides)754                elif name == "favicon.ico":755                    return self.send_path(posixpath.join(kShare, "bugcatcher.ico"))756 757        # Match directory entries.758        if components[-1] == "":759            components[-1] = "index.html"760 761        relpath = "/".join(components)762        path = posixpath.join(self.server.root, relpath)763 764        if self.server.options.debug > 1:765            print(766                '%s: SERVER: sending path "%s"' % (sys.argv[0], path), file=sys.stderr767            )768        return self.send_path(path)769 770    def send_404(self):771        self.send_error(404, "File not found")772        return None773 774    def send_path(self, path):775        # If the requested path is outside the root directory, do not open it776        rel = os.path.abspath(path)777        if not rel.startswith(os.path.abspath(self.server.root)):778            return self.send_404()779 780        ctype = self.guess_type(path)781        if ctype.startswith("text/"):782            # Patch file instead783            return self.send_patched_file(path, ctype)784        else:785            mode = "rb"786        try:787            f = open(path, mode)788        except IOError:789            return self.send_404()790        return self.send_file(f, ctype)791 792    def send_file(self, f, ctype):793        # Patch files to add links, but skip binary files.794        self.send_response(200)795        self.send_header("Content-type", ctype)796        fs = os.fstat(f.fileno())797        self.send_header("Content-Length", str(fs[6]))798        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))799        self.end_headers()800        return f801 802    def send_string(self, s, ctype="text/html", headers=True, mtime=None):803        encoded_s = s.encode("utf-8")804        if headers:805            self.send_response(200)806            self.send_header("Content-type", ctype)807            self.send_header("Content-Length", str(len(encoded_s)))808            if mtime is None:809                mtime = self.dynamic_mtime810            self.send_header("Last-Modified", self.date_time_string(mtime))811            self.end_headers()812        return BytesIO(encoded_s)813 814    def send_patched_file(self, path, ctype):815        # Allow a very limited set of variables. This is pretty gross.816        variables = {}817        variables["report"] = ""818        m = kReportFileRE.match(path)819        if m:820            variables["report"] = m.group(2)821 822        try:823            f = open(path, "rb")824        except IOError:825            return self.send_404()826        fs = os.fstat(f.fileno())827        data = f.read().decode("utf-8")828        for a, b in kReportReplacements:829            data = a.sub(b % variables, data)830        return self.send_string(data, ctype, mtime=fs.st_mtime)831 832 833def create_server(address, options, root):834    import Reporter835 836    reporters = Reporter.getReporters()837 838    return ScanViewServer(address, ScanViewRequestHandler, root, reporters, options)839