#include "config.h"
#include "ShadowChicken.h"
#include "CodeBlock.h"
#include "JSCInlines.h"
#include "ShadowChickenInlines.h"
#include <wtf/ListDump.h>
namespace JSC {
namespace ShadowChickenInternal {
static const bool verbose = false;
}
void ShadowChicken::Packet::dump(PrintStream& out) const
{
if (!*this) {
out.print("empty");
return;
}
if (isPrologue()) {
out.print(
"{callee = ", RawPointer(callee), ", frame = ", RawPointer(frame), ", callerFrame = ",
RawPointer(callerFrame), "}");
return;
}
if (isTail()) {
out.print("tail-packet:{frame = ", RawPointer(frame), "}");
return;
}
ASSERT(isThrow());
out.print("throw");
}
void ShadowChicken::Frame::dump(PrintStream& out) const
{
out.print(
"{callee = ", RawPointer(callee), ", frame = ", RawPointer(frame), ", isTailDeleted = ",
isTailDeleted, "}");
}
ShadowChicken::ShadowChicken()
: m_logSize(Options::shadowChickenLogSize())
{
m_log = static_cast<Packet*>(fastZeroedMalloc(sizeof(Packet) * m_logSize));
m_logCursor = m_log;
m_logEnd = m_log + m_logSize;
}
ShadowChicken::~ShadowChicken()
{
fastFree(m_log);
}
void ShadowChicken::log(VM& vm, ExecState* exec, const Packet& packet)
{
update(vm, exec);
*m_logCursor++ = packet;
}
void ShadowChicken::update(VM& vm, ExecState* exec)
{
if (ShadowChickenInternal::verbose) {
dataLog("Running update on: ", *this, "\n");
WTFReportBacktrace();
}
const unsigned logCursorIndex = m_logCursor - m_log;
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 (ShadowChickenInternal::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 (ShadowChickenInternal::verbose)
dataLog(" Revised stack: ", listDump(m_stack), "\n");
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 (ShadowChickenInternal::verbose)
dataLog(" Revised stack: ", listDump(m_stack), "\n");
if (!m_stack.isEmpty()) {
Vector<Frame> stackRightNow;
StackVisitor::visit(
exec, &vm, [&] (StackVisitor& visitor) -> StackVisitor::Status {
if (visitor->isInlinedFrame())
return StackVisitor::Continue;
if (visitor->isWasmFrame()) {
return StackVisitor::Continue;
}
bool isTailDeleted = false;
stackRightNow.append(Frame(jsCast<JSObject*>(visitor->callee().asCell()), visitor->callFrame(), isTailDeleted));
return StackVisitor::Continue;
});
stackRightNow.reverse();
if (ShadowChickenInternal::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;
}
if (m_stack[shadowIndex].frame == stackRightNow[rightNowIndex].frame
&& m_stack[shadowIndex].callee == stackRightNow[rightNowIndex].callee) {
shadowIndex++;
rightNowIndex++;
continue;
}
break;
}
m_stack.resize(shadowIndex);
if (ShadowChickenInternal::verbose)
dataLog(" Revised stack: ", listDump(m_stack), "\n");
}
highestPointSinceLastTime = nullptr;
for (unsigned i = m_stack.size(); i--;) {
if (!m_stack[i].isTailDeleted) {
highestPointSinceLastTime = m_stack[i].frame;
break;
}
}
if (ShadowChickenInternal::verbose)
dataLog(" Highest point since last time: ", RawPointer(highestPointSinceLastTime), "\n");
unsigned indexInLog = logCursorIndex;
auto advanceIndexInLogTo = [&] (CallFrame* frame, JSObject* callee, CallFrame* callerFrame) -> bool {
if (ShadowChickenInternal::verbose)
dataLog(" Advancing to frame = ", RawPointer(frame), " from indexInLog = ", indexInLog, "\n");
if (indexInLog > logCursorIndex) {
if (ShadowChickenInternal::verbose)
dataLog(" Bailing.\n");
return false;
}
unsigned oldIndexInLog = indexInLog;
while (indexInLog--) {
Packet packet = m_log[indexInLog];
if (packet.isPrologue() && packet.frame == frame
&& (!callee || packet.callee == callee)
&& (!callerFrame || packet.callerFrame == callerFrame)) {
if (ShadowChickenInternal::verbose)
dataLog(" Found at indexInLog = ", indexInLog, "\n");
return true;
}
}
indexInLog = oldIndexInLog;
if (ShadowChickenInternal::verbose)
dataLog(" Didn't find it.\n");
return false;
};
Vector<Frame> toPush;
StackVisitor::visit(
exec, &vm, [&] (StackVisitor& visitor) -> StackVisitor::Status {
if (visitor->isInlinedFrame()) {
return StackVisitor::Continue;
}
if (visitor->isWasmFrame()) {
return StackVisitor::Continue;
}
CallFrame* callFrame = visitor->callFrame();
if (ShadowChickenInternal::verbose)
dataLog(" Examining ", RawPointer(callFrame), "\n");
if (callFrame == highestPointSinceLastTime) {
if (ShadowChickenInternal::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();
JSValue scopeValue = callFrame->bytecodeOffset() && codeBlock && codeBlock->scopeRegister().isValid()
? callFrame->registers()[codeBlock->scopeRegister().offset()].jsValue()
: jsUndefined();
if (!scopeValue.isUndefined() && codeBlock->wasCompiledWithDebuggingOpcodes()) {
scope = jsCast<JSScope*>(scopeValue.asCell());
RELEASE_ASSERT(scope->inherits<JSScope>(vm));
} else if (foundFrame) {
scope = m_log[indexInLog].scope;
if (scope)
RELEASE_ASSERT(scope->inherits<JSScope>(vm));
}
toPush.append(Frame(jsCast<JSObject*>(visitor->callee().asCell()), callFrame, isTailDeleted, callFrame->thisValue(), scope, codeBlock, callFrame->callSiteIndex()));
if (indexInLog < logCursorIndex
&& m_log[indexInLog].frame == toPush.last().frame) {
if (ShadowChickenInternal::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);
if (!indexInLog)
break;
Packet tailPacket = m_log[indexInLog - 1];
if (!tailPacket.isTail()) {
break;
}
indexInLog--;
if (!advanceIndexInLogTo(tailPacket.frame, nullptr, nullptr)) {
if (ShadowChickenInternal::verbose)
dataLog("Can't find prologue packet for tail: ", RawPointer(tailPacket.frame), "\n");
break;
}
Packet packet = m_log[indexInLog];
bool isTailDeleted = true;
RELEASE_ASSERT(tailPacket.scope->inherits<JSScope>(vm));
toPush.append(Frame(packet.callee, packet.frame, isTailDeleted, tailPacket.thisValue, tailPacket.scope, tailPacket.codeBlock, tailPacket.callSiteIndex));
}
}
return StackVisitor::Continue;
});
if (ShadowChickenInternal::verbose)
dataLog(" Pushing: ", listDump(toPush), "\n");
for (unsigned i = toPush.size(); i--;)
m_stack.append(toPush[i]);
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 (ShadowChickenInternal::verbose)
dataLog(" After pushing: ", *this, "\n");
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.shrink(dstIndex);
}
}
if (ShadowChickenInternal::verbose)
dataLog(" After clean-up: ", *this, "\n");
}
void ShadowChicken::visitChildren(SlotVisitor& visitor)
{
for (unsigned i = m_logCursor - m_log; i--;) {
JSObject* callee = m_log[i].callee;
if (callee != Packet::tailMarker() && callee != Packet::throwMarker())
visitor.appendUnbarriered(callee);
if (callee != Packet::throwMarker())
visitor.appendUnbarriered(m_log[i].scope);
if (callee == Packet::tailMarker()) {
visitor.appendUnbarriered(m_log[i].thisValue);
visitor.appendUnbarriered(m_log[i].codeBlock);
}
}
for (unsigned i = m_stack.size(); i--; ) {
Frame& frame = m_stack[i];
visitor.appendUnbarriered(frame.thisValue);
visitor.appendUnbarriered(frame.callee);
if (frame.scope)
visitor.appendUnbarriered(frame.scope);
if (frame.codeBlock)
visitor.appendUnbarriered(frame.codeBlock);
}
}
void ShadowChicken::reset()
{
m_logCursor = m_log;
m_stack.clear();
}
void ShadowChicken::dump(PrintStream& out) const
{
out.print("{stack = [", listDump(m_stack), "], log = [");
CommaPrinter comma;
unsigned limit = static_cast<unsigned>(m_logCursor - m_log);
out.print("\n");
for (unsigned i = 0; i < limit; ++i)
out.print("\t", comma, m_log[i], "\n");
out.print("]}");
}
JSArray* ShadowChicken::functionsOnStack(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSArray* result = constructEmptyArray(exec, 0);
RETURN_IF_EXCEPTION(scope, nullptr);
iterate(
vm, exec,
[&] (const Frame& frame) -> bool {
result->push(exec, frame.callee);
scope.releaseAssertNoException(); return true;
});
return result;
}
}