1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qv4script_p.h"
5#include <private/qv4mm_p.h>
6#include "qv4function_p.h"
7#include "qv4context_p.h"
8#include "qv4debugging_p.h"
9#include "qv4scopedvalue_p.h"
10
11#include <private/qqmljsengine_p.h>
12#include <private/qqmljslexer_p.h>
13#include <private/qqmljsparser_p.h>
14#include <private/qqmljsast_p.h>
15#include <private/qqmlengine_p.h>
16#include <private/qqmlsourcecoordinate_p.h>
17#include <private/qv4profiling_p.h>
18#include <qv4runtimecodegen_p.h>
19
20#include <QtCore/QDebug>
21#include <QtCore/QString>
22#include <QScopedValueRollback>
23
24using namespace QV4;
25using namespace QQmlJS;
26
27Script::Script(ExecutionEngine *v4, QmlContext *qml, const QQmlRefPointer<ExecutableCompilationUnit> &compilationUnit)
28 : line(1), column(0), context(v4->rootContext()), strictMode(false), inheritContext(true), parsed(false)
29 , compilationUnit(compilationUnit), vmFunction(nullptr), parseAsBinding(true)
30{
31 if (qml)
32 qmlContext.set(engine: v4, value: *qml);
33
34 parsed = true;
35
36 vmFunction = compilationUnit ? compilationUnit->linkToEngine(engine: v4) : nullptr;
37}
38
39Script::~Script()
40{
41}
42
43void Script::parse()
44{
45 if (parsed)
46 return;
47
48 parsed = true;
49
50 ExecutionEngine *v4 = context->engine();
51 Scope valueScope(v4);
52
53 QV4::Compiler::Module module(v4->debugger() != nullptr);
54
55 if (sourceCode.startsWith(s: QLatin1String("function("))) {
56 static const int snippetLength = 70;
57 qWarning() << "Warning: Using function expressions as statements in scripts is not compliant with the ECMAScript specification:\n"
58 << (QStringView{sourceCode}.left(n: snippetLength) + QLatin1String("..."))
59 << "\nThis will throw a syntax error in Qt 5.12. If you want a function expression, surround it by parentheses.";
60 }
61
62 Engine ee, *engine = &ee;
63 Lexer lexer(engine);
64 lexer.setCode(code: sourceCode, lineno: line, qmlMode: parseAsBinding);
65 Parser parser(engine);
66
67 const bool parsed = parser.parseProgram();
68
69 const auto diagnosticMessages = parser.diagnosticMessages();
70 for (const DiagnosticMessage &m : diagnosticMessages) {
71 if (m.isError()) {
72 valueScope.engine->throwSyntaxError(m.message, sourceFile, m.loc.startLine, m.loc.startColumn);
73 return;
74 } else {
75 qWarning() << sourceFile << ':' << m.loc.startLine << ':' << m.loc.startColumn
76 << ": warning: " << m.message;
77 }
78 }
79
80 if (parsed) {
81 using namespace AST;
82 Program *program = AST::cast<Program *>(parser.rootNode());
83 if (!program) {
84 // if parsing was successful, and we have no program, then
85 // we're done...:
86 return;
87 }
88
89 QV4::Compiler::JSUnitGenerator jsGenerator(&module);
90 RuntimeCodegen cg(v4, &jsGenerator, strictMode);
91 if (inheritContext)
92 cg.setUseFastLookups(false);
93 cg.generateFromProgram(fileName: sourceFile, finalUrl: sourceFile, sourceCode, ast: program, module: &module, contextType);
94 if (v4->hasException)
95 return;
96
97 compilationUnit = QV4::ExecutableCompilationUnit::create(compilationUnit: cg.generateCompilationUnit());
98 vmFunction = compilationUnit->linkToEngine(engine: v4);
99 }
100
101 if (!vmFunction) {
102 // ### FIX file/line number
103 ScopedObject error(valueScope, v4->newSyntaxErrorObject(QStringLiteral("Syntax error")));
104 v4->throwError(value: error);
105 }
106}
107
108ReturnedValue Script::run(const QV4::Value *thisObject)
109{
110 if (!parsed)
111 parse();
112 if (!vmFunction)
113 return Encode::undefined();
114
115 QV4::ExecutionEngine *engine = context->engine();
116 QV4::Scope valueScope(engine);
117
118 if (qmlContext.isUndefined()) {
119 QScopedValueRollback<Function*> savedGlobalCode(engine->globalCode, vmFunction);
120
121 return vmFunction->call(thisObject: thisObject ? thisObject : engine->globalObject, argv: nullptr, argc: 0,
122 context);
123 } else {
124 Scoped<QmlContext> qml(valueScope, qmlContext.value());
125 return vmFunction->call(thisObject, argv: nullptr, argc: 0, context: qml);
126 }
127}
128
129Function *Script::function()
130{
131 if (!parsed)
132 parse();
133 return vmFunction;
134}
135
136QV4::CompiledData::CompilationUnit Script::precompile(
137 QV4::Compiler::Module *module, QQmlJS::Engine *jsEngine,
138 Compiler::JSUnitGenerator *unitGenerator, const QString &fileName, const QString &finalUrl,
139 const QString &source, QList<QQmlError> *reportedErrors,
140 QV4::Compiler::ContextType contextType)
141{
142 using namespace QV4::Compiler;
143 using namespace QQmlJS::AST;
144
145 Lexer lexer(jsEngine);
146 lexer.setCode(code: source, /*line*/lineno: 1, /*qml mode*/qmlMode: false);
147 Parser parser(jsEngine);
148
149 parser.parseProgram();
150
151 QList<QQmlError> errors = QQmlEnginePrivate::qmlErrorFromDiagnostics(fileName, diagnosticMessages: parser.diagnosticMessages());
152 if (!errors.isEmpty()) {
153 if (reportedErrors)
154 *reportedErrors << errors;
155 return nullptr;
156 }
157
158 Program *program = AST::cast<Program *>(parser.rootNode());
159 if (!program) {
160 // if parsing was successful, and we have no program, then
161 // we're done...:
162 return nullptr;
163 }
164
165 Codegen cg(unitGenerator, /*strict mode*/false);
166 cg.generateFromProgram(fileName, finalUrl, sourceCode: source, ast: program, module, contextType);
167 if (cg.hasError()) {
168 if (reportedErrors) {
169 const auto v4Error = cg.error();
170 QQmlError error;
171 error.setUrl(cg.url());
172 error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: v4Error.loc.startLine));
173 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: v4Error.loc.startColumn));
174 error.setDescription(v4Error.message);
175 reportedErrors->append(t: error);
176 }
177 return nullptr;
178 }
179
180 return cg.generateCompilationUnit(/*generate unit data*/generateUnitData: false);
181}
182
183Script *Script::createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error)
184{
185 if (error)
186 error->clear();
187
188 QQmlMetaType::CachedUnitLookupError cacheError = QQmlMetaType::CachedUnitLookupError::NoError;
189 const ExecutionEngine::DiskCacheOptions options = engine->diskCacheOptions();
190 if (const QQmlPrivate::CachedQmlUnit *cachedUnit
191 = (options & ExecutionEngine::DiskCache::Aot)
192 ? QQmlMetaType::findCachedCompilationUnit(
193 uri: originalUrl,
194 mode: (options & ExecutionEngine::DiskCache::AotByteCode)
195 ? QQmlMetaType::AcceptUntyped
196 : QQmlMetaType::RequireFullyTyped,
197 status: &cacheError)
198 : nullptr) {
199 QQmlRefPointer<QV4::ExecutableCompilationUnit> jsUnit
200 = QV4::ExecutableCompilationUnit::create(
201 compilationUnit: QV4::CompiledData::CompilationUnit(cachedUnit->qmlData, cachedUnit->aotCompiledFunctions));
202 return new QV4::Script(engine, qmlContext, jsUnit);
203 }
204
205 QFile f(fileName);
206 if (!f.open(flags: QIODevice::ReadOnly)) {
207 if (error) {
208 if (cacheError == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
209 *error = originalUrl.toString() + QString::fromUtf8(utf8: " was compiled ahead of time with an incompatible version of Qt and the original source code cannot be found. Please recompile");
210 else
211 *error = QString::fromUtf8(utf8: "Error opening source file %1: %2").arg(a: originalUrl.toString()).arg(a: f.errorString());
212 }
213 return nullptr;
214 }
215
216 QByteArray data = f.readAll();
217 QString sourceCode = QString::fromUtf8(ba: data);
218
219 auto result = new QV4::Script(engine, qmlContext, /*parseAsBinding*/false, sourceCode, originalUrl.toString());
220 result->contextType = QV4::Compiler::ContextType::ScriptImportedByQML;
221 result->parse();
222 return result;
223}
224

source code of qtdeclarative/src/qml/jsruntime/qv4script.cpp