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 <QString> |
5 | #include <qv4context_p.h> |
6 | #include <qv4object_p.h> |
7 | #include <qv4objectproto_p.h> |
8 | #include <private/qv4mm_p.h> |
9 | #include <qv4argumentsobject_p.h> |
10 | #include "qv4function_p.h" |
11 | #include "qv4stackframe_p.h" |
12 | #include "qv4symbol_p.h" |
13 | |
14 | using namespace QV4; |
15 | |
16 | DEFINE_MANAGED_VTABLE(ExecutionContext); |
17 | DEFINE_MANAGED_VTABLE(CallContext); |
18 | |
19 | Heap::CallContext *ExecutionContext::newBlockContext(CppStackFrame *frame, int blockIndex) |
20 | { |
21 | Function *function = frame->v4Function; |
22 | |
23 | Heap::InternalClass *ic = function->executableCompilationUnit()->runtimeBlocks.at(i: blockIndex); |
24 | uint nLocals = ic->size; |
25 | size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; |
26 | |
27 | ExecutionEngine *v4 = function->internalClass->engine; |
28 | Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(size: requiredMemory, ic); |
29 | c->init(); |
30 | c->type = Heap::ExecutionContext::Type_BlockContext; |
31 | |
32 | Heap::ExecutionContext *outer = static_cast<Heap::ExecutionContext *>(frame->context()->m()); |
33 | c->outer.set(e: v4, newVal: outer); |
34 | if (frame->isJSTypesFrame()) { |
35 | c->function.set(e: v4, newVal: static_cast<Heap::FunctionObject *>( |
36 | Value::fromStaticValue( |
37 | staticValue: static_cast<JSTypesStackFrame *>(frame)->jsFrame->function).m())); |
38 | } else { |
39 | c->function.set(e: v4, newVal: nullptr); |
40 | } |
41 | |
42 | c->locals.size = nLocals; |
43 | c->locals.alloc = nLocals; |
44 | |
45 | c->setupLocalTemporalDeadZone(function->executableCompilationUnit()->unitData()->blockAt(idx: blockIndex)); |
46 | |
47 | return c; |
48 | } |
49 | |
50 | Heap::CallContext *ExecutionContext::cloneBlockContext(ExecutionEngine *engine, |
51 | Heap::CallContext *callContext) |
52 | { |
53 | uint nLocals = callContext->locals.alloc; |
54 | size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; |
55 | |
56 | Heap::CallContext *c = engine->memoryManager->allocManaged<CallContext>( |
57 | size: requiredMemory, ic: callContext->internalClass); |
58 | memcpy(dest: c, src: callContext, n: requiredMemory); |
59 | |
60 | return c; |
61 | } |
62 | |
63 | Heap::CallContext *ExecutionContext::newCallContext(JSTypesStackFrame *frame) |
64 | { |
65 | Function *function = frame->v4Function; |
66 | Heap::ExecutionContext *outer = static_cast<Heap::ExecutionContext *>(frame->context()->m()); |
67 | |
68 | uint nFormals = qMax(a: static_cast<uint>(frame->argc()), b: function->nFormals); |
69 | uint localsAndFormals = function->compiledFunction->nLocals + nFormals; |
70 | size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * (localsAndFormals); |
71 | |
72 | ExecutionEngine *v4 = outer->internalClass->engine; |
73 | Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(size: requiredMemory, ic: function->internalClass); |
74 | c->init(); |
75 | |
76 | c->outer.set(e: v4, newVal: outer); |
77 | c->function.set(e: v4, newVal: static_cast<Heap::FunctionObject *>( |
78 | Value::fromStaticValue(staticValue: frame->jsFrame->function).m())); |
79 | |
80 | const CompiledData::Function *compiledFunction = function->compiledFunction; |
81 | uint nLocals = compiledFunction->nLocals; |
82 | c->locals.size = nLocals; |
83 | c->locals.alloc = localsAndFormals; |
84 | // memory allocated from the JS heap is 0 initialized, so check if empty is 0 |
85 | Q_ASSERT(Value::undefinedValue().asReturnedValue() == 0); |
86 | |
87 | c->setupLocalTemporalDeadZone(compiledFunction); |
88 | |
89 | Value *args = c->locals.values + nLocals; |
90 | ::memcpy(dest: args, src: frame->argv(), n: frame->argc() * sizeof(Value)); |
91 | c->nArgs = frame->argc(); |
92 | for (uint i = frame->argc(); i < function->nFormals; ++i) |
93 | args[i] = Encode::undefined(); |
94 | |
95 | return c; |
96 | } |
97 | |
98 | Heap::ExecutionContext *ExecutionContext::newWithContext(Heap::Object *with) const |
99 | { |
100 | Heap::ExecutionContext *c = engine()->memoryManager->alloc<ExecutionContext>(args: Heap::ExecutionContext::Type_WithContext); |
101 | c->outer.set(e: engine(), newVal: d()); |
102 | c->activation.set(e: engine(), newVal: with); |
103 | |
104 | return c; |
105 | } |
106 | |
107 | Heap::ExecutionContext *ExecutionContext::newCatchContext(CppStackFrame *frame, int blockIndex, Heap::String *exceptionVarName) |
108 | { |
109 | Scope scope(frame->context()); |
110 | ScopedString name(scope, exceptionVarName); |
111 | ScopedValue val(scope, scope.engine->catchException(trace: nullptr)); |
112 | ScopedContext ctx(scope, newBlockContext(frame, blockIndex)); |
113 | ctx->setProperty(name, value: val); |
114 | return ctx->d(); |
115 | } |
116 | |
117 | void ExecutionContext::createMutableBinding(String *name, bool deletable) |
118 | { |
119 | Scope scope(this); |
120 | |
121 | // find the right context to create the binding on |
122 | ScopedObject activation(scope); |
123 | ScopedContext ctx(scope, this); |
124 | while (ctx) { |
125 | switch (ctx->d()->type) { |
126 | case Heap::ExecutionContext::Type_CallContext: |
127 | if (!activation) { |
128 | Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx->d()); |
129 | if (!c->activation) |
130 | c->activation.set(e: scope.engine, newVal: scope.engine->newObject()); |
131 | activation = c->activation; |
132 | } |
133 | break; |
134 | case Heap::ExecutionContext::Type_QmlContext: { |
135 | // this is ugly, as it overrides the inner callcontext, but has to stay as long |
136 | // as bindings still get their own callcontext |
137 | activation = ctx->d()->activation; |
138 | break; |
139 | } |
140 | case Heap::ExecutionContext::Type_GlobalContext: { |
141 | Q_ASSERT(scope.engine->globalObject->d() == ctx->d()->activation); |
142 | if (!activation) |
143 | activation = ctx->d()->activation; |
144 | break; |
145 | } |
146 | case Heap::ExecutionContext::Type_BlockContext: |
147 | // never create activation records on block contexts |
148 | default: |
149 | break; |
150 | } |
151 | ctx = ctx->d()->outer; |
152 | } |
153 | |
154 | PropertyKey id = name->toPropertyKey(); |
155 | if (activation->getOwnProperty(id) != Attr_Invalid) |
156 | return; |
157 | ScopedProperty desc(scope); |
158 | PropertyAttributes attrs(Attr_Data); |
159 | attrs.setConfigurable(deletable); |
160 | if (!activation->defineOwnProperty(id, p: desc, attrs)) |
161 | scope.engine->throwTypeError(); |
162 | } |
163 | |
164 | static bool unscopable(ExecutionEngine *engine, Heap::Object *withObject, PropertyKey id) |
165 | { |
166 | if (!withObject) |
167 | return false; |
168 | Scope scope(engine); |
169 | ScopedObject w(scope, withObject); |
170 | ScopedObject o(scope, w->get(name: scope.engine->symbol_unscopables())); |
171 | if (o) { |
172 | ScopedValue blocked(scope, o->get(id)); |
173 | return blocked->toBoolean(); |
174 | } |
175 | return false; |
176 | } |
177 | |
178 | bool ExecutionContext::deleteProperty(String *name) |
179 | { |
180 | PropertyKey id = name->toPropertyKey(); |
181 | |
182 | Heap::ExecutionContext *ctx = d(); |
183 | ExecutionEngine *engine = ctx->internalClass->engine; |
184 | |
185 | for (; ctx; ctx = ctx->outer) { |
186 | switch (ctx->type) { |
187 | case Heap::ExecutionContext::Type_BlockContext: |
188 | case Heap::ExecutionContext::Type_CallContext: { |
189 | Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); |
190 | uint index = c->internalClass->indexOfValueOrGetter(id); |
191 | if (index < UINT_MAX) |
192 | // ### throw in strict mode? |
193 | return false; |
194 | Q_FALLTHROUGH(); |
195 | } |
196 | case Heap::ExecutionContext::Type_WithContext: { |
197 | if (ctx->activation) { |
198 | Scope scope(this); |
199 | ScopedObject object(scope, ctx->activation); |
200 | if (object && object->hasProperty(id)) { |
201 | bool u = ::unscopable(engine, withObject: ctx->activation, id); |
202 | if (engine->hasException) |
203 | return false; |
204 | if (u) |
205 | break; |
206 | return object->deleteProperty(id); |
207 | } |
208 | } |
209 | break; |
210 | } |
211 | case Heap::ExecutionContext::Type_GlobalContext: { |
212 | if (ctx->activation) { |
213 | Scope scope(this); |
214 | ScopedObject object(scope, ctx->activation); |
215 | if (object && object->hasProperty(id)) |
216 | return object->deleteProperty(id); |
217 | } |
218 | break; |
219 | } |
220 | case Heap::ExecutionContext::Type_QmlContext: |
221 | // can't delete properties on qml objects |
222 | break; |
223 | } |
224 | } |
225 | |
226 | return !engine->currentStackFrame->v4Function->isStrict(); |
227 | } |
228 | |
229 | ExecutionContext::Error ExecutionContext::setProperty(String *name, const Value &value) |
230 | { |
231 | PropertyKey id = name->toPropertyKey(); |
232 | |
233 | Heap::ExecutionContext *ctx = d(); |
234 | QV4::ExecutionEngine *engine = ctx->internalClass->engine; |
235 | |
236 | for (; ctx; ctx = ctx->outer) { |
237 | switch (ctx->type) { |
238 | case Heap::ExecutionContext::Type_WithContext: { |
239 | Scope scope(engine); |
240 | ScopedObject w(scope, ctx->activation); |
241 | if (w->hasProperty(id)) { |
242 | bool u = ::unscopable(engine, withObject: ctx->activation, id); |
243 | if (engine->hasException) |
244 | return TypeError; |
245 | if (u) |
246 | break; |
247 | if (!w->put(name, v: value)) |
248 | return TypeError; |
249 | return NoError; |
250 | } |
251 | break; |
252 | } |
253 | case Heap::ExecutionContext::Type_BlockContext: |
254 | case Heap::ExecutionContext::Type_CallContext: { |
255 | Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); |
256 | uint index = c->internalClass->indexOfValueOrGetter(id); |
257 | if (index < UINT_MAX) { |
258 | static_cast<Heap::CallContext *>(c)->locals.set(e: engine, index, v: value); |
259 | return NoError; |
260 | } |
261 | } |
262 | Q_FALLTHROUGH(); |
263 | case Heap::ExecutionContext::Type_GlobalContext: |
264 | if (ctx->activation) { |
265 | auto member = ctx->activation->internalClass->findValueOrSetter(id); |
266 | if (member.index < UINT_MAX) { |
267 | Scope scope(engine); |
268 | ScopedObject a(scope, ctx->activation); |
269 | if (!a->putValue(memberIndex: member.index, attrs: member.attrs, value)) |
270 | return TypeError; |
271 | return NoError; |
272 | } |
273 | } |
274 | break; |
275 | case Heap::ExecutionContext::Type_QmlContext: { |
276 | Scope scope(engine); |
277 | ScopedObject activation(scope, ctx->activation); |
278 | if (!activation->put(name, v: value)) |
279 | return TypeError; |
280 | return NoError; |
281 | } |
282 | } |
283 | |
284 | } |
285 | |
286 | return RangeError; |
287 | } |
288 | |
289 | ReturnedValue ExecutionContext::getProperty(String *name) |
290 | { |
291 | PropertyKey id = name->toPropertyKey(); |
292 | |
293 | Heap::ExecutionContext *ctx = d(); |
294 | QV4::ExecutionEngine *engine = ctx->internalClass->engine; |
295 | |
296 | for (; ctx; ctx = ctx->outer) { |
297 | switch (ctx->type) { |
298 | case Heap::ExecutionContext::Type_BlockContext: |
299 | case Heap::ExecutionContext::Type_CallContext: { |
300 | Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); |
301 | |
302 | uint index = c->internalClass->indexOfValueOrGetter(id); |
303 | if (index < UINT_MAX) |
304 | return c->locals[index].asReturnedValue(); |
305 | Q_FALLTHROUGH(); |
306 | } |
307 | case Heap::ExecutionContext::Type_WithContext: |
308 | if (ctx->activation) { |
309 | Scope scope(this); |
310 | ScopedObject activation(scope, ctx->activation); |
311 | if (activation->hasProperty(id)) { |
312 | bool u = ::unscopable(engine, withObject: ctx->activation, id); |
313 | if (engine->hasException) |
314 | return false; |
315 | if (u) |
316 | break; |
317 | return activation->get(id); |
318 | } |
319 | } |
320 | break; |
321 | case Heap::ExecutionContext::Type_GlobalContext: |
322 | case Heap::ExecutionContext::Type_QmlContext: { |
323 | if (ctx->activation) { |
324 | Scope scope(this); |
325 | ScopedObject activation(scope, ctx->activation); |
326 | bool hasProperty = false; |
327 | ReturnedValue v = activation->get(id, receiver: nullptr, hasProperty: &hasProperty); |
328 | if (hasProperty) |
329 | return v; |
330 | } |
331 | break; |
332 | } |
333 | } |
334 | } |
335 | return engine->throwReferenceError(value: *name); |
336 | } |
337 | |
338 | ReturnedValue ExecutionContext::getPropertyAndBase(String *name, Value *base) |
339 | { |
340 | base->setM(nullptr); |
341 | PropertyKey id = name->toPropertyKey(); |
342 | |
343 | Heap::ExecutionContext *ctx = d(); |
344 | QV4::ExecutionEngine *engine = ctx->internalClass->engine; |
345 | |
346 | for (; ctx; ctx = ctx->outer) { |
347 | switch (ctx->type) { |
348 | case Heap::ExecutionContext::Type_BlockContext: |
349 | case Heap::ExecutionContext::Type_CallContext: { |
350 | Heap::CallContext *c = static_cast<Heap::CallContext *>(ctx); |
351 | |
352 | uint index = c->internalClass->indexOfValueOrGetter(id); |
353 | if (index < UINT_MAX) |
354 | return c->locals[index].asReturnedValue(); |
355 | Q_FALLTHROUGH(); |
356 | } |
357 | case Heap::ExecutionContext::Type_GlobalContext: { |
358 | if (ctx->activation) { |
359 | Scope scope(this); |
360 | ScopedObject activation(scope, ctx->activation); |
361 | bool hasProperty = false; |
362 | ReturnedValue v = activation->get(name, hasProperty: &hasProperty); |
363 | if (hasProperty) |
364 | return v; |
365 | } |
366 | break; |
367 | } |
368 | case Heap::ExecutionContext::Type_WithContext: |
369 | if (ctx->activation) { |
370 | Scope scope(this); |
371 | ScopedObject activation(scope, ctx->activation); |
372 | if (activation->hasProperty(id)) { |
373 | bool u = ::unscopable(engine, withObject: ctx->activation, id); |
374 | if (engine->hasException) |
375 | return false; |
376 | if (u) |
377 | break; |
378 | base->setM(activation->d()); |
379 | return activation->get(id); |
380 | } |
381 | } |
382 | break; |
383 | case Heap::ExecutionContext::Type_QmlContext: { |
384 | Scope scope(this); |
385 | ScopedObject o(scope, ctx->activation); |
386 | bool hasProperty = false; |
387 | ReturnedValue v = o->get(id, receiver: nullptr, hasProperty: &hasProperty); |
388 | if (hasProperty) { |
389 | base->setM(o->d()); |
390 | return v; |
391 | } |
392 | break; |
393 | } |
394 | } |
395 | } |
396 | return engine->throwReferenceError(value: *name); |
397 | } |
398 | |
399 | void Heap::CallContext::setArg(uint index, Value v) |
400 | { |
401 | locals.set(e: internalClass->engine, index: locals.size + index, v); |
402 | } |
403 | |