diff options
Diffstat (limited to 'src/lib/corelib/language/evaluator.cpp')
-rw-r--r-- | src/lib/corelib/language/evaluator.cpp | 818 |
1 files changed, 735 insertions, 83 deletions
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp index 84272af2f..5252f06be 100644 --- a/src/lib/corelib/language/evaluator.cpp +++ b/src/lib/corelib/language/evaluator.cpp @@ -39,18 +39,19 @@ #include "evaluator.h" -#include "evaluationdata.h" -#include "evaluatorscriptclass.h" #include "filecontext.h" #include "filetags.h" #include "item.h" #include "scriptengine.h" #include "value.h" +#include <quickjs.h> + #include <buildgraph/buildgraph.h> #include <jsextensions/jsextensions.h> #include <logging/translator.h> #include <tools/error.h> +#include <tools/fileinfo.h> #include <tools/scripttools.h> #include <tools/qbsassert.h> #include <tools/qttools.h> @@ -61,27 +62,61 @@ namespace qbs { namespace Internal { +class EvaluationData +{ +public: + Evaluator *evaluator = nullptr; + const Item *item = nullptr; + mutable QHash<QString, JSValue> valueCache; +}; + +static void convertToPropertyType_impl(ScriptEngine *engine, + const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, + const CodeLocation &location, JSValue &v); +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, + JSValueConst obj); +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop); + +static bool debugProperties = false; + Evaluator::Evaluator(ScriptEngine *scriptEngine) : m_scriptEngine(scriptEngine) - , m_scriptClass(new EvaluatorScriptClass(scriptEngine)) + , m_scriptClass(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED, + getEvalPropertyNames, getEvalProperty)) { + scriptEngine->registerEvaluator(this); } Evaluator::~Evaluator() { - for (const auto &data : qAsConst(m_scriptValueMap)) - delete attachedPointer<EvaluationData>(data); - delete m_scriptClass; + Set<JSValue> valuesToFree; + for (const auto &data : qAsConst(m_scriptValueMap)) { + const auto evalData = attachedPointer<EvaluationData>(data, m_scriptClass); + valuesToFree << data; + for (const JSValue cachedValue : evalData->valueCache) + JS_FreeValue(m_scriptEngine->context(), cachedValue); + delete evalData; + } + for (const auto &scopes : qAsConst(m_fileContextScopesMap)) { + valuesToFree << scopes.fileScope; + valuesToFree << scopes.importScope; + } + for (const JSValue v : qAsConst(valuesToFree)) { + JS_FreeValue(m_scriptEngine->context(), v); + } + m_scriptEngine->unregisterEvaluator(this); } -QScriptValue Evaluator::property(const Item *item, const QString &name) +JSValue Evaluator::property(const Item *item, const QString &name) { - return scriptValue(item).property(name); + return getJsProperty(m_scriptEngine->context(), scriptValue(item), name); } -QScriptValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) +JSValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v; + JSValue v; evaluateProperty(&v, item, name, propertyWasSet); return v; } @@ -89,16 +124,19 @@ QScriptValue Evaluator::value(const Item *item, const QString &name, bool *prope bool Evaluator::boolValue(const Item *item, const QString &name, bool *propertyWasSet) { - return value(item, name, propertyWasSet).toBool(); + const ScopedJsValue sv(m_scriptEngine->context(), value(item, name, propertyWasSet)); + return JS_ToBool(m_scriptEngine->context(), sv); } int Evaluator::intValue(const Item *item, const QString &name, int defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toInt32(); + qint32 n; + JS_ToInt32(m_scriptEngine->context(), &n, v); + return n; } FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *propertySet) @@ -109,24 +147,27 @@ FileTags Evaluator::fileTagsValue(const Item *item, const QString &name, bool *p QString Evaluator::stringValue(const Item *item, const QString &name, const QString &defaultValue, bool *propertyWasSet) { - QScriptValue v; + JSValue v; if (!evaluateProperty(&v, item, name, propertyWasSet)) return defaultValue; - return v.toString(); + QString str = getJsString(m_scriptEngine->context(), v); + JS_FreeValue(m_scriptEngine->context(), v); + return str; } -static QStringList toStringList(const QScriptValue &scriptValue) +static QStringList toStringList(ScriptEngine *engine, const JSValue &scriptValue) { - if (scriptValue.isString()) - return {scriptValue.toString()}; - if (scriptValue.isArray()) { + if (JS_IsString(scriptValue)) + return {getJsString(engine->context(), scriptValue)}; + if (JS_IsArray(engine->context(), scriptValue)) { QStringList lst; int i = 0; for (;;) { - QScriptValue elem = scriptValue.property(i++); - if (!elem.isValid()) + JSValue elem = JS_GetPropertyUint32(engine->context(), scriptValue, i++); + if (JS_IsUndefined(elem)) break; - lst.push_back(elem.toString()); + lst.push_back(getJsString(engine->context(), elem)); + JS_FreeValue(engine->context(), elem); } return lst; } @@ -142,13 +183,13 @@ QStringList Evaluator::stringListValue(const Item *item, const QString &name, bo std::optional<QStringList> Evaluator::optionalStringListValue( const Item *item, const QString &name, bool *propertyWasSet) { - QScriptValue v = property(item, name); - handleEvaluationError(item, name, v); + const ScopedJsValue v(m_scriptEngine->context(), property(item, name)); + handleEvaluationError(item, name); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - if (v.isUndefined()) + if (JS_IsUndefined(v)) return std::nullopt; - return toStringList(v); + return toStringList(m_scriptEngine, v); } bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const @@ -159,15 +200,15 @@ bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const } void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc, - QScriptValue &v) + JSValue &v) { - m_scriptClass->convertToPropertyType(decl, loc, v); + convertToPropertyType_impl(engine(), QString(), nullptr, decl, loc, v); } -QScriptValue Evaluator::scriptValue(const Item *item) +JSValue Evaluator::scriptValue(const Item *item) { - QScriptValue &scriptValue = m_scriptValueMap[item]; - if (scriptValue.isObject()) { + JSValue &scriptValue = m_scriptValueMap[item]; + if (JS_IsObject(scriptValue)) { // already initialized return scriptValue; } @@ -177,109 +218,720 @@ QScriptValue Evaluator::scriptValue(const Item *item) edata->item = item; edata->item->setObserver(this); - scriptValue = m_scriptEngine->newObject(m_scriptClass); + scriptValue = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass); attachPointerTo(scriptValue, edata); return scriptValue; } void Evaluator::onItemPropertyChanged(Item *item) { - auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item)); - if (data) + const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item), + m_scriptEngine->dataWithPtrClass()); + if (data) { + for (const auto value : qAsConst(data->valueCache)) + JS_FreeValue(m_scriptEngine->context(), value); data->valueCache.clear(); + } } -void Evaluator::handleEvaluationError(const Item *item, const QString &name, - const QScriptValue &scriptValue) +void Evaluator::handleEvaluationError(const Item *item, const QString &name) { - throwOnEvaluationError(m_scriptEngine, scriptValue, [&item, &name] () { + throwOnEvaluationError(m_scriptEngine, [&item, &name] () { const ValueConstPtr &value = item->property(name); return value ? value->location() : CodeLocation(); }); } -void Evaluator::setPathPropertiesBaseDir(const QString &dirPath) -{ - m_scriptClass->setPathPropertiesBaseDir(dirPath); -} - -void Evaluator::clearPathPropertiesBaseDir() -{ - m_scriptClass->clearPathPropertiesBaseDir(); -} - -bool Evaluator::evaluateProperty(QScriptValue *result, const Item *item, const QString &name, +bool Evaluator::evaluateProperty(JSValue *result, const Item *item, const QString &name, bool *propertyWasSet) { *result = property(item, name); - handleEvaluationError(item, name, *result); + ScopedJsValue valMgr(m_scriptEngine->context(), *result); + handleEvaluationError(item, name); + valMgr.release(); if (propertyWasSet) *propertyWasSet = isNonDefaultValue(item, name); - return result->isValid() && !result->isUndefined(); + return !JS_IsUndefined(*result); } Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConstPtr &file) { FileContextScopes &result = m_fileContextScopesMap[file]; - if (!result.fileScope.isObject()) { + if (!JS_IsObject(result.fileScope)) { if (file->idScope()) result.fileScope = scriptValue(file->idScope()); else result.fileScope = m_scriptEngine->newObject(); - result.fileScope.setProperty(StringConstants::filePathGlobalVar(), file->filePath()); - result.fileScope.setProperty(StringConstants::pathGlobalVar(), file->dirPath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::filePathGlobalVar(), file->filePath()); + setJsProperty(m_scriptEngine->context(), result.fileScope, + StringConstants::pathGlobalVar(), file->dirPath()); } - if (!result.importScope.isObject()) { + if (!JS_IsObject(result.importScope)) { try { result.importScope = m_scriptEngine->newObject(); setupScriptEngineForFile(m_scriptEngine, file, result.importScope, ObserveMode::Enabled); } catch (const ErrorInfo &e) { - result.importScope = m_scriptEngine->currentContext()->throwError(e.toString()); + result.importScope = throwError(m_scriptEngine->context(), e.toString()); } } return result; } -void Evaluator::setCachingEnabled(bool enabled) +void throwOnEvaluationError(ScriptEngine *engine, + const std::function<CodeLocation()> &provideFallbackCodeLocation) { - m_scriptClass->setValueCacheEnabled(enabled); + if (JsException ex = engine->checkAndClearException(provideFallbackCodeLocation())) + throw ex.toErrorInfo(); } -PropertyDependencies Evaluator::propertyDependencies() const +static void makeTypeError(ScriptEngine *engine, const ErrorInfo &error, JSValue &v) { - return m_scriptClass->propertyDependencies(); + v = throwError(engine->context(), error.toString()); } -void Evaluator::clearPropertyDependencies() +static void makeTypeError(ScriptEngine *engine, const PropertyDeclaration &decl, + const CodeLocation &location, JSValue &v) { - m_scriptClass->clearPropertyDependencies(); + const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") + .arg(decl.name(), decl.typeString()), location); + makeTypeError(engine, error, v); } -void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, - const std::function<CodeLocation()> &provideFallbackCodeLocation) +static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) { - if (Q_LIKELY(!engine->hasErrorOrException(scriptValue))) + const VariantValuePtr v = item->variantProperty + (StringConstants::qbsSourceDirPropertyInternal()); + return v ? v->value().toString() : defaultValue; +} + +static void convertToPropertyType_impl(ScriptEngine *engine, + const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, + const CodeLocation &location, JSValue &v) +{ + JSContext * const ctx = engine->context(); + if (JS_IsUndefined(v) || JS_IsError(ctx, v) || JS_IsException(v)) return; - QString message; - QString filePath; - int line = -1; - const QScriptValue value = scriptValue.isError() ? scriptValue - : engine->uncaughtException(); - if (value.isError()) { - QScriptValue v = value.property(QStringLiteral("message")); - if (v.isString()) - message = v.toString(); - v = value.property(StringConstants::fileNameProperty()); - if (v.isString()) - filePath = v.toString(); - v = value.property(QStringLiteral("lineNumber")); - if (v.isNumber()) - line = v.toInt32(); - throw ErrorInfo(message, CodeLocation(filePath, line, -1, false)); - } - message = value.toString(); - throw ErrorInfo(message, provideFallbackCodeLocation()); + QString srcDir; + QString actualBaseDir; + if (item && !pathPropertiesBaseDir.isEmpty()) { + const VariantValueConstPtr itemSourceDir + = item->variantProperty(QStringLiteral("sourceDirectory")); + actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; + } + switch (decl.type()) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Boolean: + if (!JS_IsBool(v)) + v = JS_NewBool(ctx, JS_ToBool(ctx, v)); + break; + case PropertyDeclaration::Integer: + if (!JS_IsNumber(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::Path: + { + if (!JS_IsString(v)) { + makeTypeError(engine, decl, location, v); + break; + } + const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) + : pathPropertiesBaseDir; + if (!srcDir.isEmpty()) { + v = engine->toScriptValue(QDir::cleanPath(FileInfo::resolvePath(srcDir, + getJsString(ctx, v)))); + JS_FreeValue(ctx, v); + } + break; + } + case PropertyDeclaration::String: + if (!JS_IsString(v)) + makeTypeError(engine, decl, location, v); + break; + case PropertyDeclaration::PathList: + srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) + : pathPropertiesBaseDir; + // Fall-through. + case PropertyDeclaration::StringList: + { + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + const quint32 c = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 i = 0; i < c; ++i) { + const ScopedJsValue elem(ctx, JS_GetPropertyUint32(ctx, v, i)); + if (JS_IsUndefined(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (JS_IsNull(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (!JS_IsString(elem)) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " + "string type.").arg(i).arg(decl.name()), location); + makeTypeError(engine, error, v); + break; + } + if (srcDir.isEmpty()) + continue; + const JSValue newElem = engine->toScriptValue( + QDir::cleanPath(FileInfo::resolvePath(srcDir, getJsString(ctx, elem)))); + JS_SetPropertyUint32(ctx, v, i, newElem); + } + break; + } + case PropertyDeclaration::VariantList: + if (!JS_IsArray(ctx, v)) { + JSValue x = engine->newArray(1, JsValueOwner::ScriptEngine); + JS_SetPropertyUint32(ctx, x, 0, JS_DupValue(ctx, v)); + v = x; + } + break; + } +} + +static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen, JSValue obj) +{ + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + const Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + if (!data) + return -1; + const Item::PropertyMap &map = data->item->properties(); + *plen = map.size(); + if (!map.isEmpty()) { + *ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab)); + JSPropertyEnum *entry = *ptab; + for (auto it = map.cbegin(); it != map.cend(); ++it, ++entry) { + entry->atom = JS_NewAtom(ctx, it.key().toUtf8().constData()); + entry->is_enumerable = 1; + } + } else { + *ptab = nullptr; + } + return 0; +} + +class PropertyStackManager +{ +public: + PropertyStackManager(const Item *itemOfProperty, const QString &name, const Value *value, + std::stack<QualifiedId> &requestedProperties, + PropertyDependencies &propertyDependencies) + : m_requestedProperties(requestedProperties) + { + if (value->type() == Value::JSSourceValueType + && (itemOfProperty->type() == ItemType::ModuleInstance + || itemOfProperty->type() == ItemType::Module + || itemOfProperty->type() == ItemType::Export)) { + const VariantValueConstPtr varValue + = itemOfProperty->variantProperty(StringConstants::nameProperty()); + if (!varValue) + return; + m_stackUpdate = true; + const QualifiedId fullPropName + = QualifiedId::fromString(varValue->value().toString()) << name; + if (!requestedProperties.empty()) + propertyDependencies[fullPropName].insert(requestedProperties.top()); + m_requestedProperties.push(fullPropName); + } + } + + ~PropertyStackManager() + { + if (m_stackUpdate) + m_requestedProperties.pop(); + } + +private: + std::stack<QualifiedId> &m_requestedProperties; + bool m_stackUpdate = false; +}; + +class SVConverter : ValueHandler +{ + ScriptEngine * const engine; + const JSValue * const object; + Value * const valuePtr; + const Item * const itemOfProperty; + const QString * const propertyName; + const EvaluationData * const data; + JSValue * const result; + JSValueList scopeChain; + char pushedScopesCount; + +public: + + SVConverter(ScriptEngine *engine, const JSValue *obj, const ValuePtr &v, + const Item *_itemOfProperty, const QString *propertyName, + const EvaluationData *data, JSValue *result) + : engine(engine) + , object(obj) + , valuePtr(v.get()) + , itemOfProperty(_itemOfProperty) + , propertyName(propertyName) + , data(data) + , result(result) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + friend class AutoScopePopper; + + class AutoScopePopper + { + public: + AutoScopePopper(SVConverter *converter) + : m_converter(converter) + { + } + + ~AutoScopePopper() + { + m_converter->popScopes(); + } + + private: + SVConverter *m_converter; + }; + + void setupConvenienceProperty(const QString &conveniencePropertyName, JSValue *extraScope, + const JSValue &scriptValue) + { + if (!JS_IsObject(*extraScope)) + *extraScope = engine->newObject(); + const PropertyDeclaration::Type type + = itemOfProperty->propertyDeclaration(*propertyName).type(); + const bool isArray = type == PropertyDeclaration::StringList + || type == PropertyDeclaration::PathList + || type == PropertyDeclaration::Variant // TODO: Why? + || type == PropertyDeclaration::VariantList; + JSValue valueToSet = JS_DupValue(engine->context(), scriptValue); + if (isArray && JS_IsUndefined(valueToSet)) + valueToSet = engine->newArray(0, JsValueOwner::Caller); + setJsProperty(engine->context(), *extraScope, conveniencePropertyName, valueToSet); + } + + std::pair<JSValue, bool> createExtraScope(const JSSourceValue *value, Item *outerItem, + JSValue *outerScriptValue) + { + std::pair<JSValue, bool> result; + auto &extraScope = result.first; + result.second = true; + if (value->sourceUsesBase()) { + JSValue baseValue = JS_UNDEFINED; + if (value->baseValue()) { + SVConverter converter(engine, object, value->baseValue(), itemOfProperty, + propertyName, data, &baseValue); + converter.start(); + } + setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); + } + if (value->sourceUsesOuter()) { + JSValue v = JS_UNDEFINED; + bool doSetup = false; + if (outerItem) { + v = data->evaluator->property(outerItem, *propertyName); + if (JsException ex = engine->checkAndClearException({})) { + extraScope = engine->throwError(ex.toErrorInfo().toString()); + result.second = false; + return result; + } + doSetup = true; + JS_FreeValue(engine->context(), v); + } else if (outerScriptValue) { + doSetup = true; + v = *outerScriptValue; + } + if (doSetup) + setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); + } + if (value->sourceUsesOriginal()) { + JSValue originalValue = JS_UNDEFINED; + ScopedJsValue originalMgr(engine->context(), JS_UNDEFINED); + if (data->item->propertyDeclaration(*propertyName).isScalar()) { + const Item *item = itemOfProperty; + if (item->type() == ItemType::Module || item->type() == ItemType::Export) { + const QString errorMessage = Tr::tr("The special value 'original' cannot " + "be used on the right-hand side of a property declaration."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + // TODO: Provide a dedicated item type for not-yet-instantiated things that + // look like module instances in the AST visitor. + if (item->type() == ItemType::ModuleInstance + && !item->hasProperty(StringConstants::presentProperty())) { + const QString errorMessage = Tr::tr("Trying to assign property '%1' " + "on something that is not a module.").arg(*propertyName); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + while (item->type() == ItemType::ModuleInstance) + item = item->prototype(); + if (item->type() != ItemType::Module && item->type() != ItemType::Export) { + const QString errorMessage = Tr::tr("The special value 'original' can only " + "be used with module properties."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + const ValuePtr v = item->property(*propertyName); + + // This can happen when resolving shadow products. The error will be ignored + // in that case. + if (!v) { + const QString errorMessage = Tr::tr("Error setting up 'original'."); + extraScope = throwError(engine->context(), errorMessage); + result.second = false; + return result; + } + + SVConverter converter(engine, object, v, item, propertyName, data, &originalValue); + converter.start(); + } else { + originalValue = engine->newArray(0, JsValueOwner::Caller); + originalMgr.setValue(originalValue); + } + setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue); + } + return result; + } + + void pushScope(const JSValue &scope) + { + if (JS_IsObject(scope)) { + scopeChain << scope; + ++pushedScopesCount; + } + } + + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scopeChain.pop_back(); + } + + void handle(JSSourceValue *value) override + { + JSValue outerScriptValue = JS_UNDEFINED; + for (const JSSourceValue::Alternative &alternative : value->alternatives()) { + if (alternative.value->sourceUsesOuter() + && !data->item->outerItem() + && JS_IsUndefined(outerScriptValue)) { + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); + if (sver.hasError) { + *result = sver.scriptValue; + return; + } + outerScriptValue = sver.scriptValue; + } + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), + data->item->outerItem(), + &alternative, + value, &outerScriptValue); + if (!sver.tryNextAlternative || sver.hasError) { + *result = sver.scriptValue; + return; + } + } + *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; + } + + struct JSSourceValueEvaluationResult + { + JSValue scriptValue = JS_UNDEFINED; + bool tryNextAlternative = true; + bool hasError = false; + }; + + JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, + const JSSourceValue::Alternative *alternative = nullptr, + JSSourceValue *elseCaseValue = nullptr, JSValue *outerScriptValue = nullptr) + { + JSSourceValueEvaluationResult result; + QBS_ASSERT(!alternative || value == alternative->value.get(), return result); + AutoScopePopper autoScopePopper(this); + auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); + if (!maybeExtraScope.second) { + result.scriptValue = maybeExtraScope.first; + result.hasError = true; + return result; + } + const ScopedJsValue extraScopeMgr(engine->context(), maybeExtraScope.first); + const Evaluator::FileContextScopes fileCtxScopes + = data->evaluator->fileContextScopes(value->file()); + if (JsException ex = engine->checkAndClearException({})) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + result.hasError = true; + return result; + } + pushScope(fileCtxScopes.fileScope); + pushItemScopes(data->item); + if (itemOfProperty->type() != ItemType::ModuleInstance) { + // Own properties of module instances must not have the instance itself in the scope. + pushScope(*object); + } + if (value->definingItem()) + pushItemScopes(value->definingItem()); + pushScope(maybeExtraScope.first); + pushScope(fileCtxScopes.importScope); + if (alternative) { + ScopedJsValue sv(engine->context(), engine->evaluate(JsValueOwner::Caller, + alternative->condition.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException(alternative->condition.location)) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + //result.scriptValue = ex.takeValue(); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) { + // The condition is true. Continue evaluating the value. + result.tryNextAlternative = false; + } else { + // The condition is false. Try the next alternative or the else value. + result.tryNextAlternative = true; + return result; + } + sv.reset(engine->evaluate(JsValueOwner::Caller, + alternative->overrideListProperties.value, {}, 1, scopeChain)); + if (JsException ex = engine->checkAndClearException( + alternative->overrideListProperties.location)) { + result.scriptValue = engine->throwError(ex.toErrorInfo().toString()); + //result.scriptValue = JS_Throw(engine->context(), ex.takeValue()); + result.hasError = true; + return result; + } + if (JS_ToBool(engine->context(), sv)) + elseCaseValue->setIsExclusiveListValue(); + } + result.scriptValue = engine->evaluate(JsValueOwner::ScriptEngine, + value->sourceCodeForEvaluation(), value->file()->filePath(), value->line(), + scopeChain); + return result; + } + + void handle(ItemValue *value) override + { + *result = data->evaluator->scriptValue(value->item()); + if (JS_IsUninitialized(*result)) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) override + { + *result = engine->toScriptValue(variantValue->value()); + engine->takeOwnership(*result); + } +}; + + +static QString resultToString(JSContext *ctx, const JSValue &scriptValue) +{ + if (JS_IsUndefined(scriptValue)) + return QLatin1String("undefined"); + if (JS_IsArray(ctx, scriptValue)) + return getJsStringList(ctx, scriptValue).join(QLatin1Char(',')); + if (JS_IsObject(scriptValue)) { + return QStringLiteral("[Object: ") + + QString::number(jsObjectId(scriptValue)) + QLatin1Char(']'); + } + return getJsVariant(ctx, scriptValue).toString(); +} + +static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationData *data, + JSValue *result, const QString &propertyName, + const ValuePtr &value) +{ + JSValueList lst; + Set<Value *> ¤tNextChain = data->evaluator->currentNextChain(); + Set<Value *> oldNextChain = currentNextChain; + for (ValuePtr next = value; next; next = next->next()) + currentNextChain.insert(next.get()); + + for (ValuePtr next = value; next; next = next->next()) { + ScopedJsValue v(engine->context(), + data->evaluator->property(next->definingItem(), propertyName)); + if (JsException ex = engine->checkAndClearException({})) { + const ScopedJsValueList l(engine->context(), lst); + *result = engine->throwError(ex.toErrorInfo().toString()); + return; + } + if (JS_IsUndefined(v)) + continue; + lst.push_back(v.release()); + if (next->type() == Value::JSSourceValueType + && std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) { + // TODO: Why on earth do we keep the last _2_ elements? + auto keepIt = lst.rbegin(); + for (int i = 0; i < 2 && keepIt != lst.rend(); ++i) + ++keepIt; + for (auto it = lst.begin(); it < keepIt.base(); ++it) + JS_FreeValue(engine->context(), *it); + lst.erase(lst.begin(), keepIt.base()); + break; + } + } + currentNextChain = oldNextChain; + + *result = engine->newArray(int(lst.size()), JsValueOwner::ScriptEngine); + quint32 k = 0; + JSContext * const ctx = engine->context(); + for (const JSValue &v : qAsConst(lst)) { + QBS_ASSERT(!JS_IsError(ctx, v), continue); + if (JS_IsArray(ctx, v)) { + const quint32 vlen = getJsIntProperty(ctx, v, StringConstants::lengthProperty()); + for (quint32 j = 0; j < vlen; ++j) + JS_SetPropertyUint32(ctx, *result, k++, JS_GetPropertyUint32(ctx, v, j)); + JS_FreeValue(ctx, v); + } else { + JS_SetPropertyUint32(ctx, *result, k++, v); + } + } + setJsProperty(ctx, *result, StringConstants::lengthProperty(), JS_NewInt32(ctx, k)); +} + +static void convertToPropertyType(ScriptEngine *engine, const Item *item, + const PropertyDeclaration& decl, const Value *value, JSValue &v) +{ + if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) { + v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237 + return; + } + convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl, + value->location(), v); +} + +struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; }; +static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item, + const QString &name, const EvaluationData *data) +{ + Evaluator * const evaluator = data->evaluator; + for (; item; item = item->prototype()) { + const ValuePtr value = item->ownProperty(name); + if (!value) + continue; + const Item * const itemOfProperty = item; // The item that owns the property. + PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), + evaluator->requestedProperties(), + evaluator->propertyDependencies()); + JSValue result; + if (evaluator->cachingEnabled()) { + const auto result = data->valueCache.constFind(name); + if (result != data->valueCache.constEnd()) { + if (debugProperties) + qDebug() << "[SC] cache hit " << name << ": " + << resultToString(engine->context(), *result); + return {*result, true}; + } + } + + if (value->next() && !evaluator->currentNextChain().contains(value.get())) { + collectValuesFromNextChain(engine, data, &result, name, value); + } else { + SVConverter converter(engine, &obj, value, itemOfProperty, &name, data, &result); + converter.start(); + const PropertyDeclaration decl = data->item->propertyDeclaration(name); + convertToPropertyType(engine, data->item, decl, value.get(), result); + } + + if (debugProperties) + qDebug() << "[SC] cache miss " << name << ": " + << resultToString(engine->context(), result); + if (evaluator->cachingEnabled()) { + const auto it = data->valueCache.find(name); + if (it != data->valueCache.end()) { + JS_FreeValue(engine->context(), it.value()); + it.value() = JS_DupValue(engine->context(), result); + } else { + data->valueCache.insert(name, JS_DupValue(engine->context(), result)); + } + } + return {result, true}; + } + return {JS_UNDEFINED, false}; +} + +static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue obj, JSAtom prop) +{ + if (desc) { + desc->getter = desc->setter = desc->value = JS_UNDEFINED; + desc->flags = JS_PROP_ENUMERABLE; + } + ScriptEngine * const engine = ScriptEngine::engineForContext(ctx); + Evaluator * const evaluator = engine->evaluator(); + const auto data = attachedPointer<EvaluationData>(obj, evaluator->classId()); + const QString name = getJsString(ctx, prop); + if (debugProperties) + qDebug() << "[SC] queryProperty " << jsObjectId(obj) << " " << name; + + if (name == QStringLiteral("parent")) { + if (desc) { + Item * const parent = data->item->parent(); + desc->value = parent + ? JS_DupValue(ctx, data->evaluator->scriptValue(data->item->parent())) + : JS_UNDEFINED; + } + return 1; + } + + if (!data) { + if (debugProperties) + qDebug() << "[SC] queryProperty: no data attached"; + engine->setLastLookupStatus(false); + return -1; + } + + EvalResult result = getEvalProperty(engine, obj, data->item, name, data); + if (!result.found && data->item->parent()) { + if (debugProperties) + qDebug() << "[SC] queryProperty: query parent"; + const Item * const parentItem = data->item->parent(); + result = getEvalProperty(engine, evaluator->scriptValue(parentItem), parentItem, + name, data); + } + if (result.found) { + if (desc) + desc->value = JS_DupValue(ctx, result.v); + engine->setLastLookupStatus(true); + return 1; + } + + if (debugProperties) + qDebug() << "[SC] queryProperty: no such property"; + engine->setLastLookupStatus(false); + return 0; } } // namespace Internal |