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 | |
24 | using namespace QV4; |
25 | using namespace QQmlJS; |
26 | |
27 | Script::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 | |
39 | Script::~Script() |
40 | { |
41 | } |
42 | |
43 | void 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 = ⅇ |
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 | |
108 | ReturnedValue 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 | |
129 | Function *Script::function() |
130 | { |
131 | if (!parsed) |
132 | parse(); |
133 | return vmFunction; |
134 | } |
135 | |
136 | QV4::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 | |
183 | Script *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 | |