bool ExpressionDecompiler::decompilePC()

in js/src/vm/BytecodeUtil.cpp [1664:2163]


bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) {
  MOZ_ASSERT(script->containsPC(pc));

  JSOp op = (JSOp)*pc;

  if (const char* token = CodeToken[uint8_t(op)]) {
    MOZ_ASSERT(defIndex == 0);
    MOZ_ASSERT(CodeSpec(op).ndefs == 1);

    // Handle simple cases of binary and unary operators.
    switch (CodeSpec(op).nuses) {
      case 2: {
        const char* extra = "";

        MOZ_ASSERT(pc + 1 < script->codeEnd(),
                   "binary opcode shouldn't be the last opcode in the script");
        if (CodeSpec(op).length == 1 &&
            (JSOp)(*(pc + 1)) == JSOp::NopIsAssignOp) {
          extra = "=";
        }

        return write("(") && decompilePCForStackOperand(pc, -2) && write(" ") &&
               write(token) && write(extra) && write(" ") &&
               decompilePCForStackOperand(pc, -1) && write(")");
        break;
      }
      case 1:
        return write("(") && write(token) &&
               decompilePCForStackOperand(pc, -1) && write(")");
      default:
        break;
    }
  }

  switch (op) {
    case JSOp::DelName:
      return write("(delete ") && write(loadAtom(pc)) && write(")");

    case JSOp::GetGName:
    case JSOp::GetName:
    case JSOp::GetIntrinsic:
      return write(loadAtom(pc));
    case JSOp::GetArg: {
      unsigned slot = GET_ARGNO(pc);

      // For self-hosted scripts that are called from non-self-hosted code,
      // decompiling the parameter name in the self-hosted script is
      // unhelpful. Decompile the argument name instead.
      if (script->selfHosted()
#ifdef DEBUG
          // For stack dump, argument name is not necessary.
          && !isStackDump
#endif /* DEBUG */
      ) {
        UniqueChars result;
        if (!DecompileArgumentFromStack(cx, slot, &result)) {
          return false;
        }

        // Note that decompiling the argument in the parent frame might
        // not succeed.
        if (result) {
          return write(result.get());
        }

        // If it fails, do not return parameter name and let the caller
        // fallback.
        return write("(intermediate value)");
      }

      JSAtom* atom = getArg(slot);
      if (!atom) {
        return false;
      }
      return write(atom);
    }
    case JSOp::GetLocal: {
      JSAtom* atom = FrameSlotName(script, pc);
      MOZ_ASSERT(atom);
      return write(atom);
    }
    case JSOp::GetAliasedVar: {
      JSAtom* atom = EnvironmentCoordinateNameSlow(script, pc);
      MOZ_ASSERT(atom);
      return write(atom);
    }

    case JSOp::DelProp:
    case JSOp::StrictDelProp:
    case JSOp::GetProp:
    case JSOp::GetBoundName: {
      bool hasDelete = op == JSOp::DelProp || op == JSOp::StrictDelProp;
      Rooted<JSAtom*> prop(cx, loadAtom(pc));
      MOZ_ASSERT(prop);
      return (hasDelete ? write("(delete ") : true) &&
             decompilePCForStackOperand(pc, -1) &&
             (IsIdentifier(prop)
                  ? write(".") && quote(prop, '\0')
                  : write("[") && quote(prop, '\'') && write("]")) &&
             (hasDelete ? write(")") : true);
    }
    case JSOp::GetPropSuper: {
      Rooted<JSAtom*> prop(cx, loadAtom(pc));
      return write("super.") && quote(prop, '\0');
    }
    case JSOp::SetElem:
    case JSOp::StrictSetElem:
      // NOTE: We don't show the right hand side of the operation because
      // it's used in error messages like: "a[0] is not readable".
      //
      // We could though.
      return decompilePCForStackOperand(pc, -3) && write("[") &&
             decompilePCForStackOperand(pc, -2) && write("]");

    case JSOp::DelElem:
    case JSOp::StrictDelElem:
    case JSOp::GetElem: {
      bool hasDelete = (op == JSOp::DelElem || op == JSOp::StrictDelElem);
      return (hasDelete ? write("(delete ") : true) &&
             decompilePCForStackOperand(pc, -2) && write("[") &&
             decompilePCForStackOperand(pc, -1) && write("]") &&
             (hasDelete ? write(")") : true);
    }

    case JSOp::GetElemSuper:
      return write("super[") && decompilePCForStackOperand(pc, -2) &&
             write("]");
    case JSOp::Null:
      return write("null");
    case JSOp::True:
      return write("true");
    case JSOp::False:
      return write("false");
    case JSOp::Zero:
    case JSOp::One:
    case JSOp::Int8:
    case JSOp::Uint16:
    case JSOp::Uint24:
    case JSOp::Int32:
      sprinter.printf("%d", GetBytecodeInteger(pc));
      return true;
    case JSOp::String:
      return quote(loadString(pc), '"');
    case JSOp::Symbol: {
      unsigned i = uint8_t(pc[1]);
      MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
      if (i < JS::WellKnownSymbolLimit) {
        return write(cx->names().wellKnownSymbolDescriptions()[i]);
      }
      break;
    }
    case JSOp::Undefined:
      return write("undefined");
    case JSOp::GlobalThis:
    case JSOp::NonSyntacticGlobalThis:
      // |this| could convert to a very long object initialiser, so cite it by
      // its keyword name.
      return write("this");
    case JSOp::NewTarget:
      return write("new.target");
    case JSOp::ImportMeta:
      return write("import.meta");
    case JSOp::Call:
    case JSOp::CallContent:
    case JSOp::CallIgnoresRv:
    case JSOp::CallIter:
    case JSOp::CallContentIter: {
      uint16_t argc = GET_ARGC(pc);
      return decompilePCForStackOperand(pc, -int32_t(argc + 2)) &&
             write(argc ? "(...)" : "()");
    }
    case JSOp::SpreadCall:
      return decompilePCForStackOperand(pc, -3) && write("(...)");
    case JSOp::NewArray:
      return write("[]");
    case JSOp::RegExp: {
      Rooted<RegExpObject*> obj(cx, &script->getObject(pc)->as<RegExpObject>());
      JSString* str = RegExpObject::toString(cx, obj);
      if (!str) {
        return false;
      }
      return write(str);
    }
    case JSOp::Object: {
      JSObject* obj = script->getObject(pc);
      RootedValue objv(cx, ObjectValue(*obj));
      JSString* str = ValueToSource(cx, objv);
      if (!str) {
        return false;
      }
      return write(str);
    }
    case JSOp::Void:
      return write("(void ") && decompilePCForStackOperand(pc, -1) &&
             write(")");

    case JSOp::SuperCall:
      if (GET_ARGC(pc) == 0) {
        return write("super()");
      }
      [[fallthrough]];
    case JSOp::SpreadSuperCall:
      return write("super(...)");
    case JSOp::SuperFun:
      return write("super");

    case JSOp::Eval:
    case JSOp::SpreadEval:
    case JSOp::StrictEval:
    case JSOp::StrictSpreadEval:
      return write("eval(...)");

    case JSOp::New:
    case JSOp::NewContent: {
      uint16_t argc = GET_ARGC(pc);
      return write("(new ") &&
             decompilePCForStackOperand(pc, -int32_t(argc + 3)) &&
             write(argc ? "(...))" : "())");
    }

    case JSOp::SpreadNew:
      return write("(new ") && decompilePCForStackOperand(pc, -4) &&
             write("(...))");

    case JSOp::DynamicImport:
      return write("import(...)");

    case JSOp::Typeof:
    case JSOp::TypeofExpr:
      return write("(typeof ") && decompilePCForStackOperand(pc, -1) &&
             write(")");

    case JSOp::TypeofEq: {
      auto operand = TypeofEqOperand::fromRawValue(GET_UINT8(pc));
      JSType type = operand.type();
      JSOp compareOp = operand.compareOp();

      return write("(typeof ") && decompilePCForStackOperand(pc, -1) &&
             write(compareOp == JSOp::Ne ? " != \"" : " == \"") &&
             write(JSTypeToString(type)) && write("\")");
    }

    case JSOp::StrictConstantEq:
    case JSOp::StrictConstantNe: {
      auto operand = ConstantCompareOperand::fromRawValue(GET_UINT16(pc));
      return write("(") && decompilePCForStackOperand(pc, -1) && write(" ") &&
             write(op == JSOp::StrictConstantEq ? "===" : "!==") &&
             write(" ") && write(&operand) && write(")");
    }

    case JSOp::InitElemArray:
      return write("[...]");

    case JSOp::InitElemInc:
      if (defIndex == 0) {
        return write("[...]");
      }
      MOZ_ASSERT(defIndex == 1);
#ifdef DEBUG
      // INDEX won't be be exposed to error message.
      if (isStackDump) {
        return write("INDEX");
      }
#endif
      break;

    case JSOp::ToNumeric:
      return write("(tonumeric ") && decompilePCForStackOperand(pc, -1) &&
             write(")");

    case JSOp::Inc:
      return write("(inc ") && decompilePCForStackOperand(pc, -1) && write(")");

    case JSOp::Dec:
      return write("(dec ") && decompilePCForStackOperand(pc, -1) && write(")");

    case JSOp::BigInt:
#if defined(DEBUG) || defined(JS_JITSPEW)
      // BigInt::dumpLiteral() only available in this configuration.
      script->getBigInt(pc)->dumpLiteral(sprinter);
      return true;
#else
      return write("[bigint]");
#endif

    case JSOp::BuiltinObject: {
      auto kind = BuiltinObjectKind(GET_UINT8(pc));
      return write(BuiltinObjectName(kind));
    }

    default:
      break;
  }

#ifdef DEBUG
  if (isStackDump) {
    // Special decompilation for stack dump.
    switch (op) {
      case JSOp::Arguments:
        return write("arguments");

      case JSOp::ArgumentsLength:
        return write("arguments.length");

      case JSOp::GetFrameArg:
        sprinter.printf("arguments[%u]", GET_ARGNO(pc));
        return true;

      case JSOp::GetActualArg:
        return write("arguments[") && decompilePCForStackOperand(pc, -1) &&
               write("]");

      case JSOp::BindUnqualifiedGName:
        return write("GLOBAL");

      case JSOp::BindName:
      case JSOp::BindUnqualifiedName:
      case JSOp::BindVar:
        return write("ENV");

      case JSOp::Callee:
        return write("CALLEE");

      case JSOp::EnvCallee:
        return write("ENVCALLEE");

      case JSOp::CallSiteObj:
        return write("OBJ");

      case JSOp::Double:
        sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble());
        return true;

      case JSOp::Exception:
        return write("EXCEPTION");

      case JSOp::ExceptionAndStack:
        if (defIndex == 0) {
          return write("EXCEPTION");
        }
        MOZ_ASSERT(defIndex == 1);
        return write("STACK");

      case JSOp::Try:
        // Used for the values live on entry to the finally block.
        // See TryNoteKind::Finally above.
        if (defIndex == 0) {
          return write("PC");
        }
        if (defIndex == 1) {
          return write("STACK");
        }
        MOZ_ASSERT(defIndex == 2);
        return write("THROWING");

      case JSOp::FunctionThis:
      case JSOp::ImplicitThis:
        return write("THIS");

      case JSOp::FunWithProto:
        return write("FUN");

      case JSOp::Generator:
        return write("GENERATOR");

      case JSOp::GetImport:
        return write("VAL");

      case JSOp::GetRval:
        return write("RVAL");

      case JSOp::Hole:
        return write("HOLE");

      case JSOp::IsGenClosing:
        // For stack dump, defIndex == 0 is not used.
        MOZ_ASSERT(defIndex == 1);
        return write("ISGENCLOSING");

      case JSOp::IsNoIter:
        // For stack dump, defIndex == 0 is not used.
        MOZ_ASSERT(defIndex == 1);
        return write("ISNOITER");

      case JSOp::IsConstructing:
        return write("JS_IS_CONSTRUCTING");

      case JSOp::IsNullOrUndefined:
        return write("IS_NULL_OR_UNDEF");

      case JSOp::Iter:
        return write("ITER");

      case JSOp::Lambda:
        return write("FUN");

      case JSOp::ToAsyncIter:
        return write("ASYNCITER");

      case JSOp::MoreIter:
        // For stack dump, defIndex == 0 is not used.
        MOZ_ASSERT(defIndex == 1);
        return write("MOREITER");

      case JSOp::NewInit:
      case JSOp::NewObject:
      case JSOp::ObjWithProto:
        return write("OBJ");

      case JSOp::OptimizeGetIterator:
      case JSOp::OptimizeSpreadCall:
        return write("OPTIMIZED");

      case JSOp::Rest:
        return write("REST");

      case JSOp::Resume:
        return write("RVAL");

      case JSOp::SuperBase:
        return write("HOMEOBJECTPROTO");

      case JSOp::ToPropertyKey:
        return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc, -1) &&
               write(")");
      case JSOp::ToString:
        return write("TOSTRING(") && decompilePCForStackOperand(pc, -1) &&
               write(")");

      case JSOp::Uninitialized:
        return write("UNINITIALIZED");

      case JSOp::InitialYield:
      case JSOp::Await:
      case JSOp::Yield:
        // Printing "yield SOMETHING" is confusing since the operand doesn't
        // match to the syntax, since the stack operand for "yield 10" is
        // the result object, not 10.
        if (defIndex == 0) {
          return write("RVAL");
        }
        if (defIndex == 1) {
          return write("GENERATOR");
        }
        MOZ_ASSERT(defIndex == 2);
        return write("RESUMEKIND");

      case JSOp::ResumeKind:
        return write("RESUMEKIND");

      case JSOp::AsyncAwait:
      case JSOp::AsyncResolve:
      case JSOp::AsyncReject:
        return write("PROMISE");

      case JSOp::CanSkipAwait:
        // For stack dump, defIndex == 0 is not used.
        MOZ_ASSERT(defIndex == 1);
        return write("CAN_SKIP_AWAIT");

      case JSOp::MaybeExtractAwaitValue:
        // For stack dump, defIndex == 1 is not used.
        MOZ_ASSERT(defIndex == 0);
        return write("MAYBE_RESOLVED(") && decompilePCForStackOperand(pc, -2) &&
               write(")");

      case JSOp::CheckPrivateField:
        return write("HasPrivateField");

      case JSOp::NewPrivateName:
        return write("PRIVATENAME");

      case JSOp::CheckReturn:
        return write("RVAL");

      case JSOp::HasOwn:
        return write("HasOwn(") && decompilePCForStackOperand(pc, -2) &&
               write(", ") && decompilePCForStackOperand(pc, -1) && write(")");

#  ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
      case JSOp::AddDisposable:
        return decompilePCForStackOperand(pc, -1);

      case JSOp::TakeDisposeCapability:
        if (defIndex == 0) {
          return write("DISPOSECAPABILITY");
        }
        MOZ_ASSERT(defIndex == 1);
        return write("COUNT");
#  endif

      default:
        break;
    }
    return write("<unknown>");
  }
#endif /* DEBUG */

  return write("(intermediate value)");
}