1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #ifndef QBS_SCRIPTENGINE_H
41 #define QBS_SCRIPTENGINE_H
42 
43 #include "forward_decls.h"
44 #include "property.h"
45 #include <buildgraph/requestedartifacts.h>
46 #include <buildgraph/requesteddependencies.h>
47 #include <logging/logger.h>
48 #include <tools/codelocation.h>
49 #include <tools/filetime.h>
50 #include <tools/porting.h>
51 #include <tools/set.h>
52 
53 #include <QtCore/qdir.h>
54 #include <QtCore/qhash.h>
55 #include <QtCore/qlist.h>
56 #include <QtCore/qprocess.h>
57 #include <QtCore/qstring.h>
58 
59 #include <QtScript/qscriptengine.h>
60 
61 #include <memory>
62 #include <mutex>
63 #include <stack>
64 #include <tuple>
65 #include <unordered_map>
66 #include <vector>
67 
68 namespace qbs {
69 namespace Internal {
70 class Artifact;
71 class JsImport;
72 class PrepareScriptObserver;
73 class ScriptImporter;
74 class ScriptPropertyObserver;
75 
76 enum class EvalContext {
77     PropertyEvaluation, ProbeExecution, ModuleProvider, RuleExecution, JsCommand
78 };
79 class DubiousContext
80 {
81 public:
82     enum Suggestion { NoSuggestion, SuggestMoving };
context(c)83     DubiousContext(EvalContext c, Suggestion s = NoSuggestion) : context(c), suggestion(s) { }
84     EvalContext context;
85     Suggestion suggestion;
86 };
87 using DubiousContextList = std::vector<DubiousContext>;
88 
89 
90 /*
91  * ScriptObject that acquires resources, for example a file handle.
92  * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources.
93  */
94 class ResourceAcquiringScriptObject
95 {
96 public:
97     virtual ~ResourceAcquiringScriptObject() = default;
98     virtual void releaseResources() = 0;
99 };
100 
101 enum class ObserveMode { Enabled, Disabled };
102 
103 class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine
104 {
105     Q_OBJECT
106     ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent = nullptr);
107 public:
108     static ScriptEngine *create(Logger &logger, EvalContext evalContext, QObject *parent = nullptr);
109     ~ScriptEngine() override;
110 
logger()111     Logger &logger() const { return m_logger; }
112     void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject,
113                 ObserveMode observeMode);
114     void clearImportsCache();
115 
setEvalContext(EvalContext c)116     void setEvalContext(EvalContext c) { m_evalContext = c; }
evalContext()117     EvalContext evalContext() const { return m_evalContext; }
118     void checkContext(const QString &operation, const DubiousContextList &dubiousContexts);
119 
addPropertyRequestedInScript(const Property & property)120     void addPropertyRequestedInScript(const Property &property) {
121         m_propertiesRequestedInScript += property;
122     }
addDependenciesArrayRequested(const ResolvedProduct * p)123     void addDependenciesArrayRequested(const ResolvedProduct *p)
124     {
125         m_productsWithRequestedDependencies.insert(p);
126     }
setArtifactsMapRequested(const ResolvedProduct * product,bool forceUpdate)127     void setArtifactsMapRequested(const ResolvedProduct *product, bool forceUpdate)
128     {
129         m_requestedArtifacts.setAllArtifactTags(product, forceUpdate);
130     }
setArtifactSetRequestedForTag(const ResolvedProduct * product,const FileTag & tag)131     void setArtifactSetRequestedForTag(const ResolvedProduct *product, const FileTag &tag)
132     {
133         m_requestedArtifacts.setArtifactsForTag(product, tag);
134     }
setNonExistingArtifactSetRequested(const ResolvedProduct * product,const QString & tag)135     void setNonExistingArtifactSetRequested(const ResolvedProduct *product, const QString &tag)
136     {
137         m_requestedArtifacts.setNonExistingTagRequested(product, tag);
138     }
setArtifactsEnumerated(const ResolvedProduct * product)139     void setArtifactsEnumerated(const ResolvedProduct *product)
140     {
141         m_requestedArtifacts.setArtifactsEnumerated(product);
142     }
143     void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property);
addRequestedExport(const ResolvedProduct * product)144     void addRequestedExport(const ResolvedProduct *product) { m_requestedExports.insert(product); }
clearRequestedProperties()145     void clearRequestedProperties() {
146         m_propertiesRequestedInScript.clear();
147         m_propertiesRequestedFromArtifact.clear();
148         m_importsRequestedInScript.clear();
149         m_productsWithRequestedDependencies.clear();
150         m_requestedArtifacts.clear();
151         m_requestedExports.clear();
152     }
propertiesRequestedInScript()153     PropertySet propertiesRequestedInScript() const { return m_propertiesRequestedInScript; }
propertiesRequestedFromArtifact()154     QHash<QString, PropertySet> propertiesRequestedFromArtifact() const {
155         return m_propertiesRequestedFromArtifact;
156     }
productsWithRequestedDependencies()157     Set<const ResolvedProduct *> productsWithRequestedDependencies() const
158     {
159         return m_productsWithRequestedDependencies;
160     }
requestedDependencies()161     RequestedDependencies requestedDependencies() const
162     {
163         return RequestedDependencies(m_productsWithRequestedDependencies);
164     }
requestedArtifacts()165     RequestedArtifacts requestedArtifacts() const { return m_requestedArtifacts; }
requestedExports()166     Set<const ResolvedProduct *> requestedExports() const { return m_requestedExports; }
167 
168     void addImportRequestedInScript(qint64 importValueId);
169     std::vector<QString> importedFilesUsedInScript() const;
170 
setUsesIo()171     void setUsesIo() { m_usesIo = true; }
clearUsesIo()172     void clearUsesIo() { m_usesIo = false; }
usesIo()173     bool usesIo() const { return m_usesIo; }
174 
175     void enableProfiling(bool enable);
176 
setPropertyCacheEnabled(bool enable)177     void setPropertyCacheEnabled(bool enable) { m_propertyCacheEnabled = enable; }
isPropertyCacheEnabled()178     bool isPropertyCacheEnabled() const { return m_propertyCacheEnabled; }
179     void addToPropertyCache(const QString &moduleName, const QString &propertyName,
180                             const PropertyMapConstPtr &propertyMap, const QVariant &value);
181     QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName,
182                                        const PropertyMapConstPtr &propertyMap);
183 
184     void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor);
185     void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value);
186     void unobserveProperties();
187     void setDeprecatedProperty(QScriptValue &object, const QString &name, const QString &newName,
188             const QScriptValue &value);
observer()189     PrepareScriptObserver *observer() const { return m_observer.get(); }
190 
191     QProcessEnvironment environment() const;
192     void setEnvironment(const QProcessEnvironment &env);
193     void addCanonicalFilePathResult(const QString &filePath, const QString &resultFilePath);
194     void addFileExistsResult(const QString &filePath, bool exists);
195     void addDirectoryEntriesResult(const QString &path, QDir::Filters filters,
196                                    const QStringList &entries);
197     void addFileLastModifiedResult(const QString &filePath, const FileTime &fileTime);
canonicalFilePathResults()198     QHash<QString, QString> canonicalFilePathResults() const { return m_canonicalFilePathResult; }
fileExistsResults()199     QHash<QString, bool> fileExistsResults() const { return m_fileExistsResult; }
directoryEntriesResults()200     QHash<std::pair<QString, quint32>, QStringList> directoryEntriesResults() const
201     {
202         return m_directoryEntriesResult;
203     }
204 
fileLastModifiedResults()205     QHash<QString, FileTime> fileLastModifiedResults() const { return m_fileLastModifiedResult; }
206     Set<QString> imports() const;
207     static QScriptValueList argumentList(const QStringList &argumentNames,
208             const QScriptValue &context);
209 
uncaughtExceptionBacktraceOrEmpty()210     QStringList uncaughtExceptionBacktraceOrEmpty() const {
211         return hasUncaughtException() ? uncaughtExceptionBacktrace() : QStringList();
212     }
hasErrorOrException(const QScriptValue & v)213     bool hasErrorOrException(const QScriptValue &v) const {
214         return v.isError() || hasUncaughtException();
215     }
lastErrorValue(const QScriptValue & v)216     QScriptValue lastErrorValue(const QScriptValue &v) const {
217         return v.isError() ? v : uncaughtException();
218     }
lastErrorString(const QScriptValue & v)219     QString lastErrorString(const QScriptValue &v) const { return lastErrorValue(v).toString(); }
220     CodeLocation lastErrorLocation(const QScriptValue &v,
221                                    const CodeLocation &fallbackLocation = CodeLocation()) const;
222     ErrorInfo lastError(const QScriptValue &v,
223                         const CodeLocation &fallbackLocation = CodeLocation()) const;
224 
225     void cancel();
226 
227     // The active flag is different from QScriptEngine::isEvaluating.
228     // It is set and cleared externally for example by the rule execution code.
isActive()229     bool isActive() const { return m_active; }
setActive(bool on)230     void setActive(bool on) { m_active = on; }
231 
232     using QScriptEngine::newFunction;
233 
234     template <typename T, typename E,
235               typename = std::enable_if_t<std::is_pointer_v<T>>,
236               typename = std::enable_if_t<std::is_pointer_v<E>>,
237               typename = std::enable_if_t<std::is_base_of_v<
238                 QScriptEngine, std::remove_pointer_t<E>>>
newFunction(QScriptValue (* signature)(QScriptContext *,E,T),T arg)239               > QScriptValue newFunction(QScriptValue (*signature)(QScriptContext *, E, T), T arg) {
240         return QScriptEngine::newFunction(
241                     reinterpret_cast<FunctionWithArgSignature>(signature),
242                     reinterpret_cast<void *>(const_cast<
243                                              std::add_pointer_t<
244                                              std::remove_const_t<
245                                              std::remove_pointer_t<T>>>>(arg)));
246     }
247 
248     QScriptClass *modulePropertyScriptClass() const;
249     void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass);
250 
productPropertyScriptClass()251     QScriptClass *productPropertyScriptClass() const { return m_productPropertyScriptClass; }
setProductPropertyScriptClass(QScriptClass * productPropertyScriptClass)252     void setProductPropertyScriptClass(QScriptClass *productPropertyScriptClass)
253     {
254         m_productPropertyScriptClass = productPropertyScriptClass;
255     }
256 
artifactsScriptClass()257     QScriptClass *artifactsScriptClass() const { return m_artifactsScriptClass; }
setArtifactsScriptClass(QScriptClass * artifactsScriptClass)258     void setArtifactsScriptClass(QScriptClass *artifactsScriptClass)
259     {
260         m_artifactsScriptClass = artifactsScriptClass;
261     }
262 
263     void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj);
264     void releaseResourcesOfScriptObjects();
265 
productScriptValuePrototype(const ResolvedProduct * product)266     QScriptValue &productScriptValuePrototype(const ResolvedProduct *product)
267     {
268         return m_productScriptValues[product];
269     }
270 
projectScriptValue(const ResolvedProject * project)271     QScriptValue &projectScriptValue(const ResolvedProject *project)
272     {
273         return m_projectScriptValues[project];
274     }
275 
moduleScriptValuePrototype(const ResolvedModule * module)276     QScriptValue &moduleScriptValuePrototype(const ResolvedModule *module)
277     {
278         return m_moduleScriptValues[module];
279     }
280 
281 private:
282     QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE;
283 
284     void abort();
285 
286     bool gatherFileResults() const;
287 
288     void installQbsBuiltins();
289     void extendJavaScriptBuiltins();
290     void installFunction(const QString &name, int length, QScriptValue *functionValue,
291                          FunctionSignature f, QScriptValue *targetObject);
292     void installQbsFunction(const QString &name, int length, FunctionSignature f);
293     void installConsoleFunction(const QString &name,
294                                 QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *));
295     void installImportFunctions();
296     void uninstallImportFunctions();
297     void import(const JsImport &jsImport, QScriptValue &targetObject);
298     void observeImport(QScriptValue &jsImport);
299     void importFile(const QString &filePath, QScriptValue &targetObject);
300     static QScriptValue js_loadExtension(QScriptContext *context, QScriptEngine *qtengine);
301     static QScriptValue js_loadFile(QScriptContext *context, QScriptEngine *qtengine);
302     static QScriptValue js_require(QScriptContext *context, QScriptEngine *qtengine);
303 
304     class PropertyCacheKey
305     {
306     public:
307         PropertyCacheKey(QString moduleName, QString propertyName,
308                          PropertyMapConstPtr propertyMap);
309     private:
310         const QString m_moduleName;
311         const QString m_propertyName;
312         const PropertyMapConstPtr m_propertyMap;
313 
314         friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs);
315         friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed);
316     };
317 
318     friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs);
319     friend QHashValueType qHash(const ScriptEngine::PropertyCacheKey &k, QHashValueType seed);
320 
321     static std::mutex m_creationDestructionMutex;
322     ScriptImporter *m_scriptImporter;
323     QScriptClass *m_modulePropertyScriptClass;
324     QScriptClass *m_productPropertyScriptClass = nullptr;
325     QScriptClass *m_artifactsScriptClass = nullptr;
326     QHash<JsImport, QScriptValue> m_jsImportCache;
327     std::unordered_map<QString, QScriptValue> m_jsFileCache;
328     bool m_propertyCacheEnabled;
329     bool m_active;
330     QHash<PropertyCacheKey, QVariant> m_propertyCache;
331     PropertySet m_propertiesRequestedInScript;
332     QHash<QString, PropertySet> m_propertiesRequestedFromArtifact;
333     Logger &m_logger;
334     QScriptValue m_definePropertyFunction;
335     QScriptValue m_emptyFunction;
336     QProcessEnvironment m_environment;
337     QHash<QString, QString> m_canonicalFilePathResult;
338     QHash<QString, bool> m_fileExistsResult;
339     QHash<std::pair<QString, quint32>, QStringList> m_directoryEntriesResult;
340     QHash<QString, FileTime> m_fileLastModifiedResult;
341     std::stack<QString> m_currentDirPathStack;
342     std::stack<QStringList> m_extensionSearchPathsStack;
343     QScriptValue m_loadFileFunction;
344     QScriptValue m_loadExtensionFunction;
345     QScriptValue m_requireFunction;
346     QScriptValue m_qbsObject;
347     QScriptValue m_consoleObject;
348     QScriptValue m_cancelationError;
349     qint64 m_elapsedTimeImporting = -1;
350     bool m_usesIo = false;
351     EvalContext m_evalContext;
352     std::vector<ResourceAcquiringScriptObject *> m_resourceAcquiringScriptObjects;
353     const std::unique_ptr<PrepareScriptObserver> m_observer;
354     std::vector<std::tuple<QScriptValue, QString, QScriptValue>> m_observedProperties;
355     std::vector<QScriptValue> m_requireResults;
356     std::unordered_map<qint64, std::vector<QString>> m_filePathsPerImport;
357     std::vector<qint64> m_importsRequestedInScript;
358     Set<const ResolvedProduct *> m_productsWithRequestedDependencies;
359     RequestedArtifacts m_requestedArtifacts;
360     Set<const ResolvedProduct *> m_requestedExports;
361     ObserveMode m_observeMode = ObserveMode::Disabled;
362     std::unordered_map<const ResolvedProduct *, QScriptValue> m_productScriptValues;
363     std::unordered_map<const ResolvedProject *, QScriptValue> m_projectScriptValues;
364     std::unordered_map<const ResolvedModule *, QScriptValue> m_moduleScriptValues;
365 };
366 
367 class EvalContextSwitcher
368 {
369 public:
EvalContextSwitcher(ScriptEngine * engine,EvalContext newContext)370     EvalContextSwitcher(ScriptEngine *engine, EvalContext newContext)
371         : m_engine(engine), m_oldContext(engine->evalContext())
372     {
373         engine->setEvalContext(newContext);
374     }
375 
~EvalContextSwitcher()376     ~EvalContextSwitcher() { m_engine->setEvalContext(m_oldContext); }
377 
378 private:
379     ScriptEngine * const m_engine;
380     const EvalContext m_oldContext;
381 };
382 
383 } // namespace Internal
384 } // namespace qbs
385 
386 #endif // QBS_SCRIPTENGINE_H
387