mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
467 lines
18 KiB
C++
467 lines
18 KiB
C++
/*
|
|
* Copyright (c) 2016-present Samsung Electronics Co., Ltd
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
* USA
|
|
*/
|
|
|
|
#include "Escargot.h"
|
|
#include "SandBox.h"
|
|
#include "runtime/Context.h"
|
|
#include "runtime/Environment.h"
|
|
#include "runtime/EnvironmentRecord.h"
|
|
#include "runtime/NativeFunctionObject.h"
|
|
#include "runtime/VMInstance.h"
|
|
#include "parser/Script.h"
|
|
#include "parser/ast/Node.h"
|
|
#include "interpreter/ByteCode.h"
|
|
#include "interpreter/ByteCodeInterpreter.h"
|
|
|
|
namespace Escargot {
|
|
|
|
SandBox::SandBox(Context* s)
|
|
: m_context(s)
|
|
{
|
|
m_oldSandBox = m_context->vmInstance()->m_currentSandBox;
|
|
m_context->vmInstance()->m_currentSandBox = this;
|
|
}
|
|
|
|
SandBox::~SandBox()
|
|
{
|
|
ASSERT(m_context->vmInstance()->m_currentSandBox == this);
|
|
m_context->vmInstance()->m_currentSandBox = m_oldSandBox;
|
|
}
|
|
|
|
void SandBox::processCatch(const Value& error, SandBoxResult& result)
|
|
{
|
|
// when exception occurred, an undefined value is allocated for result value which will be never used.
|
|
// this is to avoid dereferencing of null pointer.
|
|
result.result = Value();
|
|
result.error = error;
|
|
|
|
fillStackDataIntoErrorObject(error);
|
|
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
Debugger* debugger = m_context->debugger();
|
|
Debugger::SavedStackTraceDataVector exceptionTrace;
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
ByteCodeLOCDataMap locMap;
|
|
for (size_t i = 0; i < m_stackTraceDataVector.size(); i++) {
|
|
if ((size_t)m_stackTraceDataVector[i].loc.index == SIZE_MAX && (size_t)m_stackTraceDataVector[i].loc.actualCodeBlock != SIZE_MAX) {
|
|
// this means loc not computed yet.
|
|
StackTraceDataOnStack traceData = m_stackTraceDataVector[i];
|
|
ByteCodeBlock* block = traceData.loc.actualCodeBlock;
|
|
|
|
ByteCodeLOCData* locData;
|
|
auto iterMap = locMap.find(block);
|
|
if (iterMap == locMap.end()) {
|
|
locData = new ByteCodeLOCData();
|
|
locMap.insert(std::make_pair(block, locData));
|
|
} else {
|
|
locData = iterMap->second;
|
|
}
|
|
|
|
ExtendedNodeLOC loc = block->computeNodeLOCFromByteCode(m_context,
|
|
traceData.loc.byteCodePosition, block->m_codeBlock, locData);
|
|
|
|
traceData.loc = loc;
|
|
result.stackTrace.pushBack(traceData);
|
|
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
if (i < 8 && debugger != nullptr) {
|
|
exceptionTrace.pushBack(Debugger::SavedStackTraceData(block, (uint32_t)loc.line, (uint32_t)loc.column));
|
|
}
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
} else {
|
|
result.stackTrace.pushBack(m_stackTraceDataVector[i]);
|
|
}
|
|
}
|
|
for (auto iter = locMap.begin(); iter != locMap.end(); iter++) {
|
|
delete iter->second;
|
|
}
|
|
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
if (debugger != nullptr) {
|
|
ExecutionState state(m_context);
|
|
String* message = error.toStringWithoutException(state);
|
|
|
|
debugger->exceptionCaught(message, exceptionTrace);
|
|
}
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
}
|
|
|
|
SandBox::SandBoxResult SandBox::run(Value (*scriptRunner)(ExecutionState&, void*), void* data)
|
|
{
|
|
SandBox::SandBoxResult result;
|
|
try {
|
|
ExecutionState state(m_context);
|
|
result.result = scriptRunner(state, data);
|
|
} catch (const Value& err) {
|
|
processCatch(err, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
SandBox::SandBoxResult SandBox::run(ExecutionState& parentState, Value (*runner)(ExecutionState&, void*), void* data)
|
|
{
|
|
SandBox::SandBoxResult result;
|
|
try {
|
|
ExecutionState state(m_context, &parentState, reinterpret_cast<LexicalEnvironment*>(NULL), 0, nullptr, false);
|
|
result.result = runner(state, data);
|
|
} catch (const Value& err) {
|
|
processCatch(err, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool SandBox::createStackTrace(StackTraceDataOnStackVector& stackTraceDataVector, ExecutionState& state, bool stopAtPause)
|
|
{
|
|
UNUSED_VARIABLE(stopAtPause);
|
|
|
|
ExecutionState* curState = &state;
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
uint32_t executionStateDepthIndex = 0;
|
|
ExecutionState* activeSavedStackTraceExecutionState = nullptr;
|
|
|
|
if (stopAtPause && state.context()->debuggerEnabled()) {
|
|
activeSavedStackTraceExecutionState = state.context()->debugger()->activeSavedStackTraceExecutionState();
|
|
}
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
while (curState) {
|
|
ExecutionState* pState = curState;
|
|
|
|
while (pState) {
|
|
if (!pState->lexicalEnvironment()) {
|
|
// has no lexical scope like native function
|
|
break;
|
|
}
|
|
|
|
if (pState->lexicalEnvironment()) {
|
|
LexicalEnvironment* env = pState->lexicalEnvironment();
|
|
EnvironmentRecord* record = env->record();
|
|
ASSERT(!!record);
|
|
|
|
if (pState->parent() && pState->parent()->lexicalEnvironment() == env) {
|
|
// check if state is on the same lexical scope of the parent state e.g. try-catch-finally block
|
|
// if so, move upward
|
|
pState = pState->parent();
|
|
continue;
|
|
}
|
|
|
|
if (pState->isLocalEvalCode()) {
|
|
// check eval scope first
|
|
break;
|
|
}
|
|
|
|
if (record->isDeclarativeEnvironmentRecord() && record->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord()) {
|
|
// function scope
|
|
break;
|
|
} else if (record->isGlobalEnvironmentRecord()) {
|
|
// global scope
|
|
break;
|
|
} else if (record->isModuleEnvironmentRecord()) {
|
|
// module scope
|
|
break;
|
|
}
|
|
}
|
|
|
|
pState = pState->parent();
|
|
}
|
|
|
|
if (!pState) {
|
|
break;
|
|
}
|
|
|
|
|
|
if (pState->isLocalEvalCode()) {
|
|
// for eval code case
|
|
ASSERT(pState->codeBlock());
|
|
InterpretedCodeBlock* cb = pState->codeBlock().value();
|
|
ByteCodeBlock* b = cb->byteCodeBlock();
|
|
|
|
ExtendedNodeLOC loc(SIZE_MAX, SIZE_MAX, SIZE_MAX);
|
|
if (curState->m_programCounter != nullptr) {
|
|
if ((*curState->m_programCounter >= (size_t)b->m_code.data()) && (*curState->m_programCounter < (size_t)b->m_code.data() + b->m_code.size())) {
|
|
loc.byteCodePosition = *curState->m_programCounter - (size_t)b->m_code.data();
|
|
loc.actualCodeBlock = b;
|
|
}
|
|
}
|
|
StackTraceDataOnStack data;
|
|
data.loc = loc;
|
|
data.srcName = cb->script()->srcName();
|
|
data.sourceCode = cb->script()->sourceCode();
|
|
data.isEval = true;
|
|
data.isFunction = false;
|
|
data.isAssociatedWithJavaScriptCode = true;
|
|
data.isConstructor = false;
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
data.executionStateDepth = executionStateDepthIndex;
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
stackTraceDataVector.pushBack(data);
|
|
} else if (pState->lexicalEnvironment()) {
|
|
// can be null on module outer env
|
|
LexicalEnvironment* env = pState->lexicalEnvironment();
|
|
EnvironmentRecord* record = env->record();
|
|
|
|
InterpretedCodeBlock* cb = nullptr;
|
|
bool isFunction = false;
|
|
|
|
if (record->isGlobalEnvironmentRecord()) {
|
|
cb = pState->lexicalEnvironment()->record()->asGlobalEnvironmentRecord()->globalCodeBlock();
|
|
} else if (record->isModuleEnvironmentRecord()) {
|
|
cb = pState->lexicalEnvironment()->outerEnvironment()->record()->asGlobalEnvironmentRecord()->globalCodeBlock();
|
|
} else {
|
|
ASSERT(record->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord());
|
|
isFunction = true;
|
|
cb = record->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject()->codeBlock()->asInterpretedCodeBlock();
|
|
}
|
|
|
|
ASSERT(!!cb);
|
|
ByteCodeBlock* b = cb->byteCodeBlock();
|
|
ExtendedNodeLOC loc(SIZE_MAX, SIZE_MAX, SIZE_MAX);
|
|
ASSERT(!curState->isNativeFunctionObjectExecutionContext());
|
|
if (curState->m_programCounter != nullptr) {
|
|
if ((*curState->m_programCounter >= (size_t)b->m_code.data()) && (*curState->m_programCounter < (size_t)b->m_code.data() + b->m_code.size())) {
|
|
loc.byteCodePosition = *curState->m_programCounter - (size_t)b->m_code.data();
|
|
loc.actualCodeBlock = b;
|
|
}
|
|
}
|
|
StackTraceDataOnStack data;
|
|
data.loc = loc;
|
|
data.srcName = cb->script()->srcName();
|
|
data.sourceCode = cb->script()->sourceCode();
|
|
|
|
data.functionName = cb->functionName().string();
|
|
data.isEval = false;
|
|
data.isFunction = isFunction;
|
|
data.callee = isFunction ? record->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject() : nullptr;
|
|
data.isAssociatedWithJavaScriptCode = true;
|
|
data.isConstructor = isFunction ? record->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject()->isConstructor() : false;
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
data.executionStateDepth = executionStateDepthIndex;
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
stackTraceDataVector.pushBack(data);
|
|
} else if (pState->isNativeFunctionObjectExecutionContext()) {
|
|
// find the CodeBlock of the same lexical scope
|
|
ASSERT(!!pState->m_calledNativeFunctionObject);
|
|
NativeCodeBlock* cb = pState->m_calledNativeFunctionObject->codeBlock()->asNativeCodeBlock();
|
|
ExtendedNodeLOC loc(SIZE_MAX, SIZE_MAX, SIZE_MAX);
|
|
StackTraceDataOnStack data;
|
|
data.loc = loc;
|
|
|
|
StringBuilder builder;
|
|
builder.appendString("function ");
|
|
builder.appendString(cb->functionName().string());
|
|
builder.appendString("() { ");
|
|
builder.appendString("[native function]");
|
|
builder.appendString(" } ");
|
|
data.srcName = builder.finalize();
|
|
data.sourceCode = String::emptyString;
|
|
|
|
data.functionName = cb->functionName().string();
|
|
data.isEval = false;
|
|
data.isFunction = true;
|
|
data.callee = pState->m_calledNativeFunctionObject;
|
|
data.isAssociatedWithJavaScriptCode = cb->isInterpretedCodeBlock();
|
|
data.isConstructor = cb->isNativeConstructor();
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
data.executionStateDepth = executionStateDepthIndex;
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
stackTraceDataVector.pushBack(data);
|
|
}
|
|
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
if (curState == activeSavedStackTraceExecutionState) {
|
|
return true;
|
|
}
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
|
|
curState = pState->parent();
|
|
#ifdef ESCARGOT_DEBUGGER
|
|
executionStateDepthIndex++;
|
|
#endif /* ESCARGOT_DEBUGGER */
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SandBox::throwException(ExecutionState& state, const Value& exception)
|
|
{
|
|
m_stackTraceDataVector.clear();
|
|
createStackTrace(m_stackTraceDataVector, state);
|
|
|
|
// We MUST save thrown exception Value.
|
|
// because bdwgc cannot track `thrown value`(may turned off by GC_DONT_REGISTER_MAIN_STATIC_DATA)
|
|
m_exception = exception;
|
|
throw exception;
|
|
}
|
|
|
|
void SandBox::rethrowPreviouslyCaughtException(ExecutionState& state, Value exception, StackTraceDataOnStackVector&& stackTraceDataVector)
|
|
{
|
|
m_stackTraceDataVector = stackTraceDataVector;
|
|
// update stack trace data if needs
|
|
createStackTrace(m_stackTraceDataVector, state);
|
|
|
|
// We MUST save thrown exception Value.
|
|
// because bdwgc cannot track `thrown value`(may turned off by GC_DONT_REGISTER_MAIN_STATIC_DATA)
|
|
m_exception = exception;
|
|
throw exception;
|
|
}
|
|
|
|
StackTraceData* StackTraceData::create(SandBox* sandBox)
|
|
{
|
|
StackTraceData* data = create(sandBox->stackTraceDataVector());
|
|
data->exception = sandBox->exception();
|
|
return data;
|
|
}
|
|
|
|
StackTraceData* StackTraceData::create(StackTraceDataOnStackVector& stackData)
|
|
{
|
|
StackTraceData* data = new StackTraceData();
|
|
data->gcValues.resizeWithUninitializedValues(stackData.size());
|
|
data->nonGCValues.resizeWithUninitializedValues(stackData.size());
|
|
data->exception = Value();
|
|
|
|
for (size_t i = 0; i < stackData.size(); i++) {
|
|
if ((size_t)stackData[i].loc.index == SIZE_MAX && (size_t)stackData[i].loc.actualCodeBlock != SIZE_MAX) {
|
|
data->gcValues[i].byteCodeBlock = stackData[i].loc.actualCodeBlock;
|
|
data->nonGCValues[i].byteCodePosition = stackData[i].loc.byteCodePosition;
|
|
} else {
|
|
data->gcValues[i].infoString = stackData[i].srcName;
|
|
data->nonGCValues[i].byteCodePosition = SIZE_MAX;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
void StackTraceData::buildStackTrace(Context* context, StringBuilder& builder)
|
|
{
|
|
if (exception.isObject()) {
|
|
ExecutionState state(context);
|
|
try {
|
|
auto getResult = exception.asObject()->get(state, state.context()->staticStrings().name);
|
|
if (getResult.hasValue()) {
|
|
builder.appendString(getResult.value(state, exception.asObject()).toString(state));
|
|
builder.appendString(": ");
|
|
}
|
|
getResult = exception.asObject()->get(state, state.context()->staticStrings().message);
|
|
if (getResult.hasValue()) {
|
|
builder.appendString(getResult.value(state, exception.asObject()).toString(state));
|
|
builder.appendChar('\n');
|
|
}
|
|
} catch (const Value& v) {
|
|
// ignore exception
|
|
}
|
|
}
|
|
|
|
ByteCodeLOCDataMap locMap;
|
|
for (size_t i = 0; i < gcValues.size(); i++) {
|
|
builder.appendString("at ");
|
|
if (nonGCValues[i].byteCodePosition == SIZE_MAX) {
|
|
builder.appendString(gcValues[i].infoString);
|
|
} else {
|
|
ByteCodeBlock* block = gcValues[i].byteCodeBlock;
|
|
|
|
ByteCodeLOCData* locData;
|
|
auto iterMap = locMap.find(block);
|
|
if (iterMap == locMap.end()) {
|
|
locData = new ByteCodeLOCData();
|
|
locMap.insert(std::make_pair(block, locData));
|
|
} else {
|
|
locData = iterMap->second;
|
|
}
|
|
|
|
ExtendedNodeLOC loc = gcValues[i].byteCodeBlock->computeNodeLOCFromByteCode(context,
|
|
nonGCValues[i].byteCodePosition, block->m_codeBlock, locData);
|
|
|
|
builder.appendString(block->m_codeBlock->script()->srcName());
|
|
builder.appendChar(':');
|
|
builder.appendString(String::fromDouble(loc.line));
|
|
builder.appendChar(':');
|
|
builder.appendString(String::fromDouble(loc.column));
|
|
|
|
String* src = block->m_codeBlock->script()->sourceCode();
|
|
if (src->length() && loc.index != SIZE_MAX) {
|
|
const size_t preLineMax = 40;
|
|
const size_t afterLineMax = 40;
|
|
|
|
size_t preLineSoFar = 0;
|
|
size_t afterLineSoFar = 0;
|
|
|
|
auto bad = src->bufferAccessData();
|
|
size_t start = loc.index;
|
|
int64_t idx = (int64_t)start;
|
|
while (start - idx < preLineMax) {
|
|
if (idx == 0) {
|
|
break;
|
|
}
|
|
if (bad.charAt((size_t)idx) == '\r' || bad.charAt((size_t)idx) == '\n') {
|
|
idx++;
|
|
break;
|
|
}
|
|
idx--;
|
|
}
|
|
preLineSoFar = idx;
|
|
|
|
idx = start;
|
|
while (idx - start < afterLineMax) {
|
|
if ((size_t)idx == bad.length - 1) {
|
|
break;
|
|
}
|
|
if (bad.charAt((size_t)idx) == '\r' || bad.charAt((size_t)idx) == '\n') {
|
|
break;
|
|
}
|
|
idx++;
|
|
}
|
|
afterLineSoFar = idx;
|
|
|
|
if (preLineSoFar <= afterLineSoFar && preLineSoFar <= bad.length && afterLineSoFar <= bad.length) {
|
|
builder.appendChar('\n');
|
|
builder.appendSubString(src, preLineSoFar, afterLineSoFar);
|
|
builder.appendChar('\n');
|
|
std::string sourceCodePosition;
|
|
for (size_t i = preLineSoFar; i < start; i++) {
|
|
sourceCodePosition += " ";
|
|
}
|
|
sourceCodePosition += "^";
|
|
builder.appendString(String::fromASCII(sourceCodePosition.data(), sourceCodePosition.length()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i != gcValues.size() - 1) {
|
|
builder.appendChar('\n');
|
|
}
|
|
}
|
|
for (auto iter = locMap.begin(); iter != locMap.end(); iter++) {
|
|
delete iter->second;
|
|
}
|
|
}
|
|
|
|
void SandBox::fillStackDataIntoErrorObject(const Value& e)
|
|
{
|
|
if (e.isObject() && e.asObject()->isErrorObject()) {
|
|
ErrorObject* obj = e.asObject()->asErrorObject();
|
|
|
|
obj->setStackTraceData(StackTraceData::create(this));
|
|
}
|
|
}
|
|
} // namespace Escargot
|