File indexing completed on 2024-06-30 05:51:31
0001 /* 0002 This file is part of the Okteta Kasten Framework, made within the KDE community. 0003 0004 SPDX-FileCopyrightText: 2011, 2012, 2013 Alex Richardson <alex.richardson@gmx.de> 0005 0006 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0007 */ 0008 0009 #include "defaultscriptclass.hpp" 0010 0011 #include "../../datatypes/datainformation.hpp" 0012 #include "../../datatypes/topleveldatainformation.hpp" 0013 #include "../../datatypes/uniondatainformation.hpp" 0014 #include "../../datatypes/structuredatainformation.hpp" 0015 #include "../../datatypes/primitive/pointerdatainformation.hpp" 0016 #include "../../parsers/parserutils.hpp" 0017 #include "../../parsers/scriptvalueconverter.hpp" 0018 #include "../scriptlogger.hpp" 0019 #include "../scripthandlerinfo.hpp" 0020 #include "../safereference.hpp" 0021 #include <structureslogging.hpp> 0022 // Std 0023 #include <utility> 0024 0025 DefaultScriptClass::DefaultScriptClass(QScriptEngine* engine, ScriptHandlerInfo* handlerInfo) 0026 : QScriptClass(engine) 0027 , mHandlerInfo(handlerInfo) 0028 { 0029 s_valid = engine->toStringHandle(ParserStrings::PROPERTY_VALID()); 0030 s_wasAbleToRead = engine->toStringHandle(ParserStrings::PROPERTY_ABLE_TO_READ()); 0031 s_validationError = engine->toStringHandle(ParserStrings::PROPERTY_VALIDATION_ERROR()); 0032 s_parent = engine->toStringHandle(ParserStrings::PROPERTY_PARENT()); 0033 s_byteOrder = engine->toStringHandle(ParserStrings::PROPERTY_BYTEORDER()); 0034 s_name = engine->toStringHandle(ParserStrings::PROPERTY_NAME()); 0035 s_datatype = engine->toStringHandle(ParserStrings::PROPERTY_DATATYPE()); 0036 s_updateFunc = engine->toStringHandle(ParserStrings::PROPERTY_UPDATE_FUNC()); 0037 s_validationFunc = engine->toStringHandle(ParserStrings::PROPERTY_VALIDATION_FUNC()); 0038 s_customTypeName = engine->toStringHandle(ParserStrings::PROPERTY_CUSTOM_TYPE_NAME()); 0039 s_asStringFunc = engine->toStringHandle(ParserStrings::PROPERTY_TO_STRING_FUNC()); 0040 0041 // TODO remove, every subclass should have proto 0042 mDefaultPrototype = engine->newObject(); 0043 mDefaultPrototype.setProperty(QStringLiteral("toString"), engine->newFunction(Default_proto_toString)); 0044 // add all our properties 0045 mIterableProperties.reserve(11); 0046 mIterableProperties.append(qMakePair(s_parent, QScriptValue::ReadOnly | QScriptValue::Undeletable)); 0047 mIterableProperties.append(qMakePair(s_name, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0048 mIterableProperties.append(qMakePair(s_wasAbleToRead, QScriptValue::ReadOnly | QScriptValue::Undeletable)); 0049 mIterableProperties.append(qMakePair(s_byteOrder, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0050 mIterableProperties.append(qMakePair(s_valid, QScriptValue::ReadOnly | QScriptValue::Undeletable)); 0051 mIterableProperties.append(qMakePair(s_validationError, QScriptValue::ReadOnly | QScriptValue::Undeletable)); 0052 mIterableProperties.append(qMakePair(s_validationFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0053 mIterableProperties.append(qMakePair(s_updateFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0054 mIterableProperties.append(qMakePair(s_datatype, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0055 mIterableProperties.append(qMakePair(s_customTypeName, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0056 mIterableProperties.append(qMakePair(s_asStringFunc, QScriptValue::PropertyFlags(QScriptValue::Undeletable))); 0057 } 0058 0059 DefaultScriptClass::~DefaultScriptClass() = default; 0060 0061 DataInformation* DefaultScriptClass::toDataInformation(const QScriptValue& obj) 0062 { 0063 if (!obj.scriptClass()) { 0064 return nullptr; 0065 } 0066 Q_ASSERT(obj.data().isVariant()); 0067 const QVariant variant = obj.data().toVariant(); 0068 if (variant.isValid() && variant.canConvert<SafeReference>() && variant.userType() == qMetaTypeId<SafeReference>()) { 0069 const SafeReference& ref = *reinterpret_cast<const SafeReference*>(variant.constData()); 0070 return ref.data(); 0071 } 0072 return nullptr; 0073 } 0074 0075 QScriptClass::QueryFlags DefaultScriptClass::queryProperty(const QScriptValue& object, 0076 const QScriptString& name, QScriptClass::QueryFlags flags, uint* id) 0077 { 0078 const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode(); 0079 Q_ASSERT(mode != ScriptHandlerInfo::Mode::None); 0080 DataInformation* data = toDataInformation(object); 0081 if (!data) { 0082 mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString(); 0083 engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0084 QStringLiteral("Attempting to access an invalid object")); 0085 return {}; 0086 } 0087 if (name == s_valid || name == s_validationError) { 0088 return mode == ScriptHandlerInfo::Mode::Validating ? flags : flags& ~HandlesWriteAccess; 0089 } 0090 if (mode != ScriptHandlerInfo::Mode::Updating) { 0091 // the only properties that are possibly writable when not updating are valid and validationError 0092 // but we checked them before so we remove handlesWriteAccess from the flags 0093 flags &= ~HandlesWriteAccess; 0094 } 0095 0096 if (name == s_byteOrder || name == s_name || name == s_updateFunc || name == s_validationFunc 0097 || name == s_datatype || name == s_customTypeName || name == s_asStringFunc) { 0098 return flags; 0099 } 0100 if (name == s_wasAbleToRead || name == s_parent) { 0101 return flags & ~HandlesWriteAccess; 0102 } 0103 if (queryAdditionalProperty(data, name, &flags, id)) { 0104 return flags; 0105 } 0106 0107 data->logError() << "could not find property with name" << name.toString(); 0108 engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0109 QLatin1String("Could not find property with name ") + name.toString()); 0110 return {}; 0111 } 0112 0113 QScriptValue DefaultScriptClass::property(const QScriptValue& object, const QScriptString& name, uint id) 0114 { 0115 Q_ASSERT(mHandlerInfo->mode() != ScriptHandlerInfo::Mode::None); 0116 DataInformation* data = toDataInformation(object); 0117 if (!data) { 0118 mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString(); 0119 return engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0120 QStringLiteral("Attempting to access an invalid object")); 0121 } 0122 if (name == s_valid) { 0123 return data->validationSuccessful(); 0124 } 0125 if (name == s_wasAbleToRead) { 0126 return data->wasAbleToRead(); 0127 } 0128 if (name == s_parent) { 0129 Q_CHECK_PTR(data->parent()); 0130 // parent() cannot be null 0131 if (data->parent()->isTopLevel()) { 0132 return engine()->nullValue(); 0133 } 0134 return data->parent()->asDataInformation()->toScriptValue(engine(), mHandlerInfo); 0135 } 0136 if (name == s_datatype) { 0137 return data->typeName(); 0138 } 0139 if (name == s_updateFunc) { 0140 return data->updateFunc(); 0141 } 0142 if (name == s_validationFunc) { 0143 return data->validationFunc(); 0144 } 0145 if (name == s_validationError) { 0146 return data->validationError(); 0147 } 0148 if (name == s_byteOrder) { 0149 return ParserUtils::byteOrderToString(data->byteOrder()); 0150 } 0151 if (name == s_name) { 0152 return data->name(); 0153 } 0154 if (name == s_customTypeName) { 0155 return data->typeName(); 0156 } 0157 if (name == s_asStringFunc) { 0158 return data->toStringFunction(); 0159 } 0160 QScriptValue other = additionalProperty(data, name, id); 0161 if (other.isValid()) { 0162 return other; 0163 } 0164 data->logError() << "could not find property with name" << name.toString(); 0165 return engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0166 QLatin1String("Cannot read property ") + name.toString()); 0167 } 0168 0169 void DefaultScriptClass::setDataType(const QScriptValue& value, DataInformation* data) 0170 { 0171 DataInformation* thisObj = toDataInformation(engine()->currentContext()->thisObject()); 0172 Q_CHECK_PTR(thisObj); 0173 const bool isThisObj = thisObj == data; 0174 // this object always has mHasBeenUpdated set just before calling updateFunc, so in that case it is okay 0175 if (data->hasBeenUpdated() && !isThisObj) { 0176 // this element has already been updated (and probably read, replacing it could cause crazy errors 0177 data->logError() << "Attempting to replace an already updated object. This could cause errors." 0178 "Current this object: " << (thisObj ? thisObj->fullObjectPath() : QString()); 0179 return; 0180 } 0181 // change the type of the underlying object 0182 DataInformation* newType = ScriptValueConverter::convert(value, data->name(), data->logger(), data); 0183 if (!newType) { 0184 data->logError() << "Failed to set new type, could not convert value!"; 0185 return; 0186 } 0187 0188 DataInformationBase* parent = data->parent(); 0189 Q_CHECK_PTR(parent); 0190 TopLevelDataInformation* top = data->topLevelDataInformation(); 0191 Q_CHECK_PTR(top); 0192 // only if parent is toplevel, struct or union, can we replace 0193 bool replaced = false; 0194 if (parent->isTopLevel()) { 0195 Q_ASSERT(isThisObj); // we can only do this if we are currently at the top level element 0196 parent->asTopLevel()->setActualDataInformation(newType); 0197 replaced = true; 0198 } else if (parent->isStruct()) { 0199 StructureDataInformation* stru = parent->asStruct(); 0200 int index = stru->indexOf(data); 0201 Q_ASSERT(index != -1); 0202 Q_ASSERT(uint(index) < stru->childCount()); 0203 replaced = stru->replaceChildAt(index, newType); 0204 if (!replaced) { 0205 stru->logError() << "failed to replace child at index" << index; 0206 } 0207 } else if (parent->isUnion()) { 0208 UnionDataInformation* un = parent->asUnion(); 0209 int index = un->indexOf(data); 0210 Q_ASSERT(index != -1); 0211 Q_ASSERT(uint(index) < un->childCount()); 0212 replaced = un->replaceChildAt(index, newType); 0213 if (!replaced) { 0214 un->logError() << "failed to replace child at index" << index; 0215 } 0216 } else if (parent->isPointer()) { 0217 parent->asPointer()->setPointerTarget(newType); 0218 replaced = true; 0219 } else { 0220 data->logError() << "Failed to set data type since element is not toplevel and parent" 0221 " is neither struct nor union nor pointer."; 0222 } 0223 if (replaced) { 0224 top->setChildDataChanged(); 0225 // if the current object was "this" in javascript we have to replace it 0226 if (isThisObj) { 0227 engine()->currentContext()->setThisObject(newType->toScriptValue(engine(), mHandlerInfo)); 0228 } 0229 newType->mHasBeenUpdated = true; 0230 } else { 0231 delete newType; // could not set new type 0232 } 0233 } 0234 0235 void DefaultScriptClass::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) 0236 { 0237 const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode(); 0238 Q_ASSERT(mode != ScriptHandlerInfo::Mode::None); 0239 DataInformation* data = toDataInformation(object); 0240 if (!data) { 0241 mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString(); 0242 engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0243 QStringLiteral("Attempting to access an invalid object")); 0244 return; 0245 } 0246 if (mode == ScriptHandlerInfo::Mode::Validating) { 0247 // only way write access is allowed is when validating: valid and validationError 0248 if (data->hasBeenValidated()) { 0249 data->logError() << "Cannot modify this object, it has already been validated!"; 0250 } else if (name == s_valid) { 0251 data->mValidationSuccessful = value.toBool(); 0252 } else if (name == s_validationError) { 0253 data->setValidationError(value.toString()); 0254 } else { 0255 data->logError() << "Cannot write to property" << name.toString() << "while validating!"; 0256 } 0257 return; 0258 } 0259 0260 if (mode != ScriptHandlerInfo::Mode::Updating) { 0261 data->logError() << "Writing to property" << name.toString() << "is only allowed when updating."; 0262 return; 0263 } 0264 Q_ASSERT(mode == ScriptHandlerInfo::Mode::Updating); 0265 0266 if (name == s_byteOrder) { 0267 data->setByteOrder(ParserUtils::byteOrderFromString(value.toString(), 0268 LoggerWithContext(data->logger(), data->fullObjectPath()))); 0269 } else if (name == s_datatype) { 0270 // change the type of the underlying object 0271 setDataType(value, data); 0272 } else if (name == s_updateFunc) { 0273 data->setUpdateFunc(value); 0274 } else if (name == s_validationFunc) { 0275 data->setValidationFunc(value); 0276 } else if (name == s_name) { 0277 data->setName(value.toString()); 0278 } else if (name == s_customTypeName) { 0279 if (!value.isValid() || value.isNull() || value.isUndefined()) { 0280 data->setCustomTypeName(QString()); // unset 0281 } else { 0282 data->setCustomTypeName(value.toString()); 0283 } 0284 } else if (name == s_asStringFunc) { 0285 data->setToStringFunction(value); 0286 } else { 0287 bool setAdditional = setAdditionalProperty(data, name, id, value); 0288 if (setAdditional) { 0289 return; 0290 } 0291 data->logError() << "could not set property with name" << name.toString(); 0292 engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0293 QLatin1String("Cannot write property ") + name.toString()); 0294 } 0295 } 0296 0297 QScriptValue::PropertyFlags DefaultScriptClass::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) 0298 { 0299 QScriptValue::PropertyFlags result; 0300 const ScriptHandlerInfo::Mode mode = mHandlerInfo->mode(); 0301 Q_ASSERT(mode != ScriptHandlerInfo::Mode::None); 0302 DataInformation* data = toDataInformation(object); 0303 if (!data) { 0304 mHandlerInfo->logger()->error() << "could not cast data from" << object.data().toString(); 0305 engine()->currentContext()->throwError(QScriptContext::ReferenceError, 0306 QStringLiteral("Attempting to access an invalid object")); 0307 return {}; 0308 } 0309 if (name == s_valid || name == s_validationError) { 0310 if (mode != ScriptHandlerInfo::Mode::Validating) { 0311 result |= QScriptValue::ReadOnly; 0312 } 0313 } else if (mode != ScriptHandlerInfo::Mode::Updating) { 0314 result |= QScriptValue::ReadOnly; 0315 } 0316 0317 for (const auto& property : std::as_const(mIterableProperties)) { 0318 if (property.first == name) { 0319 return result | property.second; 0320 } 0321 } 0322 0323 if (additionalPropertyFlags(data, name, id, &result)) { 0324 return result; // is a child element 0325 } 0326 data->logError() << "could not find flags for property with name" << name.toString(); 0327 return {}; 0328 } 0329 0330 QScriptValue DefaultScriptClass::prototype() const 0331 { 0332 return mDefaultPrototype; 0333 } 0334 0335 QScriptValue DefaultScriptClass::Default_proto_toString(QScriptContext* ctx, QScriptEngine* eng) 0336 { 0337 DataInformation* data = toDataInformation(ctx->thisObject()); 0338 if (!data) { 0339 qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "could not cast data"; 0340 return eng->undefinedValue(); 0341 } 0342 return QString(data->typeName() + QLatin1Char(' ') + data->name()); 0343 } 0344 0345 QScriptClassPropertyIterator* DefaultScriptClass::newIterator(const QScriptValue& object) 0346 { 0347 return new DefaultscriptClassIterator(object, this); 0348 } 0349 0350 DefaultscriptClassIterator::DefaultscriptClassIterator(const QScriptValue& object, DefaultScriptClass* cls) 0351 : QScriptClassPropertyIterator(object) 0352 , mClass(cls) 0353 { 0354 DataInformation* data = DefaultScriptClass::toDataInformation(object); 0355 Q_CHECK_PTR(data); 0356 mData = data; 0357 } 0358 0359 DefaultscriptClassIterator::~DefaultscriptClassIterator() = default; 0360 0361 bool DefaultscriptClassIterator::hasNext() const 0362 { 0363 return mCurrent < mClass->mIterableProperties.size() - 1; 0364 } 0365 0366 bool DefaultscriptClassIterator::hasPrevious() const 0367 { 0368 return mCurrent > 0; 0369 } 0370 0371 QScriptString DefaultscriptClassIterator::name() const 0372 { 0373 Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount()); 0374 if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) { 0375 return {}; 0376 } 0377 if (mCurrent < mClass->mIterableProperties.size()) { 0378 return mClass->mIterableProperties.at(mCurrent).first; 0379 } 0380 int index = mCurrent - mClass->mIterableProperties.size(); 0381 Q_ASSERT(index >= 0); 0382 DataInformation* child = mData->childAt(index); 0383 return mClass->engine()->toStringHandle(child->name()); 0384 } 0385 0386 QScriptValue::PropertyFlags DefaultscriptClassIterator::flags() const 0387 { 0388 Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount()); 0389 if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) { 0390 return {}; 0391 } 0392 if (mCurrent < mClass->mIterableProperties.size()) { 0393 return mClass->propertyFlags(object(), mClass->mIterableProperties.at(mCurrent).first, id()); 0394 } 0395 return QScriptValue::ReadOnly; 0396 } 0397 0398 uint DefaultscriptClassIterator::id() const 0399 { 0400 Q_ASSERT(mCurrent >= 0 && (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount()); 0401 if (mCurrent < 0 || (uint)mCurrent >= mClass->mIterableProperties.size() + mData->childCount()) { 0402 return 0; 0403 } 0404 // only children have an id assigned 0405 if (mCurrent < mClass->mIterableProperties.size()) { 0406 return 0; 0407 } 0408 return mCurrent - mClass->mIterableProperties.size() + 1; 0409 } 0410 0411 void DefaultscriptClassIterator::next() 0412 { 0413 Q_ASSERT(mCurrent == -1 || (uint)mCurrent < mClass->mIterableProperties.size() + mData->childCount()); 0414 mCurrent++; 0415 } 0416 0417 void DefaultscriptClassIterator::previous() 0418 { 0419 Q_ASSERT(mCurrent >= 0); 0420 mCurrent--; 0421 } 0422 0423 void DefaultscriptClassIterator::toBack() 0424 { 0425 mCurrent = mClass->mIterableProperties.size() + mData->childCount(); 0426 } 0427 0428 void DefaultscriptClassIterator::toFront() 0429 { 0430 mCurrent = -1; 0431 }