1 /*
2     This file is part of the Okteta Kasten Framework, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2010, 2011, 2012, 2013 Alex Richardson <alex.richardson@gmx.de>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 
9 #include "scriptengineinitializer.hpp"
10 #include "scriptutils.hpp"
11 #include "../datatypes/primitivefactory.hpp"
12 #include "../parsers/parserutils.hpp"
13 #include <QStringList>
14 #include <QFile>
15 #include <QScriptValue>
16 #include <QScriptEngine>
17 #include <QScriptContext>
18 #include <QScriptValueIterator>
19 #include <QStandardPaths>
20 
21 namespace ScriptEngineInitializer {
22 
addFuctionsToScriptEngine(QScriptEngine * engine)23 void addFuctionsToScriptEngine(QScriptEngine* engine)
24 {
25     engine->globalObject().setProperty(QStringLiteral("uint8"),
26                                        engine->newFunction(Private::scriptNewUInt8));
27     engine->globalObject().setProperty(QStringLiteral("uint16"),
28                                        engine->newFunction(Private::scriptNewUInt16));
29     engine->globalObject().setProperty(QStringLiteral("uint32"),
30                                        engine->newFunction(Private::scriptNewUInt32));
31     engine->globalObject().setProperty(QStringLiteral("uint64"),
32                                        engine->newFunction(Private::scriptNewUInt64));
33 
34     engine->globalObject().setProperty(QStringLiteral("int8"),
35                                        engine->newFunction(Private::scriptNewInt8));
36     engine->globalObject().setProperty(QStringLiteral("int16"),
37                                        engine->newFunction(Private::scriptNewInt16));
38     engine->globalObject().setProperty(QStringLiteral("int32"),
39                                        engine->newFunction(Private::scriptNewInt32));
40     engine->globalObject().setProperty(QStringLiteral("int64"),
41                                        engine->newFunction(Private::scriptNewInt64));
42 
43     engine->globalObject().setProperty(QStringLiteral("bool8"),
44                                        engine->newFunction(Private::scriptNewBool8));
45     engine->globalObject().setProperty(QStringLiteral("bool16"),
46                                        engine->newFunction(Private::scriptNewBool16));
47     engine->globalObject().setProperty(QStringLiteral("bool32"),
48                                        engine->newFunction(Private::scriptNewBool32));
49     engine->globalObject().setProperty(QStringLiteral("bool64"),
50                                        engine->newFunction(Private::scriptNewBool64));
51 
52     engine->globalObject().setProperty(QStringLiteral("float"),
53                                        engine->newFunction(Private::scriptNewFloat));
54     engine->globalObject().setProperty(QStringLiteral("double"),
55                                        engine->newFunction(Private::scriptNewDouble));
56 
57     engine->globalObject().setProperty(QStringLiteral("char"),
58                                        engine->newFunction(Private::scriptNewChar));
59 
60     engine->globalObject().setProperty(QStringLiteral("bitfield"),
61                                        engine->newFunction(Private::scriptNewBitfield));
62 
63     engine->globalObject().setProperty(QStringLiteral("array"),
64                                        engine->newFunction(Private::scriptNewArray));
65     engine->globalObject().setProperty(QStringLiteral("struct"),
66                                        engine->newFunction(Private::scriptNewStruct));
67     engine->globalObject().setProperty(QStringLiteral("union"),
68                                        engine->newFunction(Private::scriptNewUnion));
69 
70     // enum is a reserved keyword in JavaScript, cannot use it
71     engine->globalObject().setProperty(QStringLiteral("enumeration"),
72                                        engine->newFunction(Private::scriptNewEnum));
73     engine->globalObject().setProperty(QStringLiteral("flags"),
74                                        engine->newFunction(Private::scriptNewFlags));
75     engine->globalObject().setProperty(QStringLiteral("string"),
76                                        engine->newFunction(Private::scriptNewString));
77     engine->globalObject().setProperty(QStringLiteral("pointer"),
78                                        engine->newFunction(Private::scriptNewPointer));
79     engine->globalObject().setProperty(QStringLiteral("taggedUnion"),
80                                        engine->newFunction(Private::scriptNewTaggedUnion));
81 
82     engine->globalObject().setProperty(QStringLiteral("alternative"),
83                                        engine->newFunction(Private::alternativeFunc));
84 
85     engine->globalObject().setProperty(QStringLiteral("importScript"),
86                                        engine->newFunction(Private::importScriptFunc));
87 }
88 
newEngine()89 QScriptEngine* newEngine()
90 {
91     auto* ret = new QScriptEngine();
92     addFuctionsToScriptEngine(ret);
93     return ret;
94 }
95 
96 namespace Private {
97 
setUpdatePropertyName()98 QString setUpdatePropertyName() { return QStringLiteral("setUpdate"); }
setValidationPropertyName()99 QString setValidationPropertyName() { return QStringLiteral("setValidation"); }
setPropertyName()100 QString setPropertyName() { return QStringLiteral("set"); }
101 
102 namespace {
103 
scriptNewCommon(QScriptContext * ctx,QScriptEngine * eng,const QString & typeName)104 QScriptValue scriptNewCommon(QScriptContext* ctx, QScriptEngine* eng, const QString& typeName)
105 {
106     QScriptValue object = ctx->isCalledAsConstructor() ? ctx->thisObject() : eng->newObject();
107     object.setProperty(ParserStrings::PROPERTY_INTERNAL_TYPE(), typeName);
108     // add the setUpdate() and setValidation() functions
109     object.setProperty(setUpdatePropertyName(), eng->newFunction(addUpdateFunc, 1));
110     object.setProperty(setValidationPropertyName(), eng->newFunction(addValidationFunc, 1));
111     object.setProperty(setPropertyName(), eng->newFunction(addCustomPropertiesFunc, 1));
112     return object;
113 }
114 
115 /** create a new primitive of type @p type */
primitiveConstructor(QScriptContext * ctx,QScriptEngine * eng,const QString & type)116 QScriptValue primitiveConstructor(QScriptContext* ctx, QScriptEngine* eng, const QString& type)
117 {
118     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_PRIMITIVE());
119     object.setProperty(ParserStrings::PROPERTY_TYPE(), type);
120     return object;
121 }
122 
123 }
124 #define PRIMITIVE_CONSTRUCTOR(type) QScriptValue scriptNew##type(QScriptContext* ctx, QScriptEngine* eng) \
125         { return primitiveConstructor(ctx, eng, QStringLiteral(#type)); }
126 
127 PRIMITIVE_CONSTRUCTOR(UInt8)
PRIMITIVE_CONSTRUCTOR(UInt16)128 PRIMITIVE_CONSTRUCTOR(UInt16)
129 PRIMITIVE_CONSTRUCTOR(UInt32)
130 PRIMITIVE_CONSTRUCTOR(UInt64)
131 PRIMITIVE_CONSTRUCTOR(Int8)
132 PRIMITIVE_CONSTRUCTOR(Int16)
133 PRIMITIVE_CONSTRUCTOR(Int32)
134 PRIMITIVE_CONSTRUCTOR(Int64)
135 PRIMITIVE_CONSTRUCTOR(Bool8)
136 PRIMITIVE_CONSTRUCTOR(Bool16)
137 PRIMITIVE_CONSTRUCTOR(Bool32)
138 PRIMITIVE_CONSTRUCTOR(Bool64)
139 PRIMITIVE_CONSTRUCTOR(Float)
140 PRIMITIVE_CONSTRUCTOR(Double)
141 PRIMITIVE_CONSTRUCTOR(Char)
142 
143 #undef PRIMITIVE_CONSTRUCTOR
144 
145 QScriptValue scriptNewBitfield(QScriptContext* ctx, QScriptEngine* eng)
146 {
147     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_BITFIELD());
148 
149     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0)); // first argument is type
150     object.setProperty(ParserStrings::PROPERTY_WIDTH(), ctx->argument(1)); // second argument is width
151     return object;
152 }
153 
154 // with children:
scriptNewStruct(QScriptContext * ctx,QScriptEngine * eng)155 QScriptValue scriptNewStruct(QScriptContext* ctx, QScriptEngine* eng)
156 {
157     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_STRUCT());
158     object.setProperty(ParserStrings::PROPERTY_CHILD(), eng->newFunction(getChild));
159 
160     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0)); // first argument is children
161     return object;
162 }
163 
scriptNewUnion(QScriptContext * ctx,QScriptEngine * eng)164 QScriptValue scriptNewUnion(QScriptContext* ctx, QScriptEngine* eng)
165 {
166     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_UNION());
167     object.setProperty(ParserStrings::PROPERTY_TYPE(), eng->newFunction(getChild));
168 
169     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0)); // first argument is children
170     return object;
171 }
172 
scriptNewArray(QScriptContext * ctx,QScriptEngine * eng)173 QScriptValue scriptNewArray(QScriptContext* ctx, QScriptEngine* eng)
174 {
175     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_ARRAY());
176 
177     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0)); // first argument is child type
178     object.setProperty(ParserStrings::PROPERTY_LENGTH(), ctx->argument(1)); // second argument is length
179     return object;
180 }
181 
createEnumObject(QScriptContext * ctx,QScriptEngine * eng,const QString & typeName)182 QScriptValue createEnumObject(QScriptContext* ctx, QScriptEngine* eng, const QString& typeName)
183 {
184     QScriptValue object = scriptNewCommon(ctx, eng, typeName);
185 
186     object.setProperty(ParserStrings::PROPERTY_ENUM_NAME(), ctx->argument(0)); // first argument is the name of the underlying enum
187     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(1)); // second argument is the type of the enum
188     object.setProperty(ParserStrings::PROPERTY_ENUM_VALUES(), ctx->argument(2)); // third argument is the enum values
189     return object;
190 }
191 
scriptNewEnum(QScriptContext * ctx,QScriptEngine * eng)192 QScriptValue scriptNewEnum(QScriptContext* ctx, QScriptEngine* eng)
193 {
194     return createEnumObject(ctx, eng, ParserStrings::TYPE_ENUM());
195 }
196 
scriptNewFlags(QScriptContext * ctx,QScriptEngine * eng)197 QScriptValue scriptNewFlags(QScriptContext* ctx, QScriptEngine* eng)
198 {
199     return createEnumObject(ctx, eng, ParserStrings::TYPE_FLAGS());
200 }
201 
scriptNewString(QScriptContext * ctx,QScriptEngine * eng)202 QScriptValue scriptNewString(QScriptContext* ctx, QScriptEngine* eng)
203 {
204     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_STRING());
205 
206     object.setProperty(ParserStrings::PROPERTY_ENCODING(), ctx->argument(0));
207     return object;
208 }
209 
scriptNewPointer(QScriptContext * ctx,QScriptEngine * eng)210 QScriptValue scriptNewPointer(QScriptContext* ctx, QScriptEngine* eng)
211 {
212     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_POINTER());
213 
214     object.setProperty(ParserStrings::PROPERTY_TYPE(), ctx->argument(0));
215     object.setProperty(ParserStrings::PROPERTY_TARGET(), ctx->argument(1));
216 
217     if (ctx->argumentCount() >= 3 && ctx->argument(2).isValid()) {
218         if (ctx->argument(2).isNumber()) {
219             object.setProperty(ParserStrings::PROPERTY_SCALE(), ctx->argument(2));
220         } else {
221             return ctx->throwError(QStringLiteral("pointer(): if provided, scale must be integer!"));
222         }
223     } else {
224         object.setProperty(ParserStrings::PROPERTY_SCALE(), eng->toScriptValue(1));
225     }
226 
227     if (ctx->argumentCount() >= 4 && ctx->argument(3).isValid()) {
228         if (ctx->argument(3).isFunction()) {
229             object.setProperty(ParserStrings::PROPERTY_INTERPRET_FUNC(), ctx->argument(3));
230         } else {
231             return ctx->throwError(QStringLiteral("pointer(): if provided, interpreterFunc must be a function!"));
232         }
233     }
234 
235     return object;
236 }
237 
scriptNewTaggedUnion(QScriptContext * ctx,QScriptEngine * eng)238 QScriptValue scriptNewTaggedUnion(QScriptContext* ctx, QScriptEngine* eng)
239 {
240     QScriptValue object = scriptNewCommon(ctx, eng, ParserStrings::TYPE_TAGGED_UNION());
241     object.setProperty(ParserStrings::PROPERTY_CHILD(), eng->newFunction(getChild));
242 
243     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(0));
244     object.setProperty(ParserStrings::PROPERTY_ALTERNATIVES(), ctx->argument(1));
245     object.setProperty(ParserStrings::PROPERTY_DEFAULT_CHILDREN(), ctx->argument(2));
246     return object;
247 }
248 
getChild(QScriptContext * ctx,QScriptEngine * eng)249 QScriptValue getChild(QScriptContext* ctx, QScriptEngine* eng)
250 {
251     Q_UNUSED(eng)
252     if (ctx->argumentCount() < 1) {
253         return ctx->throwError(QStringLiteral("child(): name of child must be passed as first parameter"));
254     }
255     QString nameString = ctx->argument(0).toString();
256     QScriptValue ret = ctx->thisObject().property(ParserStrings::PROPERTY_CHILDREN()).property(nameString);
257     if (ret.isValid()) {
258         return ret;
259     }
260     return ctx->throwError(
261         QString(QLatin1String("child(): could not find child with name=") + nameString));
262 }
263 
addUpdateFunc(QScriptContext * ctx,QScriptEngine *)264 QScriptValue addUpdateFunc(QScriptContext* ctx, QScriptEngine*)
265 {
266     if (ctx->argumentCount() != 1) {
267         return ctx->throwError(QStringLiteral("setUpdate(): needs one argument!"));
268     }
269     QScriptValue thisObj = ctx->thisObject();
270     Q_ASSERT(thisObj.isValid());
271     QScriptValue func = ctx->argument(0);
272     if (!func.isFunction()) {
273         return ctx->throwError(QScriptContext::TypeError,
274                                QStringLiteral("setUpdate(): argument must be a function!"));
275     }
276     thisObj.setProperty(ParserStrings::PROPERTY_UPDATE_FUNC(), func);
277     return thisObj;
278 }
279 
addValidationFunc(QScriptContext * ctx,QScriptEngine *)280 QScriptValue addValidationFunc(QScriptContext* ctx, QScriptEngine*)
281 {
282     if (ctx->argumentCount() != 1) {
283         return ctx->throwError(QStringLiteral("setValidation(): needs one argument!"));
284     }
285     QScriptValue thisObj = ctx->thisObject();
286     Q_ASSERT(thisObj.isValid());
287     QScriptValue func = ctx->argument(0);
288     if (!func.isFunction()) {
289         return ctx->throwError(QScriptContext::TypeError,
290                                QStringLiteral("setValidation(): argument must be a function!"));
291     }
292     thisObj.setProperty(ParserStrings::PROPERTY_VALIDATION_FUNC(), func);
293     return thisObj;
294 }
295 
addCustomPropertiesFunc(QScriptContext * ctx,QScriptEngine *)296 QScriptValue addCustomPropertiesFunc(QScriptContext* ctx, QScriptEngine*)
297 {
298     if (ctx->argumentCount() != 1) {
299         return ctx->throwError(QStringLiteral("set(): needs one argument!"));
300     }
301     QScriptValue thisObj = ctx->thisObject();
302     Q_ASSERT(thisObj.isValid());
303     QScriptValue arg = ctx->argument(0);
304     if (!arg.isValid() || !arg.isObject()) {
305         return ctx->throwError(QScriptContext::TypeError,
306                                QStringLiteral("set(): argument must be an object!"));
307     }
308     int count = 0;
309     QScriptValueIterator it(arg);
310     while (it.hasNext()) {
311         it.next();
312         thisObj.setProperty(it.scriptName(), it.value());
313         count++;
314     }
315     if (count == 0) {
316         return ctx->throwError(QStringLiteral("set(): must set at least one property!"));
317     }
318     return thisObj;
319 }
320 
alternativeFunc(QScriptContext * ctx,QScriptEngine * eng)321 QScriptValue alternativeFunc(QScriptContext* ctx, QScriptEngine* eng)
322 {
323     if (ctx->argumentCount() < 2) {
324         return ctx->throwError(QStringLiteral("alternative(): needs at least 2 arguments!"));
325     }
326     QScriptValue object = ctx->isCalledAsConstructor() ? ctx->thisObject() : eng->newObject();
327     object.setProperty(ParserStrings::PROPERTY_SELECT_IF(), ctx->argument(0));
328     object.setProperty(ParserStrings::PROPERTY_CHILDREN(), ctx->argument(1));
329     if (ctx->argumentCount() > 2) {
330         object.setProperty(ParserStrings::PROPERTY_STRUCT_NAME(), ctx->argument(2));
331     }
332     return object;
333 }
334 
importScriptFunc(QScriptContext * ctx,QScriptEngine * eng)335 QScriptValue importScriptFunc(QScriptContext* ctx, QScriptEngine* eng)
336 {
337     if (ctx->argumentCount() != 1) {
338         return ctx->throwError(QStringLiteral("importScript(): expected one argument!"));
339     }
340     QString arg = ctx->argument(0).toString();
341     if (arg.contains(QLatin1String(".."))) {
342         return ctx->throwError(QStringLiteral("importScript(): You may only access installed structure files! Path traversal detected."));
343     }
344     const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("okteta/structures/") + arg);
345     if (fileName.isEmpty()) {
346         return ctx->throwError(QStringLiteral("importScript(): could not find file to import!"));
347     }
348     QFile file(fileName);
349     if (!file.open(QFile::ReadOnly)) {
350         return ctx->throwError(QStringLiteral("importScript(): failed to open file!"));
351     }
352     QTextStream s(&file);
353     QString code = s.readAll();
354     file.close();
355     // now push context so that we don't conflict with the current execution
356     QScriptContext* newCtx = eng->pushContext();
357     QScriptValue result = eng->evaluate(code);
358     if (result.isError()) {
359         result = QScriptValue(QLatin1String("importScript(): failed due to exception: ") + result.toString());
360     } else {
361         result = newCtx->activationObject();
362     }
363     eng->popContext();
364     return result;
365 }
366 
367 } // namespace Private
368 
369 } // namespace ScriptEngine Initializer
370