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