1 # Copyright 2013-2017 Free Software Foundation, Inc.
3 # This is free software: you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see
15 # <http://www.gnu.org/licenses/>.
21 want_raii_info
= False
26 def log(msg
, indent
=0):
29 sys
.stderr
.write('%s%s\n' % (' ' * indent
, msg
))
32 def is_cleanup_type(return_type
):
33 if not isinstance(return_type
, gcc
.PointerType
):
35 if not isinstance(return_type
.dereference
, gcc
.RecordType
):
37 if str(return_type
.dereference
.name
) == 'cleanup':
41 def is_constructor(decl
):
42 "Return True if the function DECL is a cleanup constructor; False otherwise"
43 return is_cleanup_type(decl
.type.type) and (not decl
.name
or str(decl
.name
) != 'make_final_cleanup')
45 destructor_names
= set(['do_cleanups', 'discard_cleanups'])
47 def is_destructor(decl
):
48 return decl
.name
in destructor_names
50 # This list is just much too long... we should probably have an
52 special_names
= set(['do_final_cleanups', 'discard_final_cleanups',
53 'save_cleanups', 'save_final_cleanups',
54 'restore_cleanups', 'restore_final_cleanups',
55 'exceptions_state_mc_init',
56 'make_my_cleanup2', 'make_final_cleanup', 'all_cleanups',
57 'save_my_cleanups', 'quit_target'])
59 def needs_special_treatment(decl
):
60 return decl
.name
in special_names
62 # Sometimes we need a new placeholder object that isn't the same as
65 def __init__(self
, location
):
66 self
.location
= location
68 # A wrapper for a cleanup which has been assigned to a variable.
69 # This holds the variable and the location.
70 class Cleanup(object):
71 def __init__(self
, var
, location
):
73 self
.location
= location
75 # A class representing a master cleanup. This holds a stack of
76 # cleanup objects and supports a merging operation.
77 class MasterCleanup(object):
78 # Create a new MasterCleanup object. OTHER, if given, is a
79 # MasterCleanup object to copy.
80 def __init__(self
, other
= None):
81 # 'cleanups' is a list of cleanups. Each element is either a
82 # Dummy, for an anonymous cleanup, or a Cleanup, for a cleanup
83 # which was assigned to a variable.
88 self
.cleanups
= other
.cleanups
[:]
89 self
.aliases
= dict(other
.aliases
)
91 def compare_vars(self
, definition
, argument
):
92 if definition
== argument
:
94 if argument
in self
.aliases
:
95 argument
= self
.aliases
[argument
]
96 if definition
in self
.aliases
:
97 definition
= self
.aliases
[definition
]
98 return definition
== argument
100 def note_assignment(self
, lhs
, rhs
):
101 log('noting assignment %s = %s' % (lhs
, rhs
), 4)
102 self
.aliases
[lhs
] = rhs
104 # Merge with another MasterCleanup.
105 # Returns True if this resulted in a change to our state.
106 def merge(self
, other
):
107 # We do explicit iteration like this so we can easily
108 # update the list after the loop.
111 for counter
in range(len(self
.cleanups
) - 1, -1, -1):
112 var
= self
.cleanups
[counter
]
113 log('merge checking %s' % var
, 4)
114 # Only interested in named cleanups.
115 if isinstance(var
, Dummy
):
116 log('=> merge dummy', 5)
118 # Now see if VAR is found in OTHER.
119 if other
._find
_var
(var
.var
) >= 0:
120 log ('=> merge found', 5)
122 log('=>merge not found', 5)
124 if found_named
and counter
< len(self
.cleanups
) - 1:
125 log ('merging to %d' % counter
, 4)
129 self
.cleanups
= self
.cleanups
[0:counter
]
131 # If SELF is empty but OTHER has some cleanups, then consider
132 # that a change as well.
133 if len(self
.cleanups
) == 0 and len(other
.cleanups
) > 0:
134 log('merging non-empty other', 4)
135 self
.cleanups
= other
.cleanups
[:]
139 # Push a new constructor onto our stack. LHS is the
140 # left-hand-side of the GimpleCall statement. It may be None,
141 # meaning that this constructor's value wasn't used.
142 def push(self
, location
, lhs
):
144 obj
= Dummy(location
)
146 obj
= Cleanup(lhs
, location
)
147 log('pushing %s' % lhs
, 4)
148 idx
= self
._find
_var
(lhs
)
150 gcc
.permerror(location
, 'reassigning to known cleanup')
151 gcc
.inform(self
.cleanups
[idx
].location
,
152 'previous assignment is here')
153 self
.cleanups
.append(obj
)
155 # A helper for merge and pop that finds BACK_TO in self.cleanups,
156 # and returns the index, or -1 if not found.
157 def _find_var(self
, back_to
):
158 for i
in range(len(self
.cleanups
) - 1, -1, -1):
159 if isinstance(self
.cleanups
[i
], Dummy
):
161 if self
.compare_vars(self
.cleanups
[i
].var
, back_to
):
165 # Pop constructors until we find one matching BACK_TO.
166 # This is invoked when we see a do_cleanups call.
167 def pop(self
, location
, back_to
):
169 i
= self
._find
_var
(back_to
)
171 self
.cleanups
= self
.cleanups
[0:i
]
173 gcc
.permerror(location
, 'destructor call with unknown argument')
175 # Check whether ARG is the current master cleanup. Return True if
177 def verify(self
, location
, arg
):
178 log('verify %s' % arg
, 4)
179 return (len(self
.cleanups
) > 0
180 and not isinstance(self
.cleanups
[0], Dummy
)
181 and self
.compare_vars(self
.cleanups
[0].var
, arg
))
183 # Check whether SELF is empty.
185 log('isempty: len = %d' % len(self
.cleanups
), 4)
186 return len(self
.cleanups
) == 0
188 # Emit informational warnings about the cleanup stack.
190 for item
in reversed(self
.cleanups
):
191 gcc
.inform(item
.location
, 'leaked cleanup')
193 class CleanupChecker
:
194 def __init__(self
, fun
):
196 self
.seen_edges
= set()
197 self
.bad_returns
= set()
199 # This maps BB indices to a list of master cleanups for the
201 self
.master_cleanups
= {}
203 # Pick a reasonable location for the basic block BB.
204 def guess_bb_location(self
, bb
):
205 if isinstance(bb
.gimple
, list):
206 for stmt
in bb
.gimple
:
211 # Compute the master cleanup list for BB.
212 # Modifies MASTER_CLEANUP in place.
213 def compute_master(self
, bb
, bb_from
, master_cleanup
):
214 if not isinstance(bb
.gimple
, list):
216 curloc
= self
.fun
.end
217 for stmt
in bb
.gimple
:
220 if isinstance(stmt
, gcc
.GimpleCall
) and stmt
.fndecl
:
221 if is_constructor(stmt
.fndecl
):
222 log('saw constructor %s in bb=%d' % (str(stmt
.fndecl
), bb
.index
), 2)
223 self
.cleanup_aware
= True
224 master_cleanup
.push(curloc
, stmt
.lhs
)
225 elif is_destructor(stmt
.fndecl
):
226 if str(stmt
.fndecl
.name
) != 'do_cleanups':
227 self
.only_do_cleanups_seen
= False
228 log('saw destructor %s in bb=%d, bb_from=%d, argument=%s'
229 % (str(stmt
.fndecl
.name
), bb
.index
, bb_from
, str(stmt
.args
[0])),
231 master_cleanup
.pop(curloc
, stmt
.args
[0])
232 elif needs_special_treatment(stmt
.fndecl
):
234 # gcc.permerror(curloc, 'function needs special treatment')
235 elif isinstance(stmt
, gcc
.GimpleAssign
):
236 if isinstance(stmt
.lhs
, gcc
.VarDecl
) and isinstance(stmt
.rhs
[0], gcc
.VarDecl
):
237 master_cleanup
.note_assignment(stmt
.lhs
, stmt
.rhs
[0])
238 elif isinstance(stmt
, gcc
.GimpleReturn
):
239 if self
.is_constructor
:
240 if not master_cleanup
.verify(curloc
, stmt
.retval
):
241 gcc
.permerror(curloc
,
242 'constructor does not return master cleanup')
243 elif not self
.is_special_constructor
:
244 if not master_cleanup
.isempty():
245 if curloc
not in self
.bad_returns
:
246 gcc
.permerror(curloc
, 'cleanup stack is not empty at return')
247 self
.bad_returns
.add(curloc
)
248 master_cleanup
.inform()
250 # Traverse a basic block, updating the master cleanup information
251 # and propagating to other blocks.
252 def traverse_bbs(self
, edge
, bb
, bb_from
, entry_master
):
253 log('traverse_bbs %d from %d' % (bb
.index
, bb_from
), 1)
255 # Propagate the entry MasterCleanup though this block.
256 master_cleanup
= MasterCleanup(entry_master
)
257 self
.compute_master(bb
, bb_from
, master_cleanup
)
260 if bb
.index
in self
.master_cleanups
:
261 # Merge the newly-computed MasterCleanup into the one we
262 # have already computed. If this resulted in a
263 # significant change, then we need to re-propagate.
264 modified
= self
.master_cleanups
[bb
.index
].merge(master_cleanup
)
266 self
.master_cleanups
[bb
.index
] = master_cleanup
269 # EDGE is None for the entry BB.
271 # If merging cleanups caused a change, check to see if we
273 if edge
in self
.seen_edges
:
274 # This error doesn't really help.
276 # gcc.permerror(self.guess_bb_location(bb),
277 # 'invalid cleanup use in loop')
279 self
.seen_edges
.add(edge
)
284 # Now propagate to successor nodes.
285 for edge
in bb
.succs
:
286 self
.traverse_bbs(edge
, edge
.dest
, bb
.index
, master_cleanup
)
288 def check_cleanups(self
):
289 if not self
.fun
.cfg
or not self
.fun
.decl
:
291 if is_destructor(self
.fun
.decl
):
293 if needs_special_treatment(self
.fun
.decl
):
296 self
.is_constructor
= is_constructor(self
.fun
.decl
)
297 self
.is_special_constructor
= not self
.is_constructor
and str(self
.fun
.decl
.name
).find('with_cleanup') > -1
299 if str(self
.fun
.decl
.name
) == 'gdb_xml_create_parser_and_cleanup_1':
300 self
.is_special_constructor
= True
302 if self
.is_special_constructor
:
303 gcc
.inform(self
.fun
.start
, 'function %s is a special constructor' % (self
.fun
.decl
.name
))
305 # If we only see do_cleanups calls, and this function is not
306 # itself a constructor, then we can convert it easily to RAII.
307 self
.only_do_cleanups_seen
= not self
.is_constructor
308 # If we ever call a constructor, then we are "cleanup-aware".
309 self
.cleanup_aware
= False
311 entry_bb
= self
.fun
.cfg
.entry
312 master_cleanup
= MasterCleanup()
313 self
.traverse_bbs(None, entry_bb
, -1, master_cleanup
)
314 if want_raii_info
and self
.only_do_cleanups_seen
and self
.cleanup_aware
:
315 gcc
.inform(self
.fun
.decl
.location
,
316 'function %s could be converted to RAII' % (self
.fun
.decl
.name
))
317 if self
.is_constructor
:
321 class CheckerPass(gcc
.GimplePass
):
322 def execute(self
, fun
):
324 log("Starting " + fun
.decl
.name
)
326 dot
= gccutils
.cfg_to_dot(fun
.cfg
, fun
.decl
.name
)
327 gccutils
.invoke_dot(dot
, name
=fun
.decl
.name
)
328 checker
= CleanupChecker(fun
)
329 what
= checker
.check_cleanups()
331 log(fun
.decl
.name
+ ': ' + what
, 2)
333 ps
= CheckerPass(name
= 'check-cleanups')
334 # We need the cfg, but we want a relatively high-level Gimple.
335 ps
.register_after('cfg')
This page took 0.038565 seconds and 4 git commands to generate.