1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Nodes for try/except/finally handling.
19
20This is the unified low level solution to trying a block, and executing code
21when it returns, break, continues, or raises an exception. See Developer
22Manual for how this maps to try/finally and try/except as in Python.
23"""
24
25from nuitka.Errors import NuitkaOptimizationError
26from nuitka.optimizations.TraceCollections import TraceCollectionBranch
27
28from .Checkers import checkStatementsSequence, checkStatementsSequenceOrNone
29from .NodeBases import StatementChildrenHavingBase
30from .StatementNodes import StatementsSequence
31
32
33class StatementTry(StatementChildrenHavingBase):
34    kind = "STATEMENT_TRY"
35
36    named_children = (
37        "tried",
38        "except_handler",
39        "break_handler",
40        "continue_handler",
41        "return_handler",
42    )
43
44    checkers = {
45        "tried": checkStatementsSequence,
46        "except_handler": checkStatementsSequenceOrNone,
47        "break_handler": checkStatementsSequenceOrNone,
48        "continue_handler": checkStatementsSequenceOrNone,
49        "return_handler": checkStatementsSequenceOrNone,
50    }
51
52    def __init__(
53        self,
54        tried,
55        except_handler,
56        break_handler,
57        continue_handler,
58        return_handler,
59        source_ref,
60    ):
61        StatementChildrenHavingBase.__init__(
62            self,
63            values={
64                "tried": tried,
65                "except_handler": except_handler,
66                "break_handler": break_handler,
67                "continue_handler": continue_handler,
68                "return_handler": return_handler,
69            },
70            source_ref=source_ref,
71        )
72
73    def computeStatement(self, trace_collection):
74        # This node has many children to handle, pylint: disable=I0021,too-many-branches,too-many-locals,too-many-statements
75        tried = self.subnode_tried
76
77        except_handler = self.subnode_except_handler
78        break_handler = self.subnode_break_handler
79        continue_handler = self.subnode_continue_handler
80        return_handler = self.subnode_return_handler
81
82        # The tried block must be considered as a branch, if it is not empty
83        # already.
84        collection_start = TraceCollectionBranch(
85            parent=trace_collection, name="try start"
86        )
87
88        abort_context = trace_collection.makeAbortStackContext(
89            catch_breaks=break_handler is not None,
90            catch_continues=continue_handler is not None,
91            catch_returns=return_handler is not None,
92            catch_exceptions=True,
93        )
94
95        with abort_context:
96            # As a branch point for the many types of handlers.
97
98            result = tried.computeStatementsSequence(trace_collection=trace_collection)
99
100            # We might be done entirely already.
101            if result is None:
102                return None, "new_statements", "Removed now empty try statement."
103
104            # Might be changed.
105            if result is not tried:
106                self.setChild("tried", result)
107                tried = result
108
109            break_collections = trace_collection.getLoopBreakCollections()
110            continue_collections = trace_collection.getLoopContinueCollections()
111            return_collections = trace_collection.getFunctionReturnCollections()
112            exception_collections = trace_collection.getExceptionRaiseCollections()
113
114        tried_may_raise = tried.mayRaiseException(BaseException)
115        # Exception handling is useless if no exception is to be raised.
116        if not tried_may_raise:
117            if except_handler is not None:
118                except_handler.finalize()
119
120                self.clearChild("except_handler")
121                trace_collection.signalChange(
122                    tags="new_statements",
123                    message="Removed useless exception handler.",
124                    source_ref=except_handler.source_ref,
125                )
126
127                except_handler = None
128
129        # If tried may raise, even empty exception handler has a meaning to
130        # ignore that exception.
131        if tried_may_raise:
132            collection_exception_handling = TraceCollectionBranch(
133                parent=collection_start, name="except handler"
134            )
135
136            # When no exception exits are there, this is a problem, we just
137            # found an inconsistency that is a bug.
138            if not exception_collections:
139                for statement in tried.subnode_statements:
140                    if statement.mayRaiseException(BaseException):
141                        raise NuitkaOptimizationError(
142                            "This statement does raise but didn't annotate an exception exit.",
143                            statement,
144                        )
145
146                raise NuitkaOptimizationError(
147                    "Falsely assuming tried block may raise, but no statement says so.",
148                    tried,
149                )
150
151            collection_exception_handling.mergeMultipleBranches(exception_collections)
152
153            if except_handler is not None:
154                result = except_handler.computeStatementsSequence(
155                    trace_collection=collection_exception_handling
156                )
157
158                # Might be changed.
159                if result is not except_handler:
160                    self.setChild("except_handler", result)
161                    except_handler = result
162
163        if break_handler is not None:
164            if not tried.mayBreak():
165                break_handler.finalize()
166
167                self.clearChild("break_handler")
168                break_handler = None
169
170        if break_handler is not None:
171            collection_break = TraceCollectionBranch(
172                parent=collection_start, name="break handler"
173            )
174
175            collection_break.mergeMultipleBranches(break_collections)
176
177            result = break_handler.computeStatementsSequence(
178                trace_collection=collection_break
179            )
180
181            # Might be changed.
182            if result is not break_handler:
183                self.setChild("break_handler", result)
184                break_handler = result
185
186        if continue_handler is not None:
187            if not tried.mayContinue():
188                continue_handler.finalize()
189
190                self.clearChild("continue_handler")
191                continue_handler = None
192
193        if continue_handler is not None:
194            collection_continue = TraceCollectionBranch(
195                parent=collection_start, name="continue handler"
196            )
197
198            collection_continue.mergeMultipleBranches(continue_collections)
199
200            result = continue_handler.computeStatementsSequence(
201                trace_collection=collection_continue
202            )
203
204            # Might be changed.
205            if result is not continue_handler:
206                self.setChild("continue_handler", result)
207                continue_handler = result
208
209        if return_handler is not None:
210            if not tried.mayReturn():
211                return_handler.finalize()
212
213                self.clearChild("return_handler")
214                return_handler = None
215
216        if return_handler is not None:
217            collection_return = TraceCollectionBranch(
218                parent=collection_start, name="return handler"
219            )
220
221            collection_return.mergeMultipleBranches(return_collections)
222
223            result = return_handler.computeStatementsSequence(
224                trace_collection=collection_return
225            )
226
227            # Might be changed.
228            if result is not return_handler:
229                self.setChild("return_handler", result)
230                return_handler = result
231
232        # Check for trivial return handlers that immediately return, they can
233        # just be removed.
234        if return_handler is not None:
235            if return_handler.subnode_statements[0].isStatementReturnReturnedValue():
236                return_handler.finalize()
237
238                self.clearChild("return_handler")
239                return_handler = None
240
241        # Merge exception handler only if it is used. Empty means it is not
242        # aborting, as it swallows the exception.
243        if tried_may_raise and (
244            except_handler is None or not except_handler.isStatementAborting()
245        ):
246            trace_collection.mergeBranches(
247                collection_yes=collection_exception_handling, collection_no=None
248            )
249
250        # An empty exception handler means we have to swallow exception.
251        if (
252            (
253                not tried_may_raise
254                or (
255                    except_handler is not None
256                    and except_handler.subnode_statements[
257                        0
258                    ].isStatementReraiseException()
259                )
260            )
261            and break_handler is None
262            and continue_handler is None
263            and return_handler is None
264        ):
265            return tried, "new_statements", "Removed useless try, all handlers removed."
266
267        tried_statements = tried.subnode_statements
268
269        pre_statements = []
270
271        while tried_statements:
272            tried_statement = tried_statements[0]
273
274            if tried_statement.mayRaiseException(BaseException):
275                break
276
277            if break_handler is not None and tried_statement.mayBreak():
278                break
279
280            if continue_handler is not None and tried_statement.mayContinue():
281                break
282
283            if return_handler is not None and tried_statement.mayReturn():
284                break
285
286            pre_statements.append(tried_statement)
287            tried_statements = list(tried_statements)
288
289            del tried_statements[0]
290
291        post_statements = []
292
293        if except_handler is not None and except_handler.isStatementAborting():
294            while tried_statements:
295                tried_statement = tried_statements[-1]
296
297                if tried_statement.mayRaiseException(BaseException):
298                    break
299
300                if break_handler is not None and tried_statement.mayBreak():
301                    break
302
303                if continue_handler is not None and tried_statement.mayContinue():
304                    break
305
306                if return_handler is not None and tried_statement.mayReturn():
307                    break
308
309                post_statements.insert(0, tried_statement)
310                tried_statements = list(tried_statements)
311
312                del tried_statements[-1]
313
314        if pre_statements or post_statements:
315            assert tried_statements  # Should be dealt with already
316
317            tried.setChild("statements", tried_statements)
318
319            result = StatementsSequence(
320                statements=pre_statements + [self] + post_statements,
321                source_ref=self.source_ref,
322            )
323
324            def explain():
325                # TODO: We probably don't want to say this for re-formulation ones.
326                result = "Reduced scope of tried block."
327
328                if pre_statements:
329                    result += " Leading statements at %s." % (
330                        ",".join(
331                            x.getSourceReference().getAsString() + "/" + str(x)
332                            for x in pre_statements
333                        )
334                    )
335
336                if post_statements:
337                    result += " Trailing statements at %s." % (
338                        ",".join(
339                            x.getSourceReference().getAsString() + "/" + str(x)
340                            for x in post_statements
341                        )
342                    )
343
344                return result
345
346            return (result, "new_statements", explain)
347
348        return self, None, None
349
350    def mayReturn(self):
351        # TODO: If we optimized return handler away, this would be not needed
352        # or even non-optimal.
353        if self.subnode_tried.mayReturn():
354            return True
355
356        except_handler = self.subnode_except_handler
357
358        if except_handler is not None and except_handler.mayReturn():
359            return True
360
361        break_handler = self.subnode_break_handler
362
363        if break_handler is not None and break_handler.mayReturn():
364            return True
365
366        continue_handler = self.subnode_continue_handler
367
368        if continue_handler is not None and continue_handler.mayReturn():
369            return True
370
371        return_handler = self.subnode_return_handler
372
373        if return_handler is not None and return_handler.mayReturn():
374            return True
375
376        return False
377
378    def mayBreak(self):
379        # TODO: If we optimized return handler away, this would be not needed
380        # or even non-optimal.
381        if self.subnode_tried.mayBreak():
382            return True
383
384        except_handler = self.subnode_except_handler
385
386        if except_handler is not None and except_handler.mayBreak():
387            return True
388
389        break_handler = self.subnode_break_handler
390
391        if break_handler is not None and break_handler.mayBreak():
392            return True
393
394        continue_handler = self.subnode_continue_handler
395
396        if continue_handler is not None and continue_handler.mayBreak():
397            return True
398
399        return_handler = self.subnode_return_handler
400
401        if return_handler is not None and return_handler.mayBreak():
402            return True
403
404        return False
405
406    def mayContinue(self):
407        # TODO: If we optimized return handler away, this would be not needed
408        # or even non-optimal.
409        if self.subnode_tried.mayContinue():
410            return True
411
412        except_handler = self.subnode_except_handler
413
414        if except_handler is not None and except_handler.mayContinue():
415            return True
416
417        break_handler = self.subnode_break_handler
418
419        if break_handler is not None and break_handler.mayContinue():
420            return True
421
422        continue_handler = self.subnode_continue_handler
423
424        if continue_handler is not None and continue_handler.mayContinue():
425            return True
426
427        return_handler = self.subnode_return_handler
428
429        if return_handler is not None and return_handler.mayContinue():
430            return True
431
432        return False
433
434    def isStatementAborting(self):
435        except_handler = self.subnode_except_handler
436
437        if except_handler is None or not except_handler.isStatementAborting():
438            return False
439
440        break_handler = self.subnode_break_handler
441
442        if break_handler is not None and not break_handler.isStatementAborting():
443            return False
444
445        continue_handler = self.subnode_continue_handler
446
447        if continue_handler is not None and not continue_handler.isStatementAborting():
448            return False
449
450        return_handler = self.subnode_return_handler
451
452        if return_handler is not None and not return_handler.isStatementAborting():
453            return False
454
455        return self.subnode_tried.isStatementAborting()
456
457    def mayRaiseException(self, exception_type):
458        tried = self.subnode_tried
459
460        if tried.mayRaiseException(exception_type):
461            except_handler = self.subnode_except_handler
462
463            if except_handler is not None and except_handler.mayRaiseException(
464                exception_type
465            ):
466                return True
467
468        break_handler = self.subnode_break_handler
469
470        if break_handler is not None and break_handler.mayRaiseException(
471            exception_type
472        ):
473            return True
474
475        continue_handler = self.subnode_continue_handler
476
477        if continue_handler is not None and continue_handler.mayRaiseException(
478            exception_type
479        ):
480            return True
481
482        return_handler = self.subnode_return_handler
483
484        if return_handler is not None and return_handler.mayRaiseException(
485            exception_type
486        ):
487            return True
488
489        return False
490
491    def needsFrame(self):
492        except_handler = self.subnode_except_handler
493
494        if except_handler is not None and except_handler.needsFrame():
495            return True
496
497        break_handler = self.subnode_break_handler
498
499        if break_handler is not None and break_handler.needsFrame():
500            return True
501
502        continue_handler = self.subnode_continue_handler
503
504        if continue_handler is not None and continue_handler.needsFrame():
505            return True
506
507        return_handler = self.subnode_return_handler
508
509        if return_handler is not None and return_handler.needsFrame():
510            return True
511
512        return self.subnode_tried.needsFrame()
513
514    @staticmethod
515    def getStatementNiceName():
516        return "tried block statement"
517