Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Fix composed completion undo #2467

Closed

Conversation

calebmer
Copy link
Contributor

@calebmer calebmer commented Aug 22, 2018

Fixes #2458.

This is a new version of my change in #2459. I closed that PR since @hermanventer had doubts which I confirmed after discovering my “fix” crashed my React Native internal bundle in a different way demonstrating its incorrectness.

After thinking about how to workaround this issue (since I was blocked on this) and fixing related issue #2466 I think I have a proper fix and I’m ready to defend it.

Note that this PR is based on top of #2466. The changes in this commit are the ones to look at.

The failing test case is the following. Annotated with some comments to help demonstrate the issue:

if (!global.__evaluatePureFunction) global.__evaluatePureFunction = f => f();

const result = global.__evaluatePureFunction(() => {
  let x, y, z;

  function f() {
    const getNumber = global.__abstract ? global.__abstract("function", "(() => 1)") : () => 1;
    const b1 = global.__abstract ? global.__abstract("boolean", "true") : true;
    const b2 = global.__abstract ? global.__abstract("boolean", "!false") : true;
    const b3 = global.__abstract ? global.__abstract("boolean", "!!true") : true;

    x = getNumber();                        // Modify binding
    if (!b1) throw new Error("abrupt");     // Compose JoinedNormalAndAbruptCompletions
    y = getNumber();                        // Modify binding
    if (!b2) throw new Error("abrupt");     // Compose JoinedNormalAndAbruptCompletions
    z = getNumber();                        // Modify binding
    if (!b3) throw new Error("abrupt");     // Compose JoinedNormalAndAbruptCompletions

    if (global.__fatal) global.__fatal();

    return x + y + z;
  }

  f();

  return x + y + z;
});

global.inspect = () => result;

What is happening is that we modify three bindings in f before we throw a FatalError. x, y, and z. We also have three JoinedNormalAndAbruptCompletions since we’re using abstract booleans and throwing if the boolean is false. Our realm when we throw the FatalError ends up looking like:

Realm {
  savedCompletion: JoinedNormalAndAbruptCompletions {
    savedEffects: { modifiedBindings: { z } },
    composedWith: JoinedNormalAndAbruptCompletions {
      savedEffects: { modifiedBindings: { y } },
      composedWith: JoinedNormalAndAbruptCompletions {
        savedEffects: { modifiedBindings: { x } },
      }
    }
  }
}

When we throw the FatalError we want to restore all our bindings to a state before we started the evaluateForEffects() call so that we can leave a residual call in pure scope. However, the way the code is currently written we don’t look into savedCompletion.composedWith even though there may be more bindings there. Instead we only look at savedCompletion so we only restore the z binding.

My previous fix modified stopEffectCaptureAndUndoEffects() to chase composedWith, but that broke the contract of stopEffectCaptureAndUndoEffects() as @hermanventer mentioned. So instead I updated the finally block in evaluateForEffects() which cleans up modified bindings of a savedCompletion (in case we throw in the middle of evaluation) to more explicitly clean up the bindings of the saved completion and all composed completions. This preserves and fixes our effect cleanup behavior without changing the meaning of stopEffectCaptureAndUndoEffects().

@hermanventer I know you mentioned you wanted to look at this issue, sorry if I’m stepping on your toes. Since I was blocked I thought a bit more about this issue and figured I was directionally correct, but the specific fix in #2459 was wrong. Also sorry for the PR churn. Thought I wasn’t going to come back to this issue.

Copy link
Contributor

@hermanventer hermanventer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks perfect.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calebmer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@calebmer calebmer deleted the composed-completion-undo branch August 27, 2018 16:11
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Composed JoinedNormalAndAbruptCompletions are not undone properly when we bail out
3 participants