Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler's waitPromise soft-lock if promise handlers executed immediately #184

Closed
FurryR opened this issue Jan 17, 2024 · 2 comments · Fixed by #185
Closed

Compiler's waitPromise soft-lock if promise handlers executed immediately #184

FurryR opened this issue Jan 17, 2024 · 2 comments · Fixed by #185

Comments

@FurryR
Copy link

FurryR commented Jan 17, 2024

Expected Behavior

in src/engine/execute.js::handlePromise, L115

const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached, lastOperation) => {
    if (thread.status === Thread.STATUS_RUNNING) {
        // Primitive returned a promise; automatically yield thread.
        thread.status = Thread.STATUS_PROMISE_WAIT; // NOTE: Status is configured **before** registering promise handlers.
    }
    // Promise handlers
    primitiveReportedValue.then(resolvedValue => {
        // ...
    }, rejectionReason => {
        // ...
    });
};

Actual Behavior

in src/compiler/jsexecute.js::isPromise, L131

const isPromise = value => (
    // see engine/execute.js
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
); // NOTE: value.catch is not detected. It should not be used either.

in src/compiler/jsexecute.js::waitPromise, L108

const waitPromise = function*(promise) {
    const thread = globalState.thread;
    let returnValue;

    promise
        .then(value => {
            returnValue = value;
            thread.status = 0; // STATUS_RUNNING
        })
        .catch(error => {
            thread.status = 0; // STATUS_RUNNING
            globalState.log.warn('Promise rejected in compiled script:', error);
        }); // NOTE: `catch` might be `undefined`.

    // enter STATUS_PROMISE_WAIT and yield
    // this will stop script execution until the promise handlers reset the thread status
    thread.status = 1; // STATUS_PROMISE_WAIT // NOTE: if promise handlers executed immediately, enter STATUS_PROMISE_WAIT here will cause soft-lock.
    yield;

    return returnValue;
};

Steps to Reproduce

in exploitExtension.js

(function (Scratch) {
  if (!Scratch.extensions.unsandboxed)
      throw new Error('Load this extension as unsandboxed extension');
  class Extension {
      getInfo () {
          return {
              id: 'exploit',
              name: 'Exploit',
              color1: '#000000',
              blocks: [
                  {
                      opcode: 'hack',
                      blockType: Scratch.BlockType.COMMAND,
                      text: 'Exploit'
                  }
              ]
          };
      }
      hack () {
          // returns a PromiseLike that calls handler immediately.
          const pm = {
              then (callbackFn) {
                  callbackFn();
                  return pm;
              },
              catch () {
                  return pm;
              }
          };
          return pm;
      }
  };
  Scratch.extensions.register(new Extension());
})(Scratch);

Operating System and Browser

N/A

@FurryR
Copy link
Author

FurryR commented Jan 17, 2024

Related issue: scratchfoundation#4197

@FurryR
Copy link
Author

FurryR commented Jan 24, 2024

This is resolved by #185.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant