//// ScriptEngineV8.cpp// libraries/script-engine/src/v8//// Created by Brad Hefta-Gaub on 12/14/13.// Modified for V8 by dr Karol Suprynowicz on 2022/10/08// Copyright 2013 High Fidelity, Inc.// Copyright 2022 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "ScriptEngineV8.h" #include <chrono>#include <mutex>#include <thread> #include <QtCore/QCoreApplication>#include <QtCore/QEventLoop>#include <QtCore/QFileInfo>#include <QtCore/QTimer>#include <QtCore/QThread>#include <QtCore/QRegularExpression> #include <QtCore/QFuture>#include <QtConcurrent/QtConcurrentRun> #include <QtWidgets/QMainWindow>#include <QtWidgets/QApplication>#include <QtWidgets/QMenuBar>#include <QtWidgets/QMenu> #include <QtNetwork/QNetworkRequest>#include <QtNetwork/QNetworkReply> #include <shared/LocalFileAccessGate.h>#include <shared/QtHelpers.h>#include <shared/AbstractLoggerInterface.h> #include <Profile.h> #include "../ScriptEngineLogging.h"#include "../ScriptProgram.h"#include "../ScriptValue.h" #include "ScriptContextV8Wrapper.h"#include "ScriptObjectV8Proxy.h"#include "ScriptProgramV8Wrapper.h"#include "ScriptValueV8Wrapper.h" static const int MAX_DEBUG_VALUE_LENGTH { 80 }; std::once_flag ScriptEngineV8::_v8InitOnceFlag;QMutex ScriptEngineV8::_v8InitMutex; bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { const QThread* currentThread = QThread::currentThread(); if (currentThread == thread) { return true; } qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") .arg(method) .arg(thread ? thread->objectName() : "(!thread)") .arg(QThread::currentThread()->objectName()); qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; Q_ASSERT(false); return false;} // engine-aware JS Error copier and factoryV8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); } return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); //V8TODO /* auto other = _other; if (_other.constGet()->IsString()) { other = QScriptEngine::newObject(); other.setProperty("message", _other.toString()); } auto proto = QScriptEngine::globalObject().property(type); if (!proto.isFunction()) { proto = QScriptEngine::globalObject().property(other.prototype().property("constructor").property("name").toString()); } if (!proto.isFunction()) {#ifdef DEBUG_JS_EXCEPTIONS qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";#endif proto = QScriptEngine::globalObject().property("Error"); } if (other.engine() != this) { // JS Objects are parented to a specific script engine instance // -- this effectively ~clones it locally by routing through a QVariant and back other = QScriptEngine::toScriptValue(other.toVariant()); } // ~ var err = new Error(other.message) auto err = proto.construct(V8ScriptValueList({ other.property("message") })); // transfer over any existing properties V8ScriptValueIterator it(other); while (it.hasNext()) { it.next(); err.setProperty(it.name(), it.value()); } return err;*/} ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); return nullValue(); //V8TODO //what does makeError actually do? /*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(_other); V8ScriptValue other; if (_other.isString()) { other = QScriptEngine::newObject(); other.setProperty("message", _other.toString()); } else if (unwrapped) { other = unwrapped->toV8Value(); } else { other = QScriptEngine::newVariant(_other.toVariant()); } V8ScriptValue result = makeError(other, type); return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result)));*/} // check syntax and when there are issues returns an actual "SyntaxError" with the detailsScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); ScriptSyntaxCheckResultPointer syntaxCheck = program->checkSyntax(); //V8TODO if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { auto err = globalObject().property("SyntaxError").construct(ScriptValueList({ newValue(syntaxCheck->errorMessage()) })); err.setProperty("fileName", program->fileName()); err.setProperty("lineNumber", syntaxCheck->errorLineNumber()); err.setProperty("expressionBeginOffset", syntaxCheck->errorColumnNumber()); err.setProperty("stack", syntaxCheck->errorBacktrace()); //err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); { const auto error = syntaxCheck->errorMessage(); const auto line = QString::number(syntaxCheck->errorLineNumber()); const auto column = QString::number(syntaxCheck->errorColumnNumber()); // for compatibility with legacy reporting const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program->fileName(), line, column); err.setProperty("formatted", message); } return err; } return undefinedValue();}/*ScriptValue ScriptEngineV8::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } //V8TODO const auto syntaxCheck = checkSyntax(sourceCode); if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { auto err = QScriptEngine::globalObject().property("SyntaxError").construct(V8ScriptValueList({ syntaxCheck.errorMessage() })); err.setProperty("fileName", fileName); err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); { const auto error = syntaxCheck.errorMessage(); const auto line = QString::number(syntaxCheck.errorLineNumber()); const auto column = QString::number(syntaxCheck.errorColumnNumber()); // for compatibility with legacy reporting const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); err.setProperty("formatted", message); } return ScriptValue(new ScriptValueV8Wrapper(this, std::move(err))); } return undefinedValue();}*/ // this pulls from the best available information to create a detailed snapshot of the current exceptionScriptValue ScriptEngineV8::cloneUncaughtException(const QString& extraDetail) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } if (!hasUncaughtException()) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); return nullValue(); //V8TODO /* auto exception = uncaughtException(); // ensure the error object is engine-local auto err = makeError(exception); // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number // on its own is often useless/wrong if arbitrarily married to a filename. // when the error object already has this info, it seems to be the most reliable auto fileName = exception.property("fileName").toString(); auto lineNumber = exception.property("lineNumber").toInt32(); // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace auto backtrace = uncaughtExceptionBacktrace(); if (backtrace.isEmpty()) { // fallback to the error object backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); } // the ad hoc "detail" property can be used now to embed additional clues auto detail = exception.property("detail").toString(); if (detail.isEmpty()) { detail = extraDetail; } else if (!extraDetail.isEmpty()) { detail += "(" + extraDetail + ")"; } if (lineNumber <= 0) { lineNumber = uncaughtExceptionLineNumber(); } if (fileName.isEmpty()) { // climb the stack frames looking for something useful to display for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { V8ScriptContextInfo info{ c }; if (!info.fileName().isEmpty()) { // take fileName:lineNumber as a pair fileName = info.fileName(); lineNumber = info.lineNumber(); if (backtrace.isEmpty()) { backtrace = c->backtrace(); } break; } } } err.setProperty("fileName", fileNlintame); err.setProperty("lineNumber", lineNumber); err.setProperty("detail", detail); err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); #ifdef DEBUG_JS_EXCEPTIONS err.setProperty("_fileName", exception.property("fileName").toString()); err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); err.setProperty("_lineNumber", uncaughtExceptionLineNumber());#endif return err; */} bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return false; } //V8TODO _v8Isolate->ThrowException(makeError(exception).get()); /*if (QScriptEngine::currentContext()) { // we have an active context / JS stack frame so throw the exception per usual QScriptEngine::currentContext()->throwValue(makeError(exception)); return true; } else if (_scriptManager) { // we are within a pure C++ stack frame (ie: being called directly by other C++ code) // in this case no context information is available so just emit the exception for reporting V8ScriptValue thrown = makeError(exception); emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); }*/ //emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); return false;} bool ScriptEngineV8::maybeEmitUncaughtException(const QString& debugHint) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return false; } if (!isEvaluating() && hasUncaughtException() && _scriptManager) { emit _scriptManager->unhandledException(cloneUncaughtException(debugHint)); clearExceptions(); return true; } return false;} // LambdaScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, const V8ScriptValue& data, const ValueOwnership& ownership) { v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); auto lambda = new Lambda(this, operation, data); auto object = newQObject(lambda, ownership); //V8TODO - I'm not sure if this works auto call = object.property("call"); call.setPrototype(object); // context->callee().prototype() === Lambda QObject call.setData(ScriptValue(new ScriptValueV8Wrapper(this, data))); // context->callee().data() will === data param return call;}QString Lambda::toString() const { v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); v8::Local<v8::String> string; QString qString(""); if (_data.constGet()->ToString(_engine->getContext()).ToLocal(&string)) { v8::String::Utf8Value utf8Value(_engine->getIsolate(), string); qString = QString(*utf8Value); } //V8TODO it was data.isValid() originally //I have no idea what happens here return QString("[Lambda%1]").arg((!_data.constGet()->IsNullOrUndefined()) ? " " + qString : qString);} Lambda::~Lambda() {#ifdef DEBUG_JS_LAMBDA_FUNCS qDebug() << "~Lambda" << "this" << this;#endif} Lambda::Lambda(ScriptEngineV8* engine, std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, V8ScriptValue data) : _engine(engine), _operation(operation), _data(data) {#ifdef DEBUG_JS_LAMBDA_FUNCS qDebug() << "Lambda" << data.toString();#endif}V8ScriptValue Lambda::call() { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); } // V8TODO: it needs to be done in entirely different way for V8 Q_ASSERT(false); //return _operation(_engine->getContext(), _engine); //return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); //return operation(static_cast<QScriptEngine*>(engine)->currentContext(), engine);} #ifdef DEBUG_JSvoid ScriptEngineV8::_debugDump(const QString& header, const V8ScriptValue& object, const QString& footer) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } if (!header.isEmpty()) { qCDebug(shared) << header; } if (!object.isObject()) { qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); return; } V8ScriptValueIterator it(object); while (it.hasNext()) { it.next(); qCDebug(shared) << it.name() << ":" << it.value().toString(); } if (!footer.isEmpty()) { qCDebug(shared) << footer; }}#endif v8::Platform* ScriptEngineV8::getV8Platform() { static std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform(); return platform.get();} ScriptEngineV8::ScriptEngineV8(ScriptManager* scriptManager) : _scriptManager(scriptManager), _isEvaluating(false) //V8TODO //_arrayBufferClass(new ArrayBufferClass(this)){ _v8InitMutex.lock(); std::call_once ( _v8InitOnceFlag, [ ]{ v8::V8::InitializeExternalStartupData(""); // Experimentally determined that the maximum size that works on Linux with a stack size of 8192K is 8182. // That would seem to be the overhead of our code and V8. // // Windows stacks are 1MB. // // Based on that, going with 256K for stacks for now. That seems like a reasonable value. // We'll probably need a more complex system on the longer term, with configurable limits. v8::V8::SetFlagsFromString("--stack-size=256"); v8::Platform* platform = getV8Platform(); v8::V8::InitializePlatform(platform); v8::V8::Initialize(); qCDebug(scriptengine) << "V8 platform initialized"; } ); _v8InitMutex.unlock(); { v8::Isolate::CreateParams isolateParams; isolateParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); _v8Isolate = v8::Isolate::New(isolateParams); _v8Isolate->Enter(); v8::HandleScope handleScope(_v8Isolate); v8::Local<v8::Context> context = v8::Context::New(_v8Isolate); Q_ASSERT(!context.IsEmpty()); v8::Context::Scope contextScope(context); _v8Context = v8::UniquePersistent<v8::Context>(_v8Isolate, context); V8ScriptValue nullScriptValue(_v8Isolate, v8::Null(_v8Isolate)); _nullValue = ScriptValue(new ScriptValueV8Wrapper(this, nullScriptValue)); V8ScriptValue undefined(_v8Isolate, v8::Undefined(_v8Isolate)); _undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, undefined)); registerSystemTypes(); // V8TODO: dispose of isolate on ScriptEngineV8 destruction //v8::UniquePersistent<v8::Value> null = v8::UniquePersistent<v8::Value>(_v8Isolate, v8::Null(_v8Isolate)); //_nullValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(null))); //V8ScriptValue undefined = v8::UniquePersistent<v8::Value>(_v8Isolate,v8::Undefined(_v8Isolate)); //_undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(undefined))); // V8TODO: //QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); } //_currentThread = QThread::currentThread(); if (_scriptManager) { // V8TODO: port to V8 /*connect(this, &QScriptEngine::signalHandlerException, this, [this](const V8ScriptValue& exception) { if (hasUncaughtException()) { // the engine's uncaughtException() seems to produce much better stack traces here emit _scriptManager->unhandledException(cloneUncaughtException("signalHandlerException")); clearExceptions(); } else { // ... but may not always be available -- so if needed we fallback to the passed exception V8ScriptValue thrown = makeError(exception); emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); } }, Qt::DirectConnection);*/ //moveToThread(scriptManager->thread()); //setThread(scriptManager->thread()); }} void ScriptEngineV8::registerEnum(const QString& enumName, QMetaEnum newEnum) { if (!newEnum.isValid()) { qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; return; } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); for (int i = 0; i < newEnum.keyCount(); i++) { const char* keyName = newEnum.key(i); QString fullName = enumName + "." + keyName; registerValue(fullName, V8ScriptValue(_v8Isolate, v8::Integer::New(_v8Isolate, newEnum.keyToValue(keyName)))); }} void ScriptEngineV8::registerValue(const QString& valueName, V8ScriptValue value) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";#endif QMetaObject::invokeMethod(this, "registerValue", Q_ARG(const QString&, valueName), Q_ARG(V8ScriptValue, value)); return; } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); QStringList pathToValue = valueName.split("."); int partsToGo = pathToValue.length(); v8::Local<v8::Object> partObject = _v8Context.Get(_v8Isolate)->Global(); for (const auto& pathPart : pathToValue) { partsToGo--; v8::Local<v8::String> pathPartV8 = v8::String::NewFromUtf8(_v8Isolate, pathPart.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); v8::Local<v8::Value> currentPath; if (!partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(¤tPath)) { if (partsToGo > 0) { //This was commented out //QObject *object = new QObject; v8::Local<v8::Object> partValue = v8::Object::New(_v8Isolate); //newQObject(object, QScriptEngine::ScriptOwnership); //V8ScriptValue partValue = QScriptEngine::newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); if (!partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, partValue).FromMaybe(false)) { Q_ASSERT(false); } } else { //partObject = currentPath->ToObject(); //V8TODO: do these still happen if asserts are disabled? if (!partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, value.constGet()).FromMaybe(false)) { Q_ASSERT(false); } } } v8::Local<v8::Value> child; if (!partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(&child)) { Q_ASSERT(false); }Lines 1–500 of 1421. Load more
// // ScriptEngineV8.cpp // libraries/script-engine/src/v8 // // Created by Brad Hefta-Gaub on 12/14/13. // Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2013 High Fidelity, Inc. // Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ScriptEngineV8.h" #include <chrono> #include <mutex> #include <thread> #include <QtCore/QCoreApplication> #include <QtCore/QEventLoop> #include <QtCore/QFileInfo> #include <QtCore/QTimer> #include <QtCore/QThread> #include <QtCore/QRegularExpression> #include <QtCore/QFuture> #include <QtConcurrent/QtConcurrentRun> #include <QtWidgets/QMainWindow> #include <QtWidgets/QApplication> #include <QtWidgets/QMenuBar> #include <QtWidgets/QMenu> #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkReply> #include <shared/LocalFileAccessGate.h> #include <shared/QtHelpers.h> #include <shared/AbstractLoggerInterface.h> #include <Profile.h> #include "../ScriptEngineLogging.h" #include "../ScriptProgram.h" #include "../ScriptValue.h" #include "ScriptContextV8Wrapper.h" #include "ScriptObjectV8Proxy.h" #include "ScriptProgramV8Wrapper.h" #include "ScriptValueV8Wrapper.h" static const int MAX_DEBUG_VALUE_LENGTH { 80 }; std::once_flag ScriptEngineV8::_v8InitOnceFlag; QMutex ScriptEngineV8::_v8InitMutex; bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { const QThread* currentThread = QThread::currentThread(); if (currentThread == thread) { return true; } qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") .arg(method) .arg(thread ? thread->objectName() : "(!thread)") .arg(QThread::currentThread()->objectName()); qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; Q_ASSERT(false); return false; } // engine-aware JS Error copier and factory V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); } return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); //V8TODO /* auto other = _other; if (_other.constGet()->IsString()) { other = QScriptEngine::newObject(); other.setProperty("message", _other.toString()); } auto proto = QScriptEngine::globalObject().property(type); if (!proto.isFunction()) { proto = QScriptEngine::globalObject().property(other.prototype().property("constructor").property("name").toString()); } if (!proto.isFunction()) { #ifdef DEBUG_JS_EXCEPTIONS qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead"; #endif proto = QScriptEngine::globalObject().property("Error"); } if (other.engine() != this) { // JS Objects are parented to a specific script engine instance // -- this effectively ~clones it locally by routing through a QVariant and back other = QScriptEngine::toScriptValue(other.toVariant()); } // ~ var err = new Error(other.message) auto err = proto.construct(V8ScriptValueList({ other.property("message") })); // transfer over any existing properties V8ScriptValueIterator it(other); while (it.hasNext()) { it.next(); err.setProperty(it.name(), it.value()); } return err;*/ } ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); return nullValue(); //V8TODO //what does makeError actually do? /*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(_other); V8ScriptValue other; if (_other.isString()) { other = QScriptEngine::newObject(); other.setProperty("message", _other.toString()); } else if (unwrapped) { other = unwrapped->toV8Value(); } else { other = QScriptEngine::newVariant(_other.toVariant()); } V8ScriptValue result = makeError(other, type); return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result)));*/ } // check syntax and when there are issues returns an actual "SyntaxError" with the details ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); ScriptSyntaxCheckResultPointer syntaxCheck = program->checkSyntax(); //V8TODO if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { auto err = globalObject().property("SyntaxError").construct(ScriptValueList({ newValue(syntaxCheck->errorMessage()) })); err.setProperty("fileName", program->fileName()); err.setProperty("lineNumber", syntaxCheck->errorLineNumber()); err.setProperty("expressionBeginOffset", syntaxCheck->errorColumnNumber()); err.setProperty("stack", syntaxCheck->errorBacktrace()); //err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); { const auto error = syntaxCheck->errorMessage(); const auto line = QString::number(syntaxCheck->errorLineNumber()); const auto column = QString::number(syntaxCheck->errorColumnNumber()); // for compatibility with legacy reporting const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program->fileName(), line, column); err.setProperty("formatted", message); } return err; } return undefinedValue(); } /*ScriptValue ScriptEngineV8::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } //V8TODO const auto syntaxCheck = checkSyntax(sourceCode); if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { auto err = QScriptEngine::globalObject().property("SyntaxError").construct(V8ScriptValueList({ syntaxCheck.errorMessage() })); err.setProperty("fileName", fileName); err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); { const auto error = syntaxCheck.errorMessage(); const auto line = QString::number(syntaxCheck.errorLineNumber()); const auto column = QString::number(syntaxCheck.errorColumnNumber()); // for compatibility with legacy reporting const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); err.setProperty("formatted", message); } return ScriptValue(new ScriptValueV8Wrapper(this, std::move(err))); } return undefinedValue(); }*/ // this pulls from the best available information to create a detailed snapshot of the current exception ScriptValue ScriptEngineV8::cloneUncaughtException(const QString& extraDetail) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return nullValue(); } if (!hasUncaughtException()) { return nullValue(); } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); return nullValue(); //V8TODO /* auto exception = uncaughtException(); // ensure the error object is engine-local auto err = makeError(exception); // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number // on its own is often useless/wrong if arbitrarily married to a filename. // when the error object already has this info, it seems to be the most reliable auto fileName = exception.property("fileName").toString(); auto lineNumber = exception.property("lineNumber").toInt32(); // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace auto backtrace = uncaughtExceptionBacktrace(); if (backtrace.isEmpty()) { // fallback to the error object backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); } // the ad hoc "detail" property can be used now to embed additional clues auto detail = exception.property("detail").toString(); if (detail.isEmpty()) { detail = extraDetail; } else if (!extraDetail.isEmpty()) { detail += "(" + extraDetail + ")"; } if (lineNumber <= 0) { lineNumber = uncaughtExceptionLineNumber(); } if (fileName.isEmpty()) { // climb the stack frames looking for something useful to display for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { V8ScriptContextInfo info{ c }; if (!info.fileName().isEmpty()) { // take fileName:lineNumber as a pair fileName = info.fileName(); lineNumber = info.lineNumber(); if (backtrace.isEmpty()) { backtrace = c->backtrace(); } break; } } } err.setProperty("fileName", fileNlintame); err.setProperty("lineNumber", lineNumber); err.setProperty("detail", detail); err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); #ifdef DEBUG_JS_EXCEPTIONS err.setProperty("_fileName", exception.property("fileName").toString()); err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); err.setProperty("_lineNumber", uncaughtExceptionLineNumber()); #endif return err; */ } bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return false; } //V8TODO _v8Isolate->ThrowException(makeError(exception).get()); /*if (QScriptEngine::currentContext()) { // we have an active context / JS stack frame so throw the exception per usual QScriptEngine::currentContext()->throwValue(makeError(exception)); return true; } else if (_scriptManager) { // we are within a pure C++ stack frame (ie: being called directly by other C++ code) // in this case no context information is available so just emit the exception for reporting V8ScriptValue thrown = makeError(exception); emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); }*/ //emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); return false; } bool ScriptEngineV8::maybeEmitUncaughtException(const QString& debugHint) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return false; } if (!isEvaluating() && hasUncaughtException() && _scriptManager) { emit _scriptManager->unhandledException(cloneUncaughtException(debugHint)); clearExceptions(); return true; } return false; } // Lambda ScriptValue ScriptEngineV8::newLambdaFunction(std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, const V8ScriptValue& data, const ValueOwnership& ownership) { v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); auto lambda = new Lambda(this, operation, data); auto object = newQObject(lambda, ownership); //V8TODO - I'm not sure if this works auto call = object.property("call"); call.setPrototype(object); // context->callee().prototype() === Lambda QObject call.setData(ScriptValue(new ScriptValueV8Wrapper(this, data))); // context->callee().data() will === data param return call; } QString Lambda::toString() const { v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope contextScope(_engine->getContext()); v8::Local<v8::String> string; QString qString(""); if (_data.constGet()->ToString(_engine->getContext()).ToLocal(&string)) { v8::String::Utf8Value utf8Value(_engine->getIsolate(), string); qString = QString(*utf8Value); } //V8TODO it was data.isValid() originally //I have no idea what happens here return QString("[Lambda%1]").arg((!_data.constGet()->IsNullOrUndefined()) ? " " + qString : qString); } Lambda::~Lambda() { #ifdef DEBUG_JS_LAMBDA_FUNCS qDebug() << "~Lambda" << "this" << this; #endif } Lambda::Lambda(ScriptEngineV8* engine, std::function<V8ScriptValue(V8ScriptContext*, ScriptEngineV8*)> operation, V8ScriptValue data) : _engine(engine), _operation(operation), _data(data) { #ifdef DEBUG_JS_LAMBDA_FUNCS qDebug() << "Lambda" << data.toString(); #endif } V8ScriptValue Lambda::call() { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); } // V8TODO: it needs to be done in entirely different way for V8 Q_ASSERT(false); //return _operation(_engine->getContext(), _engine); //return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); //return operation(static_cast<QScriptEngine*>(engine)->currentContext(), engine); } #ifdef DEBUG_JS void ScriptEngineV8::_debugDump(const QString& header, const V8ScriptValue& object, const QString& footer) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } if (!header.isEmpty()) { qCDebug(shared) << header; } if (!object.isObject()) { qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); return; } V8ScriptValueIterator it(object); while (it.hasNext()) { it.next(); qCDebug(shared) << it.name() << ":" << it.value().toString(); } if (!footer.isEmpty()) { qCDebug(shared) << footer; } } #endif v8::Platform* ScriptEngineV8::getV8Platform() { static std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform(); return platform.get(); } ScriptEngineV8::ScriptEngineV8(ScriptManager* scriptManager) : _scriptManager(scriptManager), _isEvaluating(false) //V8TODO //_arrayBufferClass(new ArrayBufferClass(this)) { _v8InitMutex.lock(); std::call_once ( _v8InitOnceFlag, [ ]{ v8::V8::InitializeExternalStartupData(""); // Experimentally determined that the maximum size that works on Linux with a stack size of 8192K is 8182. // That would seem to be the overhead of our code and V8. // // Windows stacks are 1MB. // // Based on that, going with 256K for stacks for now. That seems like a reasonable value. // We'll probably need a more complex system on the longer term, with configurable limits. v8::V8::SetFlagsFromString("--stack-size=256"); v8::Platform* platform = getV8Platform(); v8::V8::InitializePlatform(platform); v8::V8::Initialize(); qCDebug(scriptengine) << "V8 platform initialized"; } ); _v8InitMutex.unlock(); { v8::Isolate::CreateParams isolateParams; isolateParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); _v8Isolate = v8::Isolate::New(isolateParams); _v8Isolate->Enter(); v8::HandleScope handleScope(_v8Isolate); v8::Local<v8::Context> context = v8::Context::New(_v8Isolate); Q_ASSERT(!context.IsEmpty()); v8::Context::Scope contextScope(context); _v8Context = v8::UniquePersistent<v8::Context>(_v8Isolate, context); V8ScriptValue nullScriptValue(_v8Isolate, v8::Null(_v8Isolate)); _nullValue = ScriptValue(new ScriptValueV8Wrapper(this, nullScriptValue)); V8ScriptValue undefined(_v8Isolate, v8::Undefined(_v8Isolate)); _undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, undefined)); registerSystemTypes(); // V8TODO: dispose of isolate on ScriptEngineV8 destruction //v8::UniquePersistent<v8::Value> null = v8::UniquePersistent<v8::Value>(_v8Isolate, v8::Null(_v8Isolate)); //_nullValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(null))); //V8ScriptValue undefined = v8::UniquePersistent<v8::Value>(_v8Isolate,v8::Undefined(_v8Isolate)); //_undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(undefined))); // V8TODO: //QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); } //_currentThread = QThread::currentThread(); if (_scriptManager) { // V8TODO: port to V8 /*connect(this, &QScriptEngine::signalHandlerException, this, [this](const V8ScriptValue& exception) { if (hasUncaughtException()) { // the engine's uncaughtException() seems to produce much better stack traces here emit _scriptManager->unhandledException(cloneUncaughtException("signalHandlerException")); clearExceptions(); } else { // ... but may not always be available -- so if needed we fallback to the passed exception V8ScriptValue thrown = makeError(exception); emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); } }, Qt::DirectConnection);*/ //moveToThread(scriptManager->thread()); //setThread(scriptManager->thread()); } } void ScriptEngineV8::registerEnum(const QString& enumName, QMetaEnum newEnum) { if (!newEnum.isValid()) { qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; return; } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); for (int i = 0; i < newEnum.keyCount(); i++) { const char* keyName = newEnum.key(i); QString fullName = enumName + "." + keyName; registerValue(fullName, V8ScriptValue(_v8Isolate, v8::Integer::New(_v8Isolate, newEnum.keyToValue(keyName)))); } } void ScriptEngineV8::registerValue(const QString& valueName, V8ScriptValue value) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; #endif QMetaObject::invokeMethod(this, "registerValue", Q_ARG(const QString&, valueName), Q_ARG(V8ScriptValue, value)); return; } v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(_v8Context.Get(_v8Isolate)); QStringList pathToValue = valueName.split("."); int partsToGo = pathToValue.length(); v8::Local<v8::Object> partObject = _v8Context.Get(_v8Isolate)->Global(); for (const auto& pathPart : pathToValue) { partsToGo--; v8::Local<v8::String> pathPartV8 = v8::String::NewFromUtf8(_v8Isolate, pathPart.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); v8::Local<v8::Value> currentPath; if (!partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(¤tPath)) { if (partsToGo > 0) { //This was commented out //QObject *object = new QObject; v8::Local<v8::Object> partValue = v8::Object::New(_v8Isolate); //newQObject(object, QScriptEngine::ScriptOwnership); //V8ScriptValue partValue = QScriptEngine::newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); if (!partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, partValue).FromMaybe(false)) { Q_ASSERT(false); } } else { //partObject = currentPath->ToObject(); //V8TODO: do these still happen if asserts are disabled? if (!partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, value.constGet()).FromMaybe(false)) { Q_ASSERT(false); } } } v8::Local<v8::Value> child; if (!partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(&child)) { Q_ASSERT(false); }