1 | // Copyright (C) 2018 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 | |
5 | #include "qv4proxy_p.h" |
6 | #include "qv4symbol_p.h" |
7 | #include "qv4jscall_p.h" |
8 | #include "qv4objectproto_p.h" |
9 | #include "qv4persistent_p.h" |
10 | #include "qv4objectiterator_p.h" |
11 | |
12 | using namespace QV4; |
13 | |
14 | DEFINE_OBJECT_VTABLE(ProxyObject); |
15 | DEFINE_OBJECT_VTABLE(ProxyFunctionObject); |
16 | |
17 | void Heap::ProxyObject::init(const QV4::Object *target, const QV4::Object *handler) |
18 | { |
19 | Object::init(); |
20 | ExecutionEngine *e = internalClass->engine; |
21 | this->target.set(e, newVal: target->d()); |
22 | this->handler.set(e, newVal: handler->d()); |
23 | } |
24 | |
25 | void Heap::ProxyFunctionObject::init(const QV4::FunctionObject *target, const QV4::Object *handler) |
26 | { |
27 | ExecutionEngine *e = internalClass->engine; |
28 | FunctionObject::init(scope: e->rootContext()); |
29 | this->target.set(e, newVal: target->d()); |
30 | this->handler.set(e, newVal: handler->d()); |
31 | |
32 | if (!target->isConstructor()) |
33 | jsConstruct = nullptr; |
34 | } |
35 | |
36 | |
37 | ReturnedValue ProxyObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) |
38 | { |
39 | Scope scope(m); |
40 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
41 | if (!o->d()->handler) |
42 | return scope.engine->throwTypeError(); |
43 | |
44 | ScopedObject target(scope, o->d()->target); |
45 | Q_ASSERT(target); |
46 | ScopedObject handler(scope, o->d()->handler); |
47 | ScopedValue trap(scope, handler->get(name: scope.engine->id_get())); |
48 | if (scope.hasException()) |
49 | return Encode::undefined(); |
50 | if (trap->isNullOrUndefined()) |
51 | return target->get(id, receiver, hasProperty); |
52 | if (!trap->isFunctionObject()) |
53 | return scope.engine->throwTypeError(); |
54 | if (hasProperty) |
55 | *hasProperty = true; |
56 | |
57 | Value *args = scope.alloc(nValues: 3); |
58 | args[0] = target; |
59 | args[1] = id.toStringOrSymbol(e: scope.engine); |
60 | args[2] = *receiver; |
61 | JSCallData cdata(handler, args, 3); |
62 | |
63 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
64 | if (scope.hasException()) |
65 | return Encode::undefined(); |
66 | ScopedProperty targetDesc(scope); |
67 | PropertyAttributes attributes = target->getOwnProperty(id, p: targetDesc); |
68 | if (attributes != Attr_Invalid && !attributes.isConfigurable()) { |
69 | if (attributes.isData() && !attributes.isWritable()) { |
70 | if (!trapResult->sameValue(other: targetDesc->value)) |
71 | return scope.engine->throwTypeError(); |
72 | } |
73 | if (attributes.isAccessor() && targetDesc->value.isUndefined()) { |
74 | if (!trapResult->isUndefined()) |
75 | return scope.engine->throwTypeError(); |
76 | } |
77 | } |
78 | return trapResult->asReturnedValue(); |
79 | } |
80 | |
81 | bool ProxyObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) |
82 | { |
83 | Scope scope(m); |
84 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
85 | if (!o->d()->handler) |
86 | return scope.engine->throwTypeError(); |
87 | |
88 | ScopedObject target(scope, o->d()->target); |
89 | Q_ASSERT(target); |
90 | ScopedObject handler(scope, o->d()->handler); |
91 | ScopedValue trap(scope, handler->get(name: scope.engine->id_set())); |
92 | if (scope.hasException()) |
93 | return Encode::undefined(); |
94 | if (trap->isNullOrUndefined()) |
95 | return target->put(id, v: value, receiver); |
96 | if (!trap->isFunctionObject()) |
97 | return scope.engine->throwTypeError(); |
98 | |
99 | Value *args = scope.alloc(nValues: 4); |
100 | args[0] = target; |
101 | args[1] = id.toStringOrSymbol(e: scope.engine); |
102 | args[2] = value; |
103 | args[3] = *receiver; |
104 | JSCallData cdata(handler, args, 4); |
105 | |
106 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
107 | if (scope.hasException() || !trapResult->toBoolean()) |
108 | return false; |
109 | ScopedProperty targetDesc(scope); |
110 | PropertyAttributes attributes = target->getOwnProperty(id, p: targetDesc); |
111 | if (attributes != Attr_Invalid && !attributes.isConfigurable()) { |
112 | if (attributes.isData() && !attributes.isWritable()) { |
113 | if (!value.sameValue(other: targetDesc->value)) |
114 | return scope.engine->throwTypeError(); |
115 | } |
116 | if (attributes.isAccessor() && targetDesc->set.isUndefined()) |
117 | return scope.engine->throwTypeError(); |
118 | } |
119 | return true; |
120 | } |
121 | |
122 | bool ProxyObject::virtualDeleteProperty(Managed *m, PropertyKey id) |
123 | { |
124 | Scope scope(m); |
125 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
126 | if (!o->d()->handler) |
127 | return scope.engine->throwTypeError(); |
128 | |
129 | ScopedObject target(scope, o->d()->target); |
130 | Q_ASSERT(target); |
131 | ScopedObject handler(scope, o->d()->handler); |
132 | ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("deleteProperty" ))); |
133 | ScopedValue trap(scope, handler->get(name: deleteProp)); |
134 | if (scope.hasException()) |
135 | return Encode::undefined(); |
136 | if (trap->isNullOrUndefined()) |
137 | return target->deleteProperty(id); |
138 | if (!trap->isFunctionObject()) |
139 | return scope.engine->throwTypeError(); |
140 | |
141 | Value *args = scope.alloc(nValues: 3); |
142 | args[0] = target; |
143 | args[1] = id.toStringOrSymbol(e: scope.engine); |
144 | args[2] = o->d(); // ### fix receiver handling |
145 | JSCallData cdata(handler, args, 3); |
146 | |
147 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
148 | if (scope.hasException() || !trapResult->toBoolean()) |
149 | return false; |
150 | ScopedProperty targetDesc(scope); |
151 | PropertyAttributes attributes = target->getOwnProperty(id, p: targetDesc); |
152 | if (attributes == Attr_Invalid) |
153 | return true; |
154 | if (!attributes.isConfigurable()) |
155 | return scope.engine->throwTypeError(); |
156 | return true; |
157 | } |
158 | |
159 | bool ProxyObject::virtualHasProperty(const Managed *m, PropertyKey id) |
160 | { |
161 | Scope scope(m); |
162 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
163 | if (!o->d()->handler) |
164 | return scope.engine->throwTypeError(); |
165 | |
166 | ScopedObject target(scope, o->d()->target); |
167 | Q_ASSERT(target); |
168 | ScopedObject handler(scope, o->d()->handler); |
169 | ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("has" ))); |
170 | ScopedValue trap(scope, handler->get(name: hasProp)); |
171 | if (scope.hasException()) |
172 | return Encode::undefined(); |
173 | if (trap->isNullOrUndefined()) |
174 | return target->hasProperty(id); |
175 | if (!trap->isFunctionObject()) |
176 | return scope.engine->throwTypeError(); |
177 | |
178 | Value *args = scope.alloc(nValues: 2); |
179 | args[0] = target; |
180 | args[1] = id.isArrayIndex() ? Value::fromUInt32(i: id.asArrayIndex()).toString(e: scope.engine) : id.asStringOrSymbol(); |
181 | JSCallData cdata(handler, args, 2); |
182 | |
183 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
184 | if (scope.hasException()) |
185 | return false; |
186 | bool result = trapResult->toBoolean(); |
187 | if (!result) { |
188 | ScopedProperty targetDesc(scope); |
189 | PropertyAttributes attributes = target->getOwnProperty(id, p: targetDesc); |
190 | if (attributes != Attr_Invalid) { |
191 | if (!attributes.isConfigurable() || !target->isExtensible()) |
192 | return scope.engine->throwTypeError(); |
193 | } |
194 | } |
195 | return result; |
196 | } |
197 | |
198 | PropertyAttributes ProxyObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) |
199 | { |
200 | Scope scope(m); |
201 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
202 | if (!o->d()->handler) { |
203 | scope.engine->throwTypeError(); |
204 | return Attr_Invalid; |
205 | } |
206 | |
207 | ScopedObject target(scope, o->d()->target); |
208 | Q_ASSERT(target); |
209 | ScopedObject handler(scope, o->d()->handler); |
210 | ScopedString deleteProp(scope, scope.engine->newString(QStringLiteral("getOwnPropertyDescriptor" ))); |
211 | ScopedValue trap(scope, handler->get(name: deleteProp)); |
212 | if (scope.hasException()) |
213 | return Attr_Invalid; |
214 | if (trap->isNullOrUndefined()) |
215 | return target->getOwnProperty(id, p); |
216 | if (!trap->isFunctionObject()) { |
217 | scope.engine->throwTypeError(); |
218 | return Attr_Invalid; |
219 | } |
220 | |
221 | Value *args = scope.alloc(nValues: 2); |
222 | args[0] = target; |
223 | args[1] = id.isArrayIndex() ? Value::fromUInt32(i: id.asArrayIndex()).toString(e: scope.engine) : id.asStringOrSymbol(); |
224 | JSCallData cdata(handler, args, 2); |
225 | |
226 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
227 | if (scope.hasException()) |
228 | return Attr_Invalid; |
229 | if (!trapResult->isObject() && !trapResult->isUndefined()) { |
230 | scope.engine->throwTypeError(); |
231 | return Attr_Invalid; |
232 | } |
233 | |
234 | ScopedProperty targetDesc(scope); |
235 | PropertyAttributes targetAttributes = target->getOwnProperty(id, p: targetDesc); |
236 | if (trapResult->isUndefined()) { |
237 | if (p) |
238 | p->value = Encode::undefined(); |
239 | if (targetAttributes == Attr_Invalid) { |
240 | return Attr_Invalid; |
241 | } |
242 | if (!targetAttributes.isConfigurable() || !target->isExtensible()) { |
243 | scope.engine->throwTypeError(); |
244 | return Attr_Invalid; |
245 | } |
246 | return Attr_Invalid; |
247 | } |
248 | |
249 | //bool extensibleTarget = target->isExtensible(); |
250 | ScopedProperty resultDesc(scope); |
251 | PropertyAttributes resultAttributes; |
252 | ObjectPrototype::toPropertyDescriptor(engine: scope.engine, v: trapResult, desc: resultDesc, attrs: &resultAttributes); |
253 | resultDesc->completed(attrs: &resultAttributes); |
254 | |
255 | if (!targetDesc->isCompatible(attrs&: targetAttributes, other: resultDesc, otherAttrs: resultAttributes)) { |
256 | scope.engine->throwTypeError(); |
257 | return Attr_Invalid; |
258 | } |
259 | |
260 | if (!resultAttributes.isConfigurable()) { |
261 | if (targetAttributes == Attr_Invalid || targetAttributes.isConfigurable()) { |
262 | scope.engine->throwTypeError(); |
263 | return Attr_Invalid; |
264 | } |
265 | } |
266 | |
267 | if (p) { |
268 | p->value = resultDesc->value; |
269 | p->set = resultDesc->set; |
270 | } |
271 | return resultAttributes; |
272 | } |
273 | |
274 | bool ProxyObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) |
275 | { |
276 | Scope scope(m); |
277 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
278 | if (!o->d()->handler) { |
279 | scope.engine->throwTypeError(); |
280 | return false; |
281 | } |
282 | |
283 | ScopedObject target(scope, o->d()->target); |
284 | Q_ASSERT(target); |
285 | ScopedObject handler(scope, o->d()->handler); |
286 | ScopedString prop(scope, scope.engine->newString(QStringLiteral("defineProperty" ))); |
287 | ScopedValue trap(scope, handler->get(name: prop)); |
288 | if (scope.hasException()) |
289 | return false; |
290 | if (trap->isNullOrUndefined()) |
291 | return target->defineOwnProperty(id, p, attrs); |
292 | if (!trap->isFunctionObject()) { |
293 | scope.engine->throwTypeError(); |
294 | return false; |
295 | } |
296 | |
297 | Value *args = scope.alloc(nValues: 3); |
298 | args[0] = target; |
299 | args[1] = id.isArrayIndex() ? Value::fromUInt32(i: id.asArrayIndex()).toString(e: scope.engine) : id.asStringOrSymbol(); |
300 | args[2] = ObjectPrototype::fromPropertyDescriptor(engine: scope.engine, desc: p, attrs); |
301 | JSCallData cdata(handler, args, 3); |
302 | |
303 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
304 | bool result = !scope.hasException() && trapResult->toBoolean(); |
305 | if (!result) |
306 | return false; |
307 | |
308 | ScopedProperty targetDesc(scope); |
309 | PropertyAttributes targetAttributes = target->getOwnProperty(id, p: targetDesc); |
310 | bool extensibleTarget = target->isExtensible(); |
311 | bool settingConfigFalse = attrs.hasConfigurable() && !attrs.isConfigurable(); |
312 | if (targetAttributes == Attr_Invalid) { |
313 | if (!extensibleTarget || settingConfigFalse) { |
314 | scope.engine->throwTypeError(); |
315 | return false; |
316 | } |
317 | } else { |
318 | if (!targetDesc->isCompatible(attrs&: targetAttributes, other: p, otherAttrs: attrs)) { |
319 | scope.engine->throwTypeError(); |
320 | return false; |
321 | } |
322 | if (settingConfigFalse && targetAttributes.isConfigurable()) { |
323 | scope.engine->throwTypeError(); |
324 | return false; |
325 | } |
326 | } |
327 | |
328 | return true; |
329 | } |
330 | |
331 | bool ProxyObject::virtualIsExtensible(const Managed *m) |
332 | { |
333 | Scope scope(m); |
334 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
335 | if (!o->d()->handler) |
336 | return scope.engine->throwTypeError(); |
337 | |
338 | ScopedObject target(scope, o->d()->target); |
339 | Q_ASSERT(target); |
340 | ScopedObject handler(scope, o->d()->handler); |
341 | ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("isExtensible" ))); |
342 | ScopedValue trap(scope, handler->get(name: hasProp)); |
343 | if (scope.hasException()) |
344 | return Encode::undefined(); |
345 | if (trap->isNullOrUndefined()) |
346 | return target->isExtensible(); |
347 | if (!trap->isFunctionObject()) |
348 | return scope.engine->throwTypeError(); |
349 | |
350 | Value *args = scope.alloc(nValues: 1); |
351 | args[0] = target; |
352 | JSCallData cdata(handler, args, 1); |
353 | |
354 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
355 | if (scope.hasException()) |
356 | return false; |
357 | bool result = trapResult->toBoolean(); |
358 | if (result != target->isExtensible()) { |
359 | scope.engine->throwTypeError(); |
360 | return false; |
361 | } |
362 | return result; |
363 | } |
364 | |
365 | bool ProxyObject::virtualPreventExtensions(Managed *m) |
366 | { |
367 | Scope scope(m); |
368 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
369 | if (!o->d()->handler) |
370 | return scope.engine->throwTypeError(); |
371 | |
372 | ScopedObject target(scope, o->d()->target); |
373 | Q_ASSERT(target); |
374 | ScopedObject handler(scope, o->d()->handler); |
375 | ScopedString hasProp(scope, scope.engine->newString(QStringLiteral("preventExtensions" ))); |
376 | ScopedValue trap(scope, handler->get(name: hasProp)); |
377 | if (scope.hasException()) |
378 | return Encode::undefined(); |
379 | if (trap->isNullOrUndefined()) |
380 | return target->preventExtensions(); |
381 | if (!trap->isFunctionObject()) |
382 | return scope.engine->throwTypeError(); |
383 | |
384 | Value *args = scope.alloc(nValues: 1); |
385 | args[0] = target; |
386 | JSCallData cdata(handler, args, 1); |
387 | |
388 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
389 | if (scope.hasException()) |
390 | return false; |
391 | bool result = trapResult->toBoolean(); |
392 | if (result && target->isExtensible()) { |
393 | scope.engine->throwTypeError(); |
394 | return false; |
395 | } |
396 | return result; |
397 | } |
398 | |
399 | Heap::Object *ProxyObject::virtualGetPrototypeOf(const Managed *m) |
400 | { |
401 | Scope scope(m); |
402 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
403 | if (!o->d()->handler) { |
404 | scope.engine->throwTypeError(); |
405 | return nullptr; |
406 | } |
407 | |
408 | ScopedObject target(scope, o->d()->target); |
409 | Q_ASSERT(target); |
410 | ScopedObject handler(scope, o->d()->handler); |
411 | ScopedString name(scope, scope.engine->newString(QStringLiteral("getPrototypeOf" ))); |
412 | ScopedValue trap(scope, handler->get(name)); |
413 | if (scope.hasException()) |
414 | return nullptr; |
415 | if (trap->isNullOrUndefined()) |
416 | return target->getPrototypeOf(); |
417 | if (!trap->isFunctionObject()) { |
418 | scope.engine->throwTypeError(); |
419 | return nullptr; |
420 | } |
421 | |
422 | Value *args = scope.alloc(nValues: 1); |
423 | args[0] = target; |
424 | JSCallData cdata(handler, args, 1); |
425 | |
426 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
427 | if (scope.hasException()) |
428 | return nullptr; |
429 | if (!trapResult->isNull() && !trapResult->isObject()) { |
430 | scope.engine->throwTypeError(); |
431 | return nullptr; |
432 | } |
433 | Heap::Object *proto = trapResult->isNull() ? nullptr : static_cast<Heap::Object *>(trapResult->heapObject()); |
434 | if (!target->isExtensible()) { |
435 | Heap::Object *targetProto = target->getPrototypeOf(); |
436 | if (proto != targetProto) { |
437 | scope.engine->throwTypeError(); |
438 | return nullptr; |
439 | } |
440 | } |
441 | return proto; |
442 | } |
443 | |
444 | bool ProxyObject::virtualSetPrototypeOf(Managed *m, const Object *p) |
445 | { |
446 | Scope scope(m); |
447 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
448 | if (!o->d()->handler) { |
449 | scope.engine->throwTypeError(); |
450 | return false; |
451 | } |
452 | |
453 | ScopedObject target(scope, o->d()->target); |
454 | Q_ASSERT(target); |
455 | ScopedObject handler(scope, o->d()->handler); |
456 | ScopedString name(scope, scope.engine->newString(QStringLiteral("setPrototypeOf" ))); |
457 | ScopedValue trap(scope, handler->get(name)); |
458 | if (scope.hasException()) |
459 | return false; |
460 | if (trap->isNullOrUndefined()) |
461 | return target->setPrototypeOf(p); |
462 | if (!trap->isFunctionObject()) { |
463 | scope.engine->throwTypeError(); |
464 | return false; |
465 | } |
466 | |
467 | Value *args = scope.alloc(nValues: 2); |
468 | args[0] = target; |
469 | args[1] = p ? p->asReturnedValue() : Encode::null(); |
470 | JSCallData cdata(handler, args, 2); |
471 | |
472 | ScopedValue trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
473 | bool result = !scope.hasException() && trapResult->toBoolean(); |
474 | if (!result) |
475 | return false; |
476 | if (!target->isExtensible()) { |
477 | Heap::Object *targetProto = target->getPrototypeOf(); |
478 | if (p->d() != targetProto) { |
479 | scope.engine->throwTypeError(); |
480 | return false; |
481 | } |
482 | } |
483 | return true; |
484 | } |
485 | |
486 | struct ProxyObjectOwnPropertyKeyIterator : OwnPropertyKeyIterator |
487 | { |
488 | PersistentValue ownKeys; |
489 | uint index = 0; |
490 | uint len = 0; |
491 | |
492 | ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys); |
493 | ~ProxyObjectOwnPropertyKeyIterator() override = default; |
494 | PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; |
495 | |
496 | }; |
497 | |
498 | ProxyObjectOwnPropertyKeyIterator::ProxyObjectOwnPropertyKeyIterator(ArrayObject *keys) |
499 | { |
500 | ownKeys = keys; |
501 | len = keys->getLength(); |
502 | } |
503 | |
504 | PropertyKey ProxyObjectOwnPropertyKeyIterator::next(const Object *m, Property *pd, PropertyAttributes *attrs) |
505 | { |
506 | if (index >= len || m == nullptr) |
507 | return PropertyKey::invalid(); |
508 | |
509 | Scope scope(m); |
510 | ScopedObject keys(scope, ownKeys.asManaged()); |
511 | PropertyKey key = PropertyKey::fromId(id: keys->get(id: PropertyKey::fromArrayIndex(idx: index))); |
512 | ++index; |
513 | |
514 | if (pd || attrs) { |
515 | ScopedProperty p(scope); |
516 | PropertyAttributes a = const_cast<Object *>(m)->getOwnProperty(id: key, p: pd ? pd : p); |
517 | if (attrs) |
518 | *attrs = a; |
519 | } |
520 | |
521 | return key; |
522 | } |
523 | |
524 | static bool removeAllOccurrences(ArrayObject *target, ReturnedValue val) { |
525 | uint len = target->getLength(); |
526 | bool found = false; |
527 | for (uint i = 0; i < len; ++i) { |
528 | ReturnedValue v = target->get(idx: i); |
529 | if (v == val) { |
530 | found = true; |
531 | target->put(idx: i, v: Value::undefinedValue()); |
532 | } |
533 | } |
534 | return found; |
535 | } |
536 | |
537 | OwnPropertyKeyIterator *ProxyObject::virtualOwnPropertyKeys(const Object *m, Value *iteratorTarget) |
538 | { |
539 | Scope scope(m); |
540 | const ProxyObject *o = static_cast<const ProxyObject *>(m); |
541 | if (!o->d()->handler) { |
542 | scope.engine->throwTypeError(); |
543 | return nullptr; |
544 | } |
545 | |
546 | ScopedObject target(scope, o->d()->target); |
547 | Q_ASSERT(target); |
548 | ScopedObject handler(scope, o->d()->handler); |
549 | ScopedString name(scope, scope.engine->newString(QStringLiteral("ownKeys" ))); |
550 | ScopedValue trap(scope, handler->get(name)); |
551 | |
552 | if (scope.hasException()) |
553 | return nullptr; |
554 | if (trap->isUndefined()) |
555 | return target->ownPropertyKeys(target: iteratorTarget); |
556 | if (!trap->isFunctionObject()) { |
557 | scope.engine->throwTypeError(); |
558 | return nullptr; |
559 | } |
560 | |
561 | Value *args = scope.alloc(nValues: 1); |
562 | args[0] = target; |
563 | JSCallData cdata(handler, args, 1); |
564 | ScopedObject trapResult(scope, static_cast<const FunctionObject *>(trap.ptr)->call(data: cdata)); |
565 | if (scope.hasException()) |
566 | return nullptr; |
567 | if (!trapResult) { |
568 | scope.engine->throwTypeError(); |
569 | return nullptr; |
570 | } |
571 | |
572 | uint len = trapResult->getLength(); |
573 | ScopedArrayObject trapKeys(scope, scope.engine->newArrayObject()); |
574 | ScopedStringOrSymbol key(scope); |
575 | for (uint i = 0; i < len; ++i) { |
576 | key = trapResult->get(idx: i); |
577 | if (scope.hasException()) |
578 | return nullptr; |
579 | if (!key) { |
580 | scope.engine->throwTypeError(); |
581 | return nullptr; |
582 | } |
583 | Value keyAsValue = Value::fromReturnedValue(val: key->toPropertyKey().id()); |
584 | trapKeys->push_back(v: keyAsValue); |
585 | } |
586 | |
587 | ScopedArrayObject targetConfigurableKeys(scope, scope.engine->newArrayObject()); |
588 | ScopedArrayObject targetNonConfigurableKeys(scope, scope.engine->newArrayObject()); |
589 | ObjectIterator it(scope, target, ObjectIterator::EnumerableOnly); |
590 | ScopedPropertyKey k(scope); |
591 | while (1) { |
592 | PropertyAttributes attrs; |
593 | k = it.next(pd: nullptr, attributes: &attrs); |
594 | if (!k->isValid()) |
595 | break; |
596 | Value keyAsValue = Value::fromReturnedValue(val: k->id()); |
597 | if (attrs.isConfigurable()) |
598 | targetConfigurableKeys->push_back(v: keyAsValue); |
599 | else |
600 | targetNonConfigurableKeys->push_back(v: keyAsValue); |
601 | } |
602 | if (target->isExtensible() && targetNonConfigurableKeys->getLength() == 0) { |
603 | *iteratorTarget = *m; |
604 | return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
605 | } |
606 | |
607 | ScopedArrayObject uncheckedResultKeys(scope, scope.engine->newArrayObject()); |
608 | uncheckedResultKeys->copyArrayData(other: trapKeys); |
609 | |
610 | len = targetNonConfigurableKeys->getLength(); |
611 | for (uint i = 0; i < len; ++i) { |
612 | k = PropertyKey::fromId(id: targetNonConfigurableKeys->get(idx: i)); |
613 | if (!removeAllOccurrences(target: uncheckedResultKeys, val: k->id())) { |
614 | scope.engine->throwTypeError(); |
615 | return nullptr; |
616 | } |
617 | } |
618 | |
619 | if (target->isExtensible()) { |
620 | *iteratorTarget = *m; |
621 | return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
622 | } |
623 | |
624 | len = targetConfigurableKeys->getLength(); |
625 | for (uint i = 0; i < len; ++i) { |
626 | k = PropertyKey::fromId(id: targetConfigurableKeys->get(idx: i)); |
627 | if (!removeAllOccurrences(target: uncheckedResultKeys, val: k->id())) { |
628 | scope.engine->throwTypeError(); |
629 | return nullptr; |
630 | } |
631 | } |
632 | |
633 | len = uncheckedResultKeys->getLength(); |
634 | for (uint i = 0; i < len; ++i) { |
635 | if (uncheckedResultKeys->get(idx: i) != Encode::undefined()) { |
636 | scope.engine->throwTypeError(); |
637 | return nullptr; |
638 | } |
639 | } |
640 | |
641 | *iteratorTarget = *m; |
642 | return new ProxyObjectOwnPropertyKeyIterator(trapKeys); |
643 | } |
644 | |
645 | |
646 | ReturnedValue ProxyFunctionObject::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
647 | { |
648 | Scope scope(f); |
649 | const ProxyObject *o = static_cast<const ProxyObject *>(f); |
650 | if (!o->d()->handler) |
651 | return scope.engine->throwTypeError(); |
652 | |
653 | ScopedFunctionObject target(scope, o->d()->target); |
654 | Q_ASSERT(target); |
655 | ScopedObject handler(scope, o->d()->handler); |
656 | ScopedString name(scope, scope.engine->newString(QStringLiteral("construct" ))); |
657 | ScopedValue trap(scope, handler->get(name)); |
658 | |
659 | if (scope.hasException()) |
660 | return Encode::undefined(); |
661 | if (trap->isNullOrUndefined()) { |
662 | Q_ASSERT(target->isConstructor()); |
663 | return target->callAsConstructor(argv, argc, newTarget); |
664 | } |
665 | if (!trap->isFunctionObject()) |
666 | return scope.engine->throwTypeError(); |
667 | |
668 | ScopedFunctionObject trapFunction(scope, trap); |
669 | Value *arguments = scope.alloc(nValues: 3); |
670 | arguments[0] = target; |
671 | arguments[1] = scope.engine->newArrayObject(values: argv, length: argc); |
672 | arguments[2] = newTarget ? *newTarget : Value::undefinedValue(); |
673 | ScopedObject result(scope, trapFunction->call(thisObject: handler, argv: arguments, argc: 3)); |
674 | |
675 | if (!result) |
676 | return scope.engine->throwTypeError(); |
677 | return result->asReturnedValue(); |
678 | } |
679 | |
680 | ReturnedValue ProxyFunctionObject::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
681 | { |
682 | Scope scope(f); |
683 | |
684 | const ProxyObject *o = static_cast<const ProxyObject *>(f); |
685 | if (!o->d()->handler) |
686 | return scope.engine->throwTypeError(); |
687 | |
688 | ScopedFunctionObject target(scope, o->d()->target); |
689 | Q_ASSERT(target); |
690 | ScopedObject handler(scope, o->d()->handler); |
691 | ScopedString name(scope, scope.engine->newString(QStringLiteral("apply" ))); |
692 | ScopedValue trap(scope, handler->get(name)); |
693 | |
694 | if (scope.hasException()) |
695 | return Encode::undefined(); |
696 | if (trap->isNullOrUndefined()) |
697 | return checkedResult(v4: scope.engine, result: target->call(thisObject, argv, argc)); |
698 | if (!trap->isFunctionObject()) |
699 | return scope.engine->throwTypeError(); |
700 | |
701 | ScopedFunctionObject trapFunction(scope, trap); |
702 | Value *arguments = scope.alloc(nValues: 3); |
703 | arguments[0] = target; |
704 | arguments[1] = thisObject ? *thisObject : Value::undefinedValue(); |
705 | arguments[2] = scope.engine->newArrayObject(values: argv, length: argc); |
706 | return trapFunction->call(thisObject: handler, argv: arguments, argc: 3); |
707 | } |
708 | |
709 | DEFINE_OBJECT_VTABLE(Proxy); |
710 | |
711 | void Heap::Proxy::init(QV4::ExecutionContext *ctx) |
712 | { |
713 | Heap::FunctionObject::init(scope: ctx, QStringLiteral("Proxy" )); |
714 | |
715 | Scope scope(ctx); |
716 | Scoped<QV4::Proxy> ctor(scope, this); |
717 | ctor->defineDefaultProperty(QStringLiteral("revocable" ), code: QV4::Proxy::method_revocable, argumentCount: 2); |
718 | ctor->defineReadonlyConfigurableProperty(name: scope.engine->id_length(), value: Value::fromInt32(i: 2)); |
719 | } |
720 | |
721 | ReturnedValue Proxy::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *) |
722 | { |
723 | Scope scope(f); |
724 | if (argc < 2 || !argv[0].isObject() || !argv[1].isObject()) |
725 | return scope.engine->throwTypeError(); |
726 | |
727 | const Object *target = static_cast<const Object *>(argv); |
728 | const Object *handler = static_cast<const Object *>(argv + 1); |
729 | if (const ProxyObject *ptarget = target->as<ProxyObject>()) |
730 | if (!ptarget->d()->handler) |
731 | return scope.engine->throwTypeError(); |
732 | if (const ProxyObject *phandler = handler->as<ProxyObject>()) |
733 | if (!phandler->d()->handler) |
734 | return scope.engine->throwTypeError(); |
735 | |
736 | const FunctionObject *targetFunction = target->as<FunctionObject>(); |
737 | if (targetFunction) |
738 | return scope.engine->memoryManager->allocate<ProxyFunctionObject>(args&: targetFunction, args&: handler)->asReturnedValue(); |
739 | return scope.engine->memoryManager->allocate<ProxyObject>(args&: target, args&: handler)->asReturnedValue(); |
740 | } |
741 | |
742 | ReturnedValue Proxy::virtualCall(const FunctionObject *f, const Value *, const Value *, int) |
743 | { |
744 | return f->engine()->throwTypeError(); |
745 | } |
746 | |
747 | ReturnedValue Proxy::method_revocable(const FunctionObject *f, const Value *, const Value *argv, int argc) |
748 | { |
749 | Scope scope(f); |
750 | ScopedObject proxy(scope, Proxy::virtualCallAsConstructor(f, argv, argc, f)); |
751 | if (scope.hasException()) |
752 | return Encode::undefined(); |
753 | Q_ASSERT(proxy); |
754 | |
755 | ScopedString revoke(scope, scope.engine->newString(QStringLiteral("revoke" ))); |
756 | ScopedFunctionObject revoker(scope, scope.engine->memoryManager->allocate<FunctionObject>(args: scope.engine->rootContext(), args: nullptr, args&: method_revoke)); |
757 | revoker->defineReadonlyConfigurableProperty(name: scope.engine->id_length(), value: Value::fromInt32(i: 0)); |
758 | revoker->defineDefaultProperty(name: scope.engine->symbol_revokableProxy(), value: proxy); |
759 | |
760 | ScopedObject o(scope, scope.engine->newObject()); |
761 | ScopedString p(scope, scope.engine->newString(QStringLiteral("proxy" ))); |
762 | o->defineDefaultProperty(name: p, value: proxy); |
763 | o->defineDefaultProperty(name: revoke, value: revoker); |
764 | return o->asReturnedValue(); |
765 | } |
766 | |
767 | ReturnedValue Proxy::method_revoke(const FunctionObject *f, const Value *, const Value *, int) |
768 | { |
769 | Scope scope(f); |
770 | ScopedObject o(scope, f->get(name: scope.engine->symbol_revokableProxy())); |
771 | Q_ASSERT(o); |
772 | ProxyObject *proxy = o->cast<ProxyObject>(); |
773 | |
774 | proxy->d()->target.set(e: scope.engine, newVal: nullptr); |
775 | proxy->d()->handler.set(e: scope.engine, newVal: nullptr); |
776 | return Encode::undefined(); |
777 | } |
778 | |