Skip to content

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

Closed
@FurryR

Description

@FurryR

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions