summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/explain.py
blob: becfcf17a0935e47eecb3cc2eb81011615f36021 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.


import argparse
import re
from collections import defaultdict

parser = argparse.ArgumentParser(description="Process some integers.")
parser.add_argument("rootingHazards", nargs="?", default="rootingHazards.txt")
parser.add_argument("gcFunctions", nargs="?", default="gcFunctions.txt")
parser.add_argument("hazards", nargs="?", default="hazards.txt")
parser.add_argument("extra", nargs="?", default="unnecessary.txt")
parser.add_argument("refs", nargs="?", default="refs.txt")
args = parser.parse_args()


# Imitate splitFunction from utility.js.
def splitfunc(full):
    idx = full.find("$")
    if idx == -1:
        return (full, full)
    return (full[0:idx], full[idx + 1 :])


num_hazards = 0
num_refs = 0
num_missing = 0

try:
    with open(args.rootingHazards) as rootingHazards, open(
        args.hazards, "w"
    ) as hazards, open(args.extra, "w") as extra, open(args.refs, "w") as refs:
        current_gcFunction = None

        # Map from a GC function name to the list of hazards resulting from
        # that GC function
        hazardousGCFunctions = defaultdict(list)

        # List of tuples (gcFunction, index of hazard) used to maintain the
        # ordering of the hazards
        hazardOrder = []

        # Map from a hazardous GC function to the filename containing it.
        fileOfFunction = {}

        for line in rootingHazards:
            m = re.match(r"^Time: (.*)", line)
            mm = re.match(r"^Run on:", line)
            if m or mm:
                print(line, file=hazards)
                print(line, file=extra)
                print(line, file=refs)
                continue

            m = re.match(r"^Function.*has unnecessary root", line)
            if m:
                print(line, file=extra)
                continue

            m = re.match(r"^Function.*takes unsafe address of unrooted", line)
            if m:
                num_refs += 1
                print(line, file=refs)
                continue

            m = re.match(
                r"^Function.*has unrooted.*of type.*live across GC call '(.*?)' at (\S+):\d+$",
                line,
            )  # NOQA: E501
            if m:
                # Replace mangled$unmangled with just the unmangled part in the output.
                current_gcFunction = m.group(1)
                _, readable = splitfunc(current_gcFunction)
                hazardousGCFunctions[current_gcFunction].append(
                    line.replace(current_gcFunction, readable)
                )
                hazardOrder.append(
                    (
                        current_gcFunction,
                        len(hazardousGCFunctions[current_gcFunction]) - 1,
                    )
                )
                num_hazards += 1
                fileOfFunction[current_gcFunction] = m.group(2)
                continue

            m = re.match(r"Function.*expected hazard.*but none were found", line)
            if m:
                num_missing += 1
                print(line + "\n", file=hazards)
                continue

            if current_gcFunction:
                if not line.strip():
                    # Blank line => end of this hazard
                    current_gcFunction = None
                else:
                    hazardousGCFunctions[current_gcFunction][-1] += line

        mangled2full = {}
        with open(args.gcFunctions) as gcFunctions:
            gcExplanations = {}  # gcFunction => stack showing why it can GC

            current_func = None
            explanation = None
            for line in gcFunctions:
                m = re.match(r"^GC Function: (.*)", line)
                if m:
                    if current_func:
                        gcExplanations[current_func] = explanation
                    current_func = m.group(1)
                    mangled, _ = splitfunc(current_func)
                    mangled2full[mangled] = current_func
                    explanation = line
                elif current_func:
                    explanation += line
            if current_func:
                gcExplanations[current_func] = explanation

        for gcFunction, index in hazardOrder:
            gcHazards = hazardousGCFunctions[gcFunction]

            if gcFunction in gcExplanations:
                key = gcFunction
            else:
                # Mangled constructor/destructor names can map to multiple
                # unmangled names. We have both here, and the unmangled name
                # seen here in the caller may not match the unmangled name in
                # the callee, so if we don't find the full function then key
                # off of the mangled name instead.
                #
                # Normally the analysis tries to use the mangled name for
                # identity comparison, but here we're processing human-readable
                # output. Perhaps a better solution might be to treat the
                # rootingHazards.txt input here as internal and only list
                # mangled names, expanding them to unmangled names when
                # producing hazards.txt and the other output files.
                mangled, _ = splitfunc(gcFunction)
                key = mangled2full[mangled]

            if key in gcExplanations:
                print(gcHazards[index] + gcExplanations[key], file=hazards)
            else:
                print(gcHazards[index], file=hazards)

except IOError as e:
    print("Failed: %s" % str(e))

print("Wrote %s" % args.hazards)
print("Wrote %s" % args.extra)
print("Wrote %s" % args.refs)
print(
    "Found %d hazards %d unsafe references %d missing"
    % (num_hazards, num_refs, num_missing)
)