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