summaryrefslogtreecommitdiff
path: root/src/lib/corelib/language/evaluator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/corelib/language/evaluator.cpp')
-rw-r--r--src/lib/corelib/language/evaluator.cpp818
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 *> &currentNextChain = 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