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)");
}