aboutsummaryrefslogtreecommitdiff
path: root/gdb/contrib/exsummary.py
blob: dcf2b76ca3aba99e5b91e53f852b8943176b3ff2 (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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#   Copyright 2011-2017 Free Software Foundation, Inc.
#
#   This is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see
#   <http://www.gnu.org/licenses/>.

import sys
import glob

# Compute the summary information from the files created by
# excheck.py.  Run in the build directory where you used the
# excheck.py plugin.

class Function:
    def __init__(self, name):
        self.name = name
        self.location = None
        self.callers = []
        self.can_throw = False
        self.marked_nothrow = False
        self.reason = None

    def log(self, message):
        print "%s: note: %s" % (self.location, message)

    def set_location(self, location):
        self.location = location

    # CALLER is an Edge.
    def add_caller(self, caller):
        # self.log("adding call from %s" % caller.from_fn.name)
        self.callers.append(caller)
        # self.log("len = %d" % len(self.callers))

    def consistency_check(self):
        if self.marked_nothrow and self.can_throw:
            print ("%s: error: %s marked as both 'throw' and 'nothrow'"
                   % (self.location, self.name))

    def declare_nothrow(self):
        self.marked_nothrow = True
        self.consistency_check()

    def declare_throw(self):
        result = not self.can_throw # Return True the first time
        self.can_throw = True
        self.consistency_check()
        return result

    def print_stack(self, is_indirect):
        if is_indirect:
            print ("%s: error: function %s is marked nothrow but is assumed to throw due to indirect call"
                   % (self.location, self.name))
        else:
            print ("%s: error: function %s is marked nothrow but can throw"
                   % (self.location, self.name))

        edge = self.reason
        while edge is not None:
            print ("%s: info: via call to %s"
                   % (edge.location, edge.to_fn.name))
            edge = edge.to_fn.reason

    def mark_throw(self, edge, work_list, is_indirect):
        if not self.can_throw:
            # self.log("can throw")
            self.can_throw = True
            self.reason = edge
            if self.marked_nothrow:
                self.print_stack(is_indirect)
            else:
                # Do this in the 'else' to avoid extra error
                # propagation.
                work_list.append(self)

class Edge:
    def __init__(self, from_fn, to_fn, location):
        self.from_fn = from_fn
        self.to_fn = to_fn
        self.location = location

# Work list of known-throwing functions.
work_list = []
# Map from function name to Function object.
function_map = {}
# Work list of indirect calls.
indirect_functions = []
# Whether we should process cleanup functions as well.
process_cleanups = False
# Whether we should process indirect function calls.
process_indirect = False

def declare(fn_name):
    global function_map
    if fn_name not in function_map:
        function_map[fn_name] = Function(fn_name)
    return function_map[fn_name]

def define_function(fn_name, location):
    fn = declare(fn_name)
    fn.set_location(location)

def declare_throw(fn_name):
    global work_list
    fn = declare(fn_name)
    if fn.declare_throw():
        work_list.append(fn)

def declare_nothrow(fn_name):
    fn = declare(fn_name)
    fn.declare_nothrow()

def declare_cleanup(fn_name):
    global process_cleanups
    fn = declare(fn_name)
    if process_cleanups:
        fn.declare_nothrow()

def function_call(to, frm, location):
    to_fn = declare(to)
    frm_fn = declare(frm)
    to_fn.add_caller(Edge(frm_fn, to_fn, location))

def has_indirect_call(fn_name, location):
    global indirect_functions
    fn = declare(fn_name)
    phony = Function("<indirect call>")
    phony.add_caller(Edge(fn, phony, location))
    indirect_functions.append(phony)

def mark_functions(worklist, is_indirect):
    for callee in worklist:
        for edge in callee.callers:
            edge.from_fn.mark_throw(edge, worklist, is_indirect)

def help_and_exit():
    print "Usage: exsummary [OPTION]..."
    print ""
    print "Read the .py files from the exception checker plugin and"
    print "generate an error summary."
    print ""
    print "  --cleanups     Include invalid behavior in cleanups"
    print "  --indirect     Include assumed errors due to indirect function calls"
    sys.exit(0)

def main():
    global work_list
    global indirect_functions
    global process_cleanups
    global process_indirect

    for arg in sys.argv:
        if arg == '--cleanups':
            process_cleanups = True
        elif arg == '--indirect':
            process_indirect = True
        elif arg == '--help':
            help_and_exit()

    for fname in sorted(glob.glob('*.c.gdb_exc.py')):
        execfile(fname)
    print "================"
    print "= Ordinary marking"
    print "================"
    mark_functions(work_list, False)
    if process_indirect:
        print "================"
        print "= Indirect marking"
        print "================"
        mark_functions(indirect_functions, True)
    return 0

if __name__ == '__main__':
    status = main()
    sys.exit(status)