File indexing completed on 2025-01-05 05:23:45

0001 /*
0002     This file is part of the Okteta Kasten Framework, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2010, 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 "scriptengineinitializer.hpp"
0010 
0011 #include "../datatypes/primitivefactory.hpp"
0012 #include "../parsers/parserutils.hpp"
0013 #include <QStringList>
0014 #include <QFile>
0015 #include <QScriptValue>
0016 #include <QScriptEngine>
0017 #include <QScriptContext>
0018 #include <QScriptValueIterator>
0019 #include <QStandardPaths>
0020 
0021 namespace ScriptEngineInitializer {
0022 
0023 void addFuctionsToScriptEngine(QScriptEngine* engine)
0024 {
0025     engine->globalObject().setProperty(QStringLiteral("uint8"),
0026                                        engine->newFunction(Private::scriptNewUInt8));
0027     engine->globalObject().setProperty(QStringLiteral("uint16"),
0028                                        engine->newFunction(Private::scriptNewUInt16));
0029     engine->globalObject().setProperty(QStringLiteral("uint32"),
0030                                        engine->newFunction(Private::scriptNewUInt32));
0031     engine->globalObject().setProperty(QStringLiteral("uint64"),
0032                                        engine->newFunction(Private::scriptNewUInt64));
0033 
0034     engine->globalObject().setProperty(QStringLiteral("int8"),
0035                                        engine->newFunction(Private::scriptNewInt8));
0036     engine->globalObject().setProperty(QStringLiteral("int16"),
0037                                        engine->newFunction(Private::scriptNewInt16));
0038     engine->globalObject().setProperty(QStringLiteral("int32"),
0039                                        engine->newFunction(Private::scriptNewInt32));
0040     engine->globalObject().setProperty(QStringLiteral("int64"),
0041                                        engine->newFunction(Private::scriptNewInt64));
0042 
0043     engine->globalObject().setProperty(QStringLiteral("bool8"),
0044                                        engine->newFunction(Private::scriptNewBool8));
0045     engine->globalObject().setProperty(QStringLiteral("bool16"),
0046                                        engine->newFunction(Private::scriptNewBool16));
0047     engine->globalObject().setProperty(QStringLiteral("bool32"),
0048                                        engine->newFunction(Private::scriptNewBool32));
0049     engine->globalObject().setProperty(QStringLiteral("bool64"),
0050                                        engine->newFunction(Private::scriptNewBool64));
0051 
0052     engine->globalObject().setProperty(QStringLiteral("float"),
0053                                        engine->newFunction(Private::scriptNewFloat));
0054     engine->globalObject().setProperty(QStringLiteral("double"),
0055                                        engine->newFunction(Private::scriptNewDouble));
0056 
0057     engine->globalObject().setProperty(QStringLiteral("char"),
0058                                        engine->newFunction(Private::scriptNewChar));
0059 
0060     engine->globalObject().setProperty(QStringLiteral("bitfield"),
0061                                        engine->newFunction(Private::scriptNewBitfield));
0062 
0063     engine->globalObject().setProperty(QStringLiteral("array"),
0064                                        engine->newFunction(Private::scriptNewArray));
0065     engine->globalObject().setProperty(QStringLiteral("struct"),
0066                                        engine->newFunction(Private::scriptNewStruct));
0067     engine->globalObject().setProperty(QStringLiteral("union"),
0068                                        engine->newFunction(Private::scriptNewUnion));
0069 
0070     // enum is a reserved keyword in JavaScript, cannot use it
0071     engine->globalObject().setProperty(QStringLiteral("enumeration"),
0072                                        engine->newFunction(Private::scriptNewEnum));
0073     engine->globalObject().setProperty(QStringLiteral("flags"),
0074                                        engine->newFunction(Private::scriptNewFlags));
0075     engine->globalObject().setProperty(QStringLiteral("string"),
0076                                        engine->newFunction(Private::scriptNewString));
0077     engine->globalObject().setProperty(QStringLiteral("pointer"),
0078                                        engine->newFunction(Private::scriptNewPointer));
0079     engine->globalObject().setProperty(QStringLiteral("taggedUnion"),
0080                                        engine->newFunction(Private::scriptNewTaggedUnion));
0081 
0082     engine->globalObject().setProperty(QStringLiteral("alternative"),
0083                                        engine->newFunction(Private::alternativeFunc));
0084 
0085     engine->globalObject().setProperty(QStringLiteral("importScript"),
0086                                        engine->newFunction(Private::importScriptFunc));
0087 }
0088 
0089 QScriptEngine* newEngine()
0090 {
0091     auto* ret = new QScriptEngine();
0092     addFuctionsToScriptEngine(ret);
0093     return ret;
0094 }
0095 
0096 namespace Private {
0097 
0098 QString setUpdatePropertyName() { return QStringLiteral("setUpdate"); }
0099 QString setValidationPropertyName() { return QStringLiteral("setValidation"); }
0100 QString setPropertyName() { return QStringLiteral("set"); }
0101 
0102 namespace {
0103 
0104 QScriptValue scriptNewCommon(QScriptContext* ctx, QScriptEngine* eng, const QString& typeName)
0105 {
0106     QScriptValue object = ctx->isCalledAsConstructor() ? ctx->thisObject() : eng->newObject();
0107     object.setProperty(ParserStrings::PROPERTY_INTERNAL_TYPE(), typeName);
0108     // add the setUpdate() and setValidation() functions
0109     object.setProperty(setUpdatePropertyName(), eng->newFunction(addUpdateFunc, 1));
0110     object.setProperty(setValidationPropertyName(), eng->newFunction(addValidationFunc, 1));
0111     object.setProperty(setPropertyName(), eng->newFunction(addCustomPropertiesFunc, 1));
0112     return object;
0113 }
0114 
0115 /** create a new primitive of type @p type */
0116 QScriptValue primitiveConstructor(QScriptContext* ctx, QScriptEngine* eng, const QString& type)
0117 {
0118     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_PRIMITIVE());
0119     object.setProperty(ParserStrings::PROPERTY_TYPE(), type);
0120     return object;
0121 }
0122 
0123 }
0124 #define PRIMITIVE_CONSTRUCTOR(type) QScriptValue scriptNew##type(QScriptContext* ctx, QScriptEngine* eng) \
0125         { return primitiveConstructor(ctx, eng, QStringLiteral(#type)); }
0126 
0127 PRIMITIVE_CONSTRUCTOR(UInt8)
0128 PRIMITIVE_CONSTRUCTOR(UInt16)
0129 PRIMITIVE_CONSTRUCTOR(UInt32)
0130 PRIMITIVE_CONSTRUCTOR(UInt64)
0131 PRIMITIVE_CONSTRUCTOR(Int8)
0132 PRIMITIVE_CONSTRUCTOR(Int16)
0133 PRIMITIVE_CONSTRUCTOR(Int32)
0134 PRIMITIVE_CONSTRUCTOR(Int64)
0135 PRIMITIVE_CONSTRUCTOR(Bool8)
0136 PRIMITIVE_CONSTRUCTOR(Bool16)
0137 PRIMITIVE_CONSTRUCTOR(Bool32)
0138 PRIMITIVE_CONSTRUCTOR(Bool64)
0139 PRIMITIVE_CONSTRUCTOR(Float)
0140 PRIMITIVE_CONSTRUCTOR(Double)
0141 PRIMITIVE_CONSTRUCTOR(Char)
0142 
0143 #undef PRIMITIVE_CONSTRUCTOR
0144 
0145 QScriptValue scriptNewBitfield(QScriptContext* ctx, QScriptEngine* eng)
0146 {
0147     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_BITFIELD());
0148 
0149     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0)); // first argument is type
0150     object.setProperty(ParserStrings::PROPERTY_WIDTH(), ctx->argument(1)); // second argument is width
0151     return object;
0152 }
0153 
0154 // with children:
0155 QScriptValue scriptNewStruct(QScriptContext* ctx, QScriptEngine* eng)
0156 {
0157     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_STRUCT());
0158     object.setProperty(ParserStrings::PROPERTY_CHILD(), eng->newFunction(getChild));
0159 
0160     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0)); // first argument is children
0161     return object;
0162 }
0163 
0164 QScriptValue scriptNewUnion(QScriptContext* ctx, QScriptEngine* eng)
0165 {
0166     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_UNION());
0167     object.setProperty(ParserStrings::PROPERTY_TYPE(), eng->newFunction(getChild));
0168 
0169     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0)); // first argument is children
0170     return object;
0171 }
0172 
0173 QScriptValue scriptNewArray(QScriptContext* ctx, QScriptEngine* eng)
0174 {
0175     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_ARRAY());
0176 
0177     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0)); // first argument is child type
0178     object.setProperty(ParserStrings::PROPERTY_LENGTH(), ctx->argument(1)); // second argument is length
0179     return object;
0180 }
0181 
0182 QScriptValue createEnumObject(QScriptContext* ctx, QScriptEngine* eng, const QString& typeName)
0183 {
0184     QScriptValue object = scriptNewCommon(ctx, eng, typeName);
0185 
0186     object.setProperty(ParserStrings::PROPERTY_ENUM_NAME(), ctx->argument(0)); // first argument is the name of the underlying enum
0187     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(1)); // second argument is the type of the enum
0188     object.setProperty(ParserStrings::PROPERTY_ENUM_VALUES(), ctx->argument(2)); // third argument is the enum values
0189     return object;
0190 }
0191 
0192 QScriptValue scriptNewEnum(QScriptContext* ctx, QScriptEngine* eng)
0193 {
0194     return createEnumObject(ctx, eng, ParserStrings::TYPE_ENUM());
0195 }
0196 
0197 QScriptValue scriptNewFlags(QScriptContext* ctx, QScriptEngine* eng)
0198 {
0199     return createEnumObject(ctx, eng, ParserStrings::TYPE_FLAGS());
0200 }
0201 
0202 QScriptValue scriptNewString(QScriptContext* ctx, QScriptEngine* eng)
0203 {
0204     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_STRING());
0205 
0206     object.setProperty(ParserStrings::PROPERTY_ENCODING(), ctx->argument(0));
0207     return object;
0208 }
0209 
0210 QScriptValue scriptNewPointer(QScriptContext* ctx, QScriptEngine* eng)
0211 {
0212     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_POINTER());
0213 
0214     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0));
0215     object.setProperty(ParserStrings::PROPERTY_TARGET(), ctx->argument(1));
0216 
0217     if (ctx->argumentCount() >= 3 && ctx->argument(2).isValid()) {
0218         if (ctx->argument(2).isNumber()) {
0219             object.setProperty(ParserStrings::PROPERTY_SCALE(), ctx->argument(2));
0220         } else {
0221             return ctx->throwError(QStringLiteral("pointer(): if provided, scale must be integer!"));
0222         }
0223     } else {
0224         object.setProperty(ParserStrings::PROPERTY_SCALE(), eng->toScriptValue(1));
0225     }
0226 
0227     if (ctx->argumentCount() >= 4 && ctx->argument(3).isValid()) {
0228         if (ctx->argument(3).isFunction()) {
0229             object.setProperty(ParserStrings::PROPERTY_INTERPRET_FUNC(), ctx->argument(3));
0230         } else {
0231             return ctx->throwError(QStringLiteral("pointer(): if provided, interpreterFunc must be a function!"));
0232         }
0233     }
0234 
0235     return object;
0236 }
0237 
0238 QScriptValue scriptNewTaggedUnion(QScriptContext* ctx, QScriptEngine* eng)
0239 {
0240     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_TAGGED_UNION());
0241     object.setProperty(ParserStrings::PROPERTY_CHILD(), eng->newFunction(getChild));
0242 
0243     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0));
0244     object.setProperty(ParserStrings::PROPERTY_ALTERNATIVES(), ctx->argument(1));
0245     object.setProperty(ParserStrings::PROPERTY_DEFAULT_CHILDREN(), ctx->argument(2));
0246     return object;
0247 }
0248 
0249 QScriptValue getChild(QScriptContext* ctx, QScriptEngine* eng)
0250 {
0251     Q_UNUSED(eng)
0252     if (ctx->argumentCount() < 1) {
0253         return ctx->throwError(QStringLiteral("child(): name of child must be passed as first parameter"));
0254     }
0255     QString nameString = ctx->argument(0).toString();
0256     QScriptValue ret = ctx->thisObject().property(ParserStrings::PROPERTY_CHILDREN()).property(nameString);
0257     if (ret.isValid()) {
0258         return ret;
0259     }
0260     return ctx->throwError(
0261         QString(QLatin1String("child(): could not find child with name=") + nameString));
0262 }
0263 
0264 QScriptValue addUpdateFunc(QScriptContext* ctx, QScriptEngine*)
0265 {
0266     if (ctx->argumentCount() != 1) {
0267         return ctx->throwError(QStringLiteral("setUpdate(): needs one argument!"));
0268     }
0269     QScriptValue thisObj = ctx->thisObject();
0270     Q_ASSERT(thisObj.isValid());
0271     QScriptValue func = ctx->argument(0);
0272     if (!func.isFunction()) {
0273         return ctx->throwError(QScriptContext::TypeError,
0274                                QStringLiteral("setUpdate(): argument must be a function!"));
0275     }
0276     thisObj.setProperty(ParserStrings::PROPERTY_UPDATE_FUNC(), func);
0277     return thisObj;
0278 }
0279 
0280 QScriptValue addValidationFunc(QScriptContext* ctx, QScriptEngine*)
0281 {
0282     if (ctx->argumentCount() != 1) {
0283         return ctx->throwError(QStringLiteral("setValidation(): needs one argument!"));
0284     }
0285     QScriptValue thisObj = ctx->thisObject();
0286     Q_ASSERT(thisObj.isValid());
0287     QScriptValue func = ctx->argument(0);
0288     if (!func.isFunction()) {
0289         return ctx->throwError(QScriptContext::TypeError,
0290                                QStringLiteral("setValidation(): argument must be a function!"));
0291     }
0292     thisObj.setProperty(ParserStrings::PROPERTY_VALIDATION_FUNC(), func);
0293     return thisObj;
0294 }
0295 
0296 QScriptValue addCustomPropertiesFunc(QScriptContext* ctx, QScriptEngine*)
0297 {
0298     if (ctx->argumentCount() != 1) {
0299         return ctx->throwError(QStringLiteral("set(): needs one argument!"));
0300     }
0301     QScriptValue thisObj = ctx->thisObject();
0302     Q_ASSERT(thisObj.isValid());
0303     QScriptValue arg = ctx->argument(0);
0304     if (!arg.isValid() || !arg.isObject()) {
0305         return ctx->throwError(QScriptContext::TypeError,
0306                                QStringLiteral("set(): argument must be an object!"));
0307     }
0308     int count = 0;
0309     QScriptValueIterator it(arg);
0310     while (it.hasNext()) {
0311         it.next();
0312         thisObj.setProperty(it.scriptName(), it.value());
0313         count++;
0314     }
0315     if (count == 0) {
0316         return ctx->throwError(QStringLiteral("set(): must set at least one property!"));
0317     }
0318     return thisObj;
0319 }
0320 
0321 QScriptValue alternativeFunc(QScriptContext* ctx, QScriptEngine* eng)
0322 {
0323     if (ctx->argumentCount() < 2) {
0324         return ctx->throwError(QStringLiteral("alternative(): needs at least 2 arguments!"));
0325     }
0326     QScriptValue object = ctx->isCalledAsConstructor() ? ctx->thisObject() : eng->newObject();
0327     object.setProperty(ParserStrings::PROPERTY_SELECT_IF(), ctx->argument(0));
0328     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(1));
0329     if (ctx->argumentCount() > 2) {
0330         object.setProperty(ParserStrings::PROPERTY_STRUCT_NAME(), ctx->argument(2));
0331     }
0332     return object;
0333 }
0334 
0335 QScriptValue importScriptFunc(QScriptContext* ctx, QScriptEngine* eng)
0336 {
0337     if (ctx->argumentCount() != 1) {
0338         return ctx->throwError(QStringLiteral("importScript(): expected one argument!"));
0339     }
0340     QString arg = ctx->argument(0).toString();
0341     if (arg.contains(QLatin1String(".."))) {
0342         return ctx->throwError(QStringLiteral("importScript(): You may only access installed structure files! Path traversal detected."));
0343     }
0344     const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("okteta/structures/") + arg);
0345     if (fileName.isEmpty()) {
0346         return ctx->throwError(QStringLiteral("importScript(): could not find file to import!"));
0347     }
0348     QFile file(fileName);
0349     if (!file.open(QFile::ReadOnly)) {
0350         return ctx->throwError(QStringLiteral("importScript(): failed to open file!"));
0351     }
0352     QTextStream s(&file);
0353     QString code = s.readAll();
0354     file.close();
0355     // now push context so that we don't conflict with the current execution
0356     QScriptContext* newCtx = eng->pushContext();
0357     QScriptValue result = eng->evaluate(code);
0358     if (result.isError()) {
0359         result = QScriptValue(QLatin1String("importScript(): failed due to exception: ") + result.toString());
0360     } else {
0361         result = newCtx->activationObject();
0362     }
0363     eng->popContext();
0364     return result;
0365 }
0366 
0367 } // namespace Private
0368 
0369 } // namespace ScriptEngine Initializer