bool JSActivation::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) { JSActivation* thisObject = jsCast<JSActivation*>(object); if (propertyName == exec->propertyNames().arguments) { // Defend against the inspector asking for the arguments object after it has been optimized out. CallFrame* callFrame = CallFrame::create(reinterpret_cast<Register*>(thisObject->m_registers)); if (!thisObject->isTornOff() && (callFrame->codeBlock()->usesArguments() || callFrame->codeBlock()->usesEval())) { slot.setCustom(thisObject, DontEnum, argumentsGetter); return true; } } if (thisObject->symbolTableGet(propertyName, slot)) return true; unsigned attributes; if (JSValue value = thisObject->getDirect(exec->vm(), propertyName, attributes)) { slot.setValue(thisObject, attributes, value); return true; } // We don't call through to JSObject because there's no way to give an // activation object getter properties or a prototype. ASSERT(!thisObject->hasGetterSetterProperties()); ASSERT(thisObject->prototype().isNull()); return false; }
// Evaluate some JavaScript code in the scope of this frame. JSValue DebuggerCallFrame::evaluate(const String& script, JSValue& exception) { ASSERT(isValid()); CallFrame* callFrame = m_callFrame; if (!callFrame) return jsNull(); JSLockHolder lock(callFrame); if (!callFrame->codeBlock()) return JSValue(); VM& vm = callFrame->vm(); EvalExecutable* eval = EvalExecutable::create(callFrame, makeSource(script), callFrame->codeBlock()->isStrictMode()); if (vm.exception()) { exception = vm.exception(); vm.clearException(); return jsUndefined(); } JSValue thisValue = thisValueForCallFrame(callFrame); JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, scope()); if (vm.exception()) { exception = vm.exception(); vm.clearException(); } ASSERT(result); return result; }
JSValue DebuggerCallFrame::evaluateNonBlocking(const String& script, NakedPtr<Exception>& exception) { ASSERT(isValid()); CallFrame* callFrame = m_callFrame; if (!callFrame) return jsNull(); if (!callFrame->codeBlock()) return JSValue(); DebuggerEvalEnabler evalEnabler(callFrame); VM& vm = callFrame->vm(); auto& codeBlock = *callFrame->codeBlock(); ThisTDZMode thisTDZMode = codeBlock.unlinkedCodeBlock()->constructorKind() == ConstructorKind::Derived ? ThisTDZMode::AlwaysCheck : ThisTDZMode::CheckIfNeeded; VariableEnvironment variablesUnderTDZ; JSScope::collectVariablesUnderTDZ(scope()->jsScope(), variablesUnderTDZ); EvalExecutable* eval = EvalExecutable::create(callFrame, makeSource(script), codeBlock.isStrictMode(), thisTDZMode, codeBlock.unlinkedCodeBlock()->isDerivedConstructorContext(), codeBlock.unlinkedCodeBlock()->isArrowFunction(), &variablesUnderTDZ); if (vm.exception()) { exception = vm.exception(); vm.clearException(); return jsUndefined(); } JSValue thisValue = thisValueForCallFrame(callFrame); JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, scope()->jsScope()); if (vm.exception()) { exception = vm.exception(); vm.clearException(); } ASSERT(result); return result; }
CallFrame* CallFrame::trueCallerFrameSlow() { // this -> The callee; this is either an inlined callee in which case it already has // a pointer to the true caller. Otherwise it contains current PC in the machine // caller. // // machineCaller -> The caller according to the machine, which may be zero or // more frames above the true caller due to inlining. // // trueCaller -> The real caller. // Am I an inline call frame? If so, we're done. if (isInlineCallFrame()) return callerFrame(); // I am a machine call frame, so the question is: is my caller a machine call frame // that has inlines or a machine call frame that doesn't? CallFrame* machineCaller = callerFrame()->removeHostCallFrameFlag(); if (!machineCaller) return 0; ASSERT(!machineCaller->isInlineCallFrame()); if (!machineCaller->codeBlock() || !machineCaller->codeBlock()->hasCodeOrigins()) return machineCaller; // No inlining, so machineCaller == trueCaller // Figure out where the caller frame would have gone relative to the machine // caller, and rematerialize it. Do so for the entire inline stack. CodeOrigin codeOrigin = machineCaller->codeBlock()->codeOriginForReturn(returnPC()); for (InlineCallFrame* inlineCallFrame = codeOrigin.inlineCallFrame; inlineCallFrame;) { InlineCallFrame* nextInlineCallFrame = inlineCallFrame = inlineCallFrame->caller.inlineCallFrame; CallFrame* inlinedCaller = machineCaller + inlineCallFrame->stackOffset; JSObject* callee = machineCaller->registers()[inlineCallFrame->calleeVR].function(); JSCell* calleeAsFunctionCell = getJSFunction(callee); ASSERT(calleeAsFunctionCell); JSFunction* calleeAsFunction = asFunction(calleeAsFunctionCell); // Fill in the inlinedCaller inlinedCaller->setCodeBlock(machineCaller->codeBlock()); inlinedCaller->setScopeChain(calleeAsFunction->scope()); if (nextInlineCallFrame) inlinedCaller->setCallerFrame(machineCaller + nextInlineCallFrame->stackOffset); else inlinedCaller->setCallerFrame(machineCaller); inlinedCaller->setInlineCallFrame(inlineCallFrame); inlinedCaller->setArgumentCountIncludingThis(inlineCallFrame->numArgumentsIncludingThis); inlinedCaller->setCallee(callee); inlineCallFrame = nextInlineCallFrame; } return machineCaller + codeOrigin.inlineCallFrame->stackOffset; }
JSValue VM::throwException(ExecState* exec, JSValue error) { ASSERT(exec == topCallFrame || exec == exec->lexicalGlobalObject()->globalExec() || exec == exec->dynamicGlobalObject()->globalExec()); Vector<StackFrame> stackTrace; interpreter->getStackTrace(stackTrace); m_exceptionStack = RefCountedArray<StackFrame>(stackTrace); m_exception = error; if (stackTrace.isEmpty() || !error.isObject()) return error; JSObject* exception = asObject(error); StackFrame stackFrame; for (unsigned i = 0 ; i < stackTrace.size(); ++i) { stackFrame = stackTrace.at(i); if (stackFrame.bytecodeOffset) break; } unsigned bytecodeOffset = stackFrame.bytecodeOffset; if (!hasErrorInfo(exec, exception)) { // FIXME: We should only really be adding these properties to VM generated exceptions, // but the inspector currently requires these for all thrown objects. unsigned line; unsigned column; stackFrame.computeLineAndColumn(line, column); exception->putDirect(*this, Identifier(this, "line"), jsNumber(line), ReadOnly | DontDelete); exception->putDirect(*this, Identifier(this, "column"), jsNumber(column), ReadOnly | DontDelete); if (!stackFrame.sourceURL.isEmpty()) exception->putDirect(*this, Identifier(this, "sourceURL"), jsString(this, stackFrame.sourceURL), ReadOnly | DontDelete); } if (exception->isErrorInstance() && static_cast<ErrorInstance*>(exception)->appendSourceToMessage()) { unsigned stackIndex = 0; CallFrame* callFrame; for (callFrame = exec; callFrame && !callFrame->codeBlock(); callFrame = callFrame->callerFrame()->removeHostCallFrameFlag()) stackIndex++; if (callFrame && callFrame->codeBlock()) { stackFrame = stackTrace.at(stackIndex); bytecodeOffset = stackFrame.bytecodeOffset; appendSourceToError(callFrame, static_cast<ErrorInstance*>(exception), bytecodeOffset); } } if (exception->hasProperty(exec, this->propertyNames->stack)) return error; exception->putDirect(*this, propertyNames->stack, interpreter->stackTraceAsString(topCallFrame, stackTrace), DontEnum); return error; }
// Evaluate some JavaScript code in the scope of this frame. JSValue DebuggerCallFrame::evaluateWithScopeExtension(const String& script, JSObject* scopeExtensionObject, NakedPtr<Exception>& exception) { ASSERT(isValid()); CallFrame* callFrame = m_validMachineFrame; if (!callFrame) return jsUndefined(); VM& vm = callFrame->vm(); JSLockHolder lock(vm); auto catchScope = DECLARE_CATCH_SCOPE(vm); CodeBlock* codeBlock = nullptr; if (isTailDeleted()) codeBlock = m_shadowChickenFrame.codeBlock; else codeBlock = callFrame->codeBlock(); if (!codeBlock) return jsUndefined(); DebuggerEvalEnabler evalEnabler(callFrame); EvalContextType evalContextType; if (isFunctionParseMode(codeBlock->unlinkedCodeBlock()->parseMode())) evalContextType = EvalContextType::FunctionEvalContext; else if (codeBlock->unlinkedCodeBlock()->codeType() == EvalCode) evalContextType = codeBlock->unlinkedCodeBlock()->evalContextType(); else evalContextType = EvalContextType::None; VariableEnvironment variablesUnderTDZ; JSScope::collectClosureVariablesUnderTDZ(scope()->jsScope(), variablesUnderTDZ); EvalExecutable* eval = DirectEvalExecutable::create(callFrame, makeSource(script), codeBlock->isStrictMode(), codeBlock->unlinkedCodeBlock()->derivedContextType(), codeBlock->unlinkedCodeBlock()->isArrowFunction(), evalContextType, &variablesUnderTDZ); if (UNLIKELY(catchScope.exception())) { exception = catchScope.exception(); catchScope.clearException(); return jsUndefined(); } JSGlobalObject* globalObject = callFrame->vmEntryGlobalObject(); if (scopeExtensionObject) { JSScope* ignoredPreviousScope = globalObject->globalScope(); globalObject->setGlobalScopeExtension(JSWithScope::create(vm, globalObject, scopeExtensionObject, ignoredPreviousScope)); } JSValue thisValue = this->thisValue(); JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, scope()->jsScope()); if (UNLIKELY(catchScope.exception())) { exception = catchScope.exception(); catchScope.clearException(); } if (scopeExtensionObject) globalObject->clearGlobalScopeExtension(); ASSERT(result); return result; }
EncodedJSValue JSActivation::argumentsGetter(ExecState*, EncodedJSValue slotBase, EncodedJSValue, PropertyName) { JSActivation* activation = jsCast<JSActivation*>(JSValue::decode(slotBase)); CallFrame* callFrame = CallFrame::create(reinterpret_cast<Register*>(activation->m_registers)); ASSERT(!activation->isTornOff() && (callFrame->codeBlock()->usesArguments() || callFrame->codeBlock()->usesEval())); if (activation->isTornOff() || !(callFrame->codeBlock()->usesArguments() || callFrame->codeBlock()->usesEval())) return JSValue::encode(jsUndefined()); VirtualRegister argumentsRegister = callFrame->codeBlock()->argumentsRegister(); if (JSValue arguments = callFrame->uncheckedR(argumentsRegister.offset()).jsValue()) return JSValue::encode(arguments); int realArgumentsRegister = unmodifiedArgumentsRegister(argumentsRegister).offset(); JSValue arguments = JSValue(Arguments::create(callFrame->vm(), callFrame)); callFrame->uncheckedR(argumentsRegister.offset()) = arguments; callFrame->uncheckedR(realArgumentsRegister) = arguments; ASSERT(callFrame->uncheckedR(realArgumentsRegister).jsValue().inherits(Arguments::info())); return JSValue::encode(callFrame->uncheckedR(realArgumentsRegister).jsValue()); }
JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source) { JSGlobalData* globalData = &exec->globalData(); addErrorInfo(globalData, error, line, source); JSArray* stack = constructEmptyArray(exec); CallFrame* frame = exec; JSObject* stackFrame; CodeBlock* codeBlock; UString sourceURL; UString functionName; ReturnAddressPtr pc; while (!frame->hasHostCallFrameFlag()) { stackFrame = constructEmptyObject(exec); codeBlock = frame->codeBlock(); // sourceURL sourceURL = codeBlock->ownerExecutable()->sourceURL(); stackFrame->putWithAttributes( globalData, Identifier(globalData, sourceURLPropertyName), jsString(globalData, sourceURL), ReadOnly | DontDelete ); // line if (frame != exec) { line = codeBlock->lineNumberForBytecodeOffset(codeBlock->bytecodeOffset(pc)); } stackFrame->putWithAttributes( globalData, Identifier(globalData, linePropertyName), jsNumber(line), ReadOnly | DontDelete ); // function JSObject* function = frame->callee(); if (function && function->inherits(&JSFunction::s_info)) { functionName = asFunction(function)->calculatedDisplayName(exec); stackFrame->putWithAttributes( globalData, Identifier(globalData, functionPropertyName), jsString(globalData, functionName), ReadOnly | DontDelete ); } stack->push(exec, JSValue(stackFrame)); pc = frame->returnPC(); frame = frame->callerFrame(); } error->putWithAttributes(globalData, Identifier(globalData, stackPropertyName), stack, ReadOnly | DontDelete); return error; }
JSValue VM::throwException(ExecState* exec, JSValue error) { if (Options::breakOnThrow()) { dataLog("In call frame ", RawPointer(exec), " for code block ", *exec->codeBlock(), "\n"); CRASH(); } ASSERT(exec == topCallFrame || exec == exec->lexicalGlobalObject()->globalExec() || exec == exec->vmEntryGlobalObject()->globalExec()); Vector<StackFrame> stackTrace; interpreter->getStackTrace(stackTrace); m_exceptionStack = RefCountedArray<StackFrame>(stackTrace); m_exception = error; if (stackTrace.isEmpty() || !error.isObject()) return error; JSObject* exception = asObject(error); StackFrame stackFrame; for (unsigned i = 0 ; i < stackTrace.size(); ++i) { stackFrame = stackTrace.at(i); if (stackFrame.bytecodeOffset) break; } if (!hasErrorInfo(exec, exception)) { // FIXME: We should only really be adding these properties to VM generated exceptions, // but the inspector currently requires these for all thrown objects. unsigned line; unsigned column; stackFrame.computeLineAndColumn(line, column); exception->putDirect(*this, Identifier(this, "line"), jsNumber(line), ReadOnly | DontDelete); exception->putDirect(*this, Identifier(this, "column"), jsNumber(column), ReadOnly | DontDelete); if (!stackFrame.sourceURL.isEmpty()) exception->putDirect(*this, Identifier(this, "sourceURL"), jsString(this, stackFrame.sourceURL), ReadOnly | DontDelete); } if (exception->isErrorInstance() && static_cast<ErrorInstance*>(exception)->appendSourceToMessage()) { FindFirstCallerFrameWithCodeblockFunctor functor(exec); topCallFrame->iterate(functor); CallFrame* callFrame = functor.foundCallFrame(); unsigned stackIndex = functor.index(); if (callFrame && callFrame->codeBlock()) { stackFrame = stackTrace.at(stackIndex); appendSourceToError(callFrame, static_cast<ErrorInstance*>(exception), stackFrame.bytecodeOffset); } } if (exception->hasProperty(exec, this->propertyNames->stack)) return error; exception->putDirect(*this, propertyNames->stack, interpreter->stackTraceAsString(topCallFrame, stackTrace), DontEnum); return error; }
void ErrorInstance::finishCreation(ExecState* exec, VM& vm, const String& message, bool useCurrentFrame) { Base::finishCreation(vm); ASSERT(inherits(info())); if (!message.isNull()) putDirect(vm, vm.propertyNames->message, jsString(&vm, message), DontEnum); unsigned bytecodeOffset = hasSourceAppender(); CallFrame* callFrame = nullptr; bool hasTrace = addErrorInfoAndGetBytecodeOffset(exec, vm, this, useCurrentFrame, callFrame, bytecodeOffset); if (hasTrace && callFrame && hasSourceAppender()) { if (callFrame && callFrame->codeBlock()) appendSourceToError(callFrame, this, bytecodeOffset); } }
EncodedJSValue JSLexicalEnvironment::argumentsGetter(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) { JSLexicalEnvironment* lexicalEnvironment = jsCast<JSLexicalEnvironment*>(slotBase); CallFrame* callFrame = CallFrame::create(reinterpret_cast<Register*>(lexicalEnvironment->m_registers)); return JSValue::encode(jsUndefined()); VirtualRegister argumentsRegister = callFrame->codeBlock()->argumentsRegister(); if (JSValue arguments = callFrame->uncheckedR(argumentsRegister.offset()).jsValue()) return JSValue::encode(arguments); int realArgumentsRegister = unmodifiedArgumentsRegister(argumentsRegister).offset(); JSValue arguments = JSValue(Arguments::create(callFrame->vm(), callFrame)); callFrame->uncheckedR(argumentsRegister.offset()) = arguments; callFrame->uncheckedR(realArgumentsRegister) = arguments; ASSERT(callFrame->uncheckedR(realArgumentsRegister).jsValue().inherits(Arguments::info())); return JSValue::encode(callFrame->uncheckedR(realArgumentsRegister).jsValue()); }
JSValue JSActivation::argumentsGetter(ExecState*, JSValue slotBase, PropertyName) { JSActivation* activation = jsCast<JSActivation*>(slotBase); if (activation->isTornOff()) return jsUndefined(); CallFrame* callFrame = CallFrame::create(reinterpret_cast<Register*>(activation->m_registers)); int argumentsRegister = callFrame->codeBlock()->argumentsRegister(); if (JSValue arguments = callFrame->uncheckedR(argumentsRegister).jsValue()) return arguments; int realArgumentsRegister = unmodifiedArgumentsRegister(argumentsRegister); JSValue arguments = JSValue(Arguments::create(callFrame->vm(), callFrame)); callFrame->uncheckedR(argumentsRegister) = arguments; callFrame->uncheckedR(realArgumentsRegister) = arguments; ASSERT(callFrame->uncheckedR(realArgumentsRegister).jsValue().inherits(Arguments::info())); return callFrame->uncheckedR(realArgumentsRegister).jsValue(); }
void JSActivation::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { JSActivation* thisObject = jsCast<JSActivation*>(object); CallFrame* callFrame = CallFrame::create(reinterpret_cast<Register*>(thisObject->m_registers)); if (mode == IncludeDontEnumProperties && !thisObject->isTornOff() && (callFrame->codeBlock()->usesArguments() || callFrame->codeBlock()->usesEval())) propertyNames.add(exec->propertyNames().arguments); { ConcurrentJITLocker locker(thisObject->symbolTable()->m_lock); SymbolTable::Map::iterator end = thisObject->symbolTable()->end(locker); for (SymbolTable::Map::iterator it = thisObject->symbolTable()->begin(locker); it != end; ++it) { if (it->value.getAttributes() & DontEnum && mode != IncludeDontEnumProperties) continue; if (!thisObject->isValid(it->value)) continue; propertyNames.add(Identifier(exec, it->key.get())); } } // Skip the JSVariableObject implementation of getOwnNonIndexPropertyNames JSObject::getOwnNonIndexPropertyNames(thisObject, exec, propertyNames, mode); }
void ShadowChicken::update(VM& vm, ExecState* exec) { if (verbose) { dataLog("Running update on: ", *this, "\n"); WTFReportBacktrace(); } const unsigned logCursorIndex = m_logCursor - m_log; // We need to figure out how to reconcile the current machine stack with our shadow stack. We do // that by figuring out how much of the shadow stack to pop. We apply three different rules. The // precise rule relies on the log. The log contains caller frames, which means that we know // where we bottomed out after making any call. If we bottomed out but made no calls then 'exec' // will tell us. That's why "highestPointSinceLastTime" will go no lower than exec. The third // rule, based on comparing to the current real stack, is executed in a later loop. CallFrame* highestPointSinceLastTime = exec; for (unsigned i = logCursorIndex; i--;) { Packet packet = m_log[i]; if (packet.isPrologue()) { CallFrame* watermark; if (i && m_log[i - 1].isTail()) watermark = packet.frame; else watermark = packet.callerFrame; highestPointSinceLastTime = std::max(highestPointSinceLastTime, watermark); } } if (verbose) dataLog("Highest point since last time: ", RawPointer(highestPointSinceLastTime), "\n"); while (!m_stack.isEmpty() && (m_stack.last().frame < highestPointSinceLastTime || m_stack.last().isTailDeleted)) m_stack.removeLast(); if (verbose) dataLog(" Revised stack: ", listDump(m_stack), "\n"); // It's possible that the top of stack is now tail-deleted. The stack no longer contains any // frames below the log's high watermark. That means that we just need to look for the first // occurence of a tail packet for the current stack top. if (!m_stack.isEmpty()) { ASSERT(!m_stack.last().isTailDeleted); for (unsigned i = 0; i < logCursorIndex; ++i) { Packet& packet = m_log[i]; if (packet.isTail() && packet.frame == m_stack.last().frame) { Frame& frame = m_stack.last(); frame.thisValue = packet.thisValue; frame.scope = packet.scope; frame.codeBlock = packet.codeBlock; frame.callSiteIndex = packet.callSiteIndex; frame.isTailDeleted = true; break; } } } if (verbose) dataLog(" Revised stack: ", listDump(m_stack), "\n"); // The log-based and exec-based rules require that ShadowChicken was enabled. The point of // ShadowChicken is to give sensible-looking results even if we had not logged. This means that // we need to reconcile the shadow stack and the real stack by actually looking at the real // stack. This reconciliation allows the shadow stack to have extra tail-deleted frames, but it // forbids it from diverging from the real stack on normal frames. if (!m_stack.isEmpty()) { Vector<Frame> stackRightNow; StackVisitor::visit( exec, [&] (StackVisitor& visitor) -> StackVisitor::Status { if (visitor->isInlinedFrame()) return StackVisitor::Continue; if (visitor->isWasmFrame()) { // FIXME: Make shadow chicken work with Wasm. // https://bugs.webkit.org/show_bug.cgi?id=165441 return StackVisitor::Continue; } bool isTailDeleted = false; // FIXME: Make shadow chicken work with Wasm. // https://bugs.webkit.org/show_bug.cgi?id=165441 stackRightNow.append(Frame(jsCast<JSObject*>(visitor->callee()), visitor->callFrame(), isTailDeleted)); return StackVisitor::Continue; }); stackRightNow.reverse(); if (verbose) dataLog(" Stack right now: ", listDump(stackRightNow), "\n"); unsigned shadowIndex = 0; unsigned rightNowIndex = 0; while (shadowIndex < m_stack.size() && rightNowIndex < stackRightNow.size()) { if (m_stack[shadowIndex].isTailDeleted) { shadowIndex++; continue; } // We specifically don't use operator== here because we are using a less // strict filter on equality of frames. For example, the scope pointer // could change, but we wouldn't want to consider the frames different entities // because of that because it's natural for the program to change scopes. if (m_stack[shadowIndex].frame == stackRightNow[rightNowIndex].frame && m_stack[shadowIndex].callee == stackRightNow[rightNowIndex].callee) { shadowIndex++; rightNowIndex++; continue; } break; } m_stack.resize(shadowIndex); if (verbose) dataLog(" Revised stack: ", listDump(m_stack), "\n"); } // It's possible that the top stack frame is actually lower than highestPointSinceLastTime. // Account for that here. highestPointSinceLastTime = nullptr; for (unsigned i = m_stack.size(); i--;) { if (!m_stack[i].isTailDeleted) { highestPointSinceLastTime = m_stack[i].frame; break; } } if (verbose) dataLog(" Highest point since last time: ", RawPointer(highestPointSinceLastTime), "\n"); // Set everything up so that we know where the top frame is in the log. unsigned indexInLog = logCursorIndex; auto advanceIndexInLogTo = [&] (CallFrame* frame, JSObject* callee, CallFrame* callerFrame) -> bool { if (verbose) dataLog(" Advancing to frame = ", RawPointer(frame), " from indexInLog = ", indexInLog, "\n"); if (indexInLog > logCursorIndex) { if (verbose) dataLog(" Bailing.\n"); return false; } unsigned oldIndexInLog = indexInLog; while (indexInLog--) { Packet packet = m_log[indexInLog]; // If all callees opt into ShadowChicken, then this search will rapidly terminate when // we find our frame. But if our frame's callee didn't emit a prologue packet because it // didn't opt in, then we will keep looking backwards until we *might* find a different // frame. If we've been given the callee and callerFrame as a filter, then it's unlikely // that we will hit the wrong frame. But we don't always have that information. // // This means it's worth adding other filters. For example, we could track changes in // stack size. Once we've seen a frame at some height, we're no longer interested in // frames below that height. Also, we can break as soon as we see a frame higher than // the one we're looking for. // FIXME: Add more filters. // https://bugs.webkit.org/show_bug.cgi?id=155685 if (packet.isPrologue() && packet.frame == frame && (!callee || packet.callee == callee) && (!callerFrame || packet.callerFrame == callerFrame)) { if (verbose) dataLog(" Found at indexInLog = ", indexInLog, "\n"); return true; } } // This is an interesting eventuality. We will see this if ShadowChicken was not // consistently enabled. We have a choice between: // // - Leaving the log index at -1, which will prevent the log from being considered. This is // the most conservative. It means that we will not be able to recover tail-deleted frames // from anything that sits above a frame that didn't log a prologue packet. This means // that everyone who creates prologues must log prologue packets. // // - Restoring the log index to what it was before. This prevents us from considering // whether this frame has tail-deleted frames behind it, but that's about it. The problem // with this approach is that it might recover tail-deleted frames that aren't relevant. // I haven't thought about this too deeply, though. // // It seems like the latter option is less harmful, so that's what we do. indexInLog = oldIndexInLog; if (verbose) dataLog(" Didn't find it.\n"); return false; }; Vector<Frame> toPush; StackVisitor::visit( exec, [&] (StackVisitor& visitor) -> StackVisitor::Status { if (visitor->isInlinedFrame()) { // FIXME: Handle inlining. // https://bugs.webkit.org/show_bug.cgi?id=155686 return StackVisitor::Continue; } if (visitor->isWasmFrame()) { // FIXME: Make shadow chicken work with Wasm. return StackVisitor::Continue; } CallFrame* callFrame = visitor->callFrame(); if (verbose) dataLog(" Examining ", RawPointer(callFrame), "\n"); if (callFrame == highestPointSinceLastTime) { if (verbose) dataLog(" Bailing at ", RawPointer(callFrame), " because it's the highest point since last time.\n"); return StackVisitor::Done; } bool foundFrame = advanceIndexInLogTo(callFrame, callFrame->jsCallee(), callFrame->callerFrame()); bool isTailDeleted = false; JSScope* scope = nullptr; CodeBlock* codeBlock = callFrame->codeBlock(); if (codeBlock && codeBlock->wasCompiledWithDebuggingOpcodes() && codeBlock->scopeRegister().isValid()) { scope = callFrame->scope(codeBlock->scopeRegister().offset()); RELEASE_ASSERT(scope->inherits(vm, JSScope::info())); } else if (foundFrame) { scope = m_log[indexInLog].scope; if (scope) RELEASE_ASSERT(scope->inherits(vm, JSScope::info())); } toPush.append(Frame(jsCast<JSObject*>(visitor->callee()), callFrame, isTailDeleted, callFrame->thisValue(), scope, codeBlock, callFrame->callSiteIndex())); if (indexInLog < logCursorIndex // This condition protects us from the case where advanceIndexInLogTo didn't find // anything. && m_log[indexInLog].frame == toPush.last().frame) { if (verbose) dataLog(" Going to loop through to find tail deleted frames with indexInLog = ", indexInLog, " and push-stack top = ", toPush.last(), "\n"); for (;;) { ASSERT(m_log[indexInLog].frame == toPush.last().frame); // Right now the index is pointing at a prologue packet of the last frame that // we pushed. Peek behind that packet to see if there is a tail packet. If there // is one then we know that there is a corresponding prologue packet that will // tell us about a tail-deleted frame. if (!indexInLog) break; Packet tailPacket = m_log[indexInLog - 1]; if (!tailPacket.isTail()) { // Last frame that we recorded was not the outcome of a tail call. So, there // will not be any more deleted frames. // FIXME: We might want to have a filter here. Consider that this was a tail // marker for a tail call to something that didn't log anything. It should // be sufficient to give the tail marker a copy of the caller frame. // https://bugs.webkit.org/show_bug.cgi?id=155687 break; } indexInLog--; // Skip over the tail packet. if (!advanceIndexInLogTo(tailPacket.frame, nullptr, nullptr)) { if (verbose) dataLog("Can't find prologue packet for tail: ", RawPointer(tailPacket.frame), "\n"); // We were unable to locate the prologue packet for this tail packet. // This is rare but can happen in a situation like: // function foo() { // ... call some deeply tail-recursive function, causing a random number of log processings. // return bar(); // tail call // } break; } Packet packet = m_log[indexInLog]; bool isTailDeleted = true; RELEASE_ASSERT(tailPacket.scope->inherits(vm, JSScope::info())); toPush.append(Frame(packet.callee, packet.frame, isTailDeleted, tailPacket.thisValue, tailPacket.scope, tailPacket.codeBlock, tailPacket.callSiteIndex)); } } return StackVisitor::Continue; }); if (verbose) dataLog(" Pushing: ", listDump(toPush), "\n"); for (unsigned i = toPush.size(); i--;) m_stack.append(toPush[i]); // We want to reset the log. There is a fun corner-case: there could be a tail marker at the end // of this log. We could make that work by setting isTailDeleted on the top of stack, but that // would require more corner cases in the complicated reconciliation code above. That code // already knows how to handle a tail packet at the beginning, so we just leverage that here. if (logCursorIndex && m_log[logCursorIndex - 1].isTail()) { m_log[0] = m_log[logCursorIndex - 1]; m_logCursor = m_log + 1; } else m_logCursor = m_log; if (verbose) dataLog(" After pushing: ", *this, "\n"); // Remove tail frames until the number of tail deleted frames is small enough. const unsigned maxTailDeletedFrames = Options::shadowChickenMaxTailDeletedFramesSize(); if (m_stack.size() > maxTailDeletedFrames) { unsigned numberOfTailDeletedFrames = 0; for (const Frame& frame : m_stack) { if (frame.isTailDeleted) numberOfTailDeletedFrames++; } if (numberOfTailDeletedFrames > maxTailDeletedFrames) { unsigned dstIndex = 0; unsigned srcIndex = 0; while (srcIndex < m_stack.size()) { Frame frame = m_stack[srcIndex++]; if (numberOfTailDeletedFrames > maxTailDeletedFrames && frame.isTailDeleted) { numberOfTailDeletedFrames--; continue; } m_stack[dstIndex++] = frame; } m_stack.resize(dstIndex); } } if (verbose) dataLog(" After clean-up: ", *this, "\n"); }
JSObject* addErrorInfo(ExecState* exec, JSObject* error, int line, const SourceCode& source) { JSGlobalData* globalData = &exec->globalData(); addErrorInfo(globalData, error, line, source); UStringBuilder stackString; JSArray* stackArray = constructEmptyArray(exec); CallFrame* frame = exec; stackString.append(error->toString(exec)); bool functionKnown; ReturnAddressPtr pc; while (!frame->hasHostCallFrameFlag()) { CodeBlock* codeBlock = frame->codeBlock(); JSObject* arrayItem = constructEmptyObject(exec); stackString.append("\n at "); JSObject* callee = frame->callee(); UString functionName; if (callee && callee->inherits(&JSFunction::s_info)) { functionName = asFunction(callee)->calculatedDisplayName(exec); functionKnown = !functionName.isEmpty(); } else { functionKnown = false; } if (functionKnown) { stackString.append(functionName); stackString.append(" ("); arrayItem->putWithAttributes( globalData, Identifier(globalData, functionPropertyName), jsString(globalData, functionName), ReadOnly | DontDelete ); } UString sourceURL = codeBlock->ownerExecutable()->sourceURL(); arrayItem->putWithAttributes( globalData, Identifier(globalData, sourceURLPropertyName), jsString(globalData, sourceURL), ReadOnly | DontDelete ); stackString.append(sourceURL); stackString.append(":"); if (frame != exec) { line = codeBlock->lineNumberForBytecodeOffset(codeBlock->bytecodeOffset(pc)); } arrayItem->putWithAttributes( globalData, Identifier(globalData, linePropertyName), jsNumber(line), ReadOnly | DontDelete ); stackString.append(UString::number(line)); if (functionKnown) { stackString.append(")"); } stackArray->push(exec, JSValue(arrayItem)); pc = frame->returnPC(); frame = frame->callerFrame(); } error->putWithAttributes( globalData, Identifier(globalData, stackPropertyName), jsString(globalData, stackString.toUString()), ReadOnly | DontDelete ); error->putWithAttributes( globalData, Identifier(globalData, "stackArray"), stackArray, ReadOnly | DontDelete ); return error; }