================
@@ -2000,6 +2004,80 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt 
&S,
   EmitStmt(S.getSubStmt());
 }
 
+namespace {
+struct EmitDeferredStatement final : EHScopeStack::Cleanup {
+  const DeferStmt &Stmt;
+  EmitDeferredStatement(const DeferStmt *Stmt) : Stmt(*Stmt) {}
+
+  void Emit(CodeGenFunction &CGF, Flags) override {
+    // Take care that any cleanups pushed by the body of the 'defer' don't
+    // clobber the current cleanup slot value.
+    //
+    // This situation warrants some explanation: Assume we have a scope that
+    // pushes a cleanup; when that scope is exited, we need to run that 
cleanup;
+    // this is accomplished by emitting the cleanup into a separate block and
+    // then branching to that block at scope exit.
+    //
+    // Where this gets complicated is if we exit the scope in multiple 
different
+    // ways; e.g. in a 'for' loop, we may exit the scope of its body by falling
+    // off the end (in which case we need to run the cleanup and then branch to
+    // the increment), or by 'break'ing out of the loop (in which case we need
+    // to run the cleanup and then branch to the loop exit block); in both 
cases
+    // we first branch to the cleanup block to run the cleanup, but the block 
we
+    // need to jump to *after* running the cleanup is different.
+    //
+    // This is accomplished using a local integer variable called the 'cleanup
+    // slot': before branching to the cleanup block, we store a value into that
+    // slot. Then, in the cleanup block, after running the cleanup, we load the
+    // value of that variable and 'switch' on it to branch to the appropriate
+    // continuation block.
+    //
+    // The problem that arises once 'defer' statements are involved is that the
+    // body of a 'defer' is an arbitrary statement which itself can create more
+    // cleanups. This means we may end up overwriting the cleanup slot before 
we
+    // ever have a chance to 'switch' on it, which means that once we *do* get
+    // to the 'switch', we end up in whatever block the cleanup code happened 
to
+    // pick as the default 'switch' exit label!
+    //
+    // That is, what is normally supposed to happen is something like:
+    //
+    //   1. Store 'X' to cleanup slot.
+    //   2. Branch to cleanup block.
+    //   3. Execute cleanup.
+    //   4. Read value from cleanup slot.
+    //   5. Branch to the block associated with 'X'.
+    //
+    // But if we encounter a 'defer' statement that contains a cleanup, then
+    // what might instead happen is:
+    //
+    //   1. Store 'X' to cleanup slot.
+    //   2. Branch to cleanup block.
+    //   3. Execute cleanup; this ends up pushing another cleanup, so:
+    //       3a. Store 'Y' to cleanup slot.
+    //       3b. Run steps 2–5 recursively.
+    //   4. Read value from cleanup slot, which is now 'Y' instead of 'X'.
+    //   5. Branch to the block associated with 'Y'... which doesn't even
+    //      exist because the value 'Y' is only meaningful for the inner
+    //      cleanup. The result is we just branch 'somewhere random'.
+    //
+    // The rest of the cleanup code simply isn't prepared to handle this case
+    // because there are no other cleanups that can push more cleanups, and
+    // thus, emitting any other cleanup cannot clobber the cleanup slot.
+    //
+    // To prevent this from happening, simply force the allocation of a new
+    // cleanup slot for the body of the defer statement by temporarily clearing
+    // out any existing slot.
+    SaveAndRestore PreserveCleanupSlot{CGF.NormalCleanupDest,
+                                       RawAddress::invalid()};
+    CGF.EmitStmt(Stmt.getBody());
----------------
cor3ntin wrote:

@erichkeane can you have a look at that bit?

https://github.com/llvm/llvm-project/pull/162848
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to