1 | // Copyright (C) 2018 Crimson AS <info@crimson.no> |
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 "qv4mapobject_p.h" |
5 | #include "qv4mapiterator_p.h" |
6 | #include "qv4estable_p.h" |
7 | #include "qv4symbol_p.h" |
8 | |
9 | using namespace QV4; |
10 | |
11 | DEFINE_OBJECT_VTABLE(WeakMapCtor); |
12 | DEFINE_OBJECT_VTABLE(MapCtor); |
13 | DEFINE_OBJECT_VTABLE(MapObject); |
14 | |
15 | void Heap::WeakMapCtor::init(QV4::ExecutionContext *scope) |
16 | { |
17 | Heap::FunctionObject::init(scope, QStringLiteral("WeakMap" )); |
18 | } |
19 | |
20 | void Heap::MapCtor::init(QV4::ExecutionContext *scope) |
21 | { |
22 | Heap::FunctionObject::init(scope, QStringLiteral("Map" )); |
23 | } |
24 | |
25 | ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget, bool weakMap) |
26 | { |
27 | Scope scope(f); |
28 | Scoped<MapObject> a(scope, scope.engine->memoryManager->allocate<MapObject>()); |
29 | bool protoSet = false; |
30 | if (newTarget) |
31 | protoSet = a->setProtoFromNewTarget(newTarget); |
32 | if (!protoSet && weakMap) { |
33 | a->setPrototypeOf(scope.engine->weakMapPrototype()); |
34 | scope.engine->memoryManager->registerWeakMap(map: a->d()); |
35 | } |
36 | a->d()->isWeakMap = weakMap; |
37 | |
38 | if (argc > 0) { |
39 | ScopedValue iterable(scope, argv[0]); |
40 | |
41 | if (!iterable->isNullOrUndefined()) { |
42 | ScopedFunctionObject adder(scope, a->get(name: ScopedString(scope, scope.engine->newString(s: QString::fromLatin1(ba: "set" ))))); |
43 | if (!adder) |
44 | return scope.engine->throwTypeError(); |
45 | |
46 | ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); |
47 | if (scope.hasException()) |
48 | return Encode::undefined(); |
49 | Q_ASSERT(iter); |
50 | |
51 | ScopedValue obj(scope); |
52 | Value *arguments = scope.alloc(nValues: 2); |
53 | ScopedValue done(scope); |
54 | forever { |
55 | done = Runtime::IteratorNext::call(scope.engine, iter, obj); |
56 | if (scope.hasException()) |
57 | break; |
58 | if (done->toBoolean()) |
59 | return a->asReturnedValue(); |
60 | const Object *o = obj->objectValue(); |
61 | if (!o) { |
62 | scope.engine->throwTypeError(); |
63 | break; |
64 | } |
65 | |
66 | arguments[0] = o->get(id: PropertyKey::fromArrayIndex(idx: 0)); |
67 | if (scope.hasException()) |
68 | break; |
69 | arguments[1] = o->get(id: PropertyKey::fromArrayIndex(idx: 1)); |
70 | if (scope.hasException()) |
71 | break; |
72 | |
73 | adder->call(thisObject: a, argv: arguments, argc: 2); |
74 | if (scope.hasException()) |
75 | break; |
76 | } |
77 | ScopedValue falsey(scope, Encode(false)); |
78 | return Runtime::IteratorClose::call(scope.engine, iter, falsey); |
79 | } |
80 | } |
81 | return a->asReturnedValue(); |
82 | |
83 | } |
84 | |
85 | ReturnedValue WeakMapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
86 | { |
87 | return construct(f, argv, argc, newTarget, weakMap: true); |
88 | } |
89 | |
90 | ReturnedValue WeakMapCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) |
91 | { |
92 | Scope scope(f); |
93 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "(Weak)Map requires new" )); |
94 | } |
95 | |
96 | |
97 | ReturnedValue MapCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
98 | { |
99 | return construct(f, argv, argc, newTarget, weakMap: false); |
100 | } |
101 | |
102 | void WeakMapPrototype::init(ExecutionEngine *engine, Object *ctor) |
103 | { |
104 | Scope scope(engine); |
105 | ScopedObject o(scope); |
106 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 0)); |
107 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
108 | defineDefaultProperty(name: engine->id_constructor(), value: (o = ctor)); |
109 | |
110 | defineDefaultProperty(QStringLiteral("delete" ), code: method_delete, argumentCount: 1); |
111 | defineDefaultProperty(QStringLiteral("get" ), code: method_get, argumentCount: 1); |
112 | defineDefaultProperty(QStringLiteral("has" ), code: method_has, argumentCount: 1); |
113 | defineDefaultProperty(QStringLiteral("set" ), code: method_set, argumentCount: 2); |
114 | |
115 | ScopedString val(scope, engine->newString(s: QLatin1String("WeakMap" ))); |
116 | defineReadonlyConfigurableProperty(name: engine->symbol_toStringTag(), value: val); |
117 | } |
118 | |
119 | |
120 | void MapPrototype::init(ExecutionEngine *engine, Object *ctor) |
121 | { |
122 | Scope scope(engine); |
123 | ScopedObject o(scope); |
124 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 0)); |
125 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
126 | ctor->addSymbolSpecies(); |
127 | defineDefaultProperty(name: engine->id_constructor(), value: (o = ctor)); |
128 | |
129 | defineDefaultProperty(QStringLiteral("clear" ), code: method_clear, argumentCount: 0); |
130 | defineDefaultProperty(QStringLiteral("delete" ), code: method_delete, argumentCount: 1); |
131 | defineDefaultProperty(QStringLiteral("forEach" ), code: method_forEach, argumentCount: 1); |
132 | defineDefaultProperty(QStringLiteral("get" ), code: method_get, argumentCount: 1); |
133 | defineDefaultProperty(QStringLiteral("has" ), code: method_has, argumentCount: 1); |
134 | defineDefaultProperty(QStringLiteral("keys" ), code: method_keys, argumentCount: 0); |
135 | defineDefaultProperty(QStringLiteral("set" ), code: method_set, argumentCount: 2); |
136 | defineAccessorProperty(QStringLiteral("size" ), getter: method_get_size, setter: nullptr); |
137 | defineDefaultProperty(QStringLiteral("values" ), code: method_values, argumentCount: 0); |
138 | |
139 | // Per the spec, the value for entries/@@iterator is the same |
140 | ScopedString valString(scope, scope.engine->newIdentifier(QStringLiteral("entries" ))); |
141 | ScopedFunctionObject entriesFn(scope, FunctionObject::createBuiltinFunction(engine, nameOrSymbol: valString, code: MapPrototype::method_entries, argumentCount: 0)); |
142 | defineDefaultProperty(QStringLiteral("entries" ), value: entriesFn); |
143 | defineDefaultProperty(name: engine->symbol_iterator(), value: entriesFn); |
144 | |
145 | ScopedString val(scope, engine->newString(s: QLatin1String("Map" ))); |
146 | defineReadonlyConfigurableProperty(name: engine->symbol_toStringTag(), value: val); |
147 | } |
148 | |
149 | void Heap::MapObject::init() |
150 | { |
151 | Object::init(); |
152 | esTable = new ESTable(); |
153 | } |
154 | |
155 | void Heap::MapObject::destroy() |
156 | { |
157 | delete esTable; |
158 | esTable = nullptr; |
159 | } |
160 | |
161 | void Heap::MapObject::removeUnmarkedKeys() |
162 | { |
163 | esTable->removeUnmarkedKeys(); |
164 | } |
165 | |
166 | void Heap::MapObject::markObjects(Heap::Base *that, MarkStack *markStack) |
167 | { |
168 | MapObject *m = static_cast<MapObject *>(that); |
169 | m->esTable->markObjects(s: markStack, isWeakMap: m->isWeakMap); |
170 | Object::markObjects(base: that, stack: markStack); |
171 | } |
172 | |
173 | ReturnedValue WeakMapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
174 | { |
175 | Scope scope(b); |
176 | Scoped<MapObject> that(scope, thisObject); |
177 | if (!that || !that->d()->isWeakMap) |
178 | return scope.engine->throwTypeError(); |
179 | if (!argc || !argv[0].isObject()) |
180 | return Encode(false); |
181 | |
182 | return Encode(that->d()->esTable->remove(k: argv[0])); |
183 | |
184 | } |
185 | |
186 | ReturnedValue WeakMapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
187 | { |
188 | Scope scope(b); |
189 | Scoped<MapObject> that(scope, thisObject); |
190 | if (!that || !that->d()->isWeakMap) |
191 | return scope.engine->throwTypeError(); |
192 | if (!argc || !argv[0].isObject()) |
193 | return Encode::undefined(); |
194 | |
195 | return that->d()->esTable->get(k: argv[0]); |
196 | } |
197 | |
198 | ReturnedValue WeakMapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
199 | { |
200 | Scope scope(b); |
201 | Scoped<MapObject> that(scope, thisObject); |
202 | if (!that || !that->d()->isWeakMap) |
203 | return scope.engine->throwTypeError(); |
204 | if (!argc || !argv[0].isObject()) |
205 | return Encode(false); |
206 | |
207 | return Encode(that->d()->esTable->has(k: argv[0])); |
208 | } |
209 | |
210 | ReturnedValue WeakMapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
211 | { |
212 | Scope scope(b); |
213 | Scoped<MapObject> that(scope, thisObject); |
214 | if ((!that || !that->d()->isWeakMap) || |
215 | (!argc || !argv[0].isObject())) |
216 | return scope.engine->throwTypeError(); |
217 | |
218 | that->d()->esTable->set(k: argv[0], v: argc > 1 ? argv[1] : Value::undefinedValue()); |
219 | return that.asReturnedValue(); |
220 | } |
221 | |
222 | |
223 | ReturnedValue MapPrototype::method_clear(const FunctionObject *b, const Value *thisObject, const Value *, int) |
224 | { |
225 | Scope scope(b); |
226 | Scoped<MapObject> that(scope, thisObject); |
227 | if (!that || that->d()->isWeakMap) |
228 | return scope.engine->throwTypeError(); |
229 | |
230 | that->d()->esTable->clear(); |
231 | return Encode::undefined(); |
232 | } |
233 | |
234 | ReturnedValue MapPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
235 | { |
236 | Scope scope(b); |
237 | Scoped<MapObject> that(scope, thisObject); |
238 | if (!that || that->d()->isWeakMap) |
239 | return scope.engine->throwTypeError(); |
240 | |
241 | return Encode(that->d()->esTable->remove(k: argc ? argv[0] : Value::undefinedValue())); |
242 | } |
243 | |
244 | ReturnedValue MapPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) |
245 | { |
246 | Scope scope(b); |
247 | Scoped<MapObject> that(scope, thisObject); |
248 | if (!that || that->d()->isWeakMap) |
249 | return scope.engine->throwTypeError(); |
250 | |
251 | Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(o: that)); |
252 | ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; |
253 | return ao->asReturnedValue(); |
254 | } |
255 | |
256 | ReturnedValue MapPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
257 | { |
258 | Scope scope(b); |
259 | Scoped<MapObject> that(scope, thisObject); |
260 | if (!that || that->d()->isWeakMap) |
261 | return scope.engine->throwTypeError(); |
262 | |
263 | ScopedFunctionObject callbackfn(scope, argv[0]); |
264 | if (!callbackfn) |
265 | return scope.engine->throwTypeError(); |
266 | |
267 | ScopedValue thisArg(scope, Value::undefinedValue()); |
268 | if (argc > 1) |
269 | thisArg = ScopedValue(scope, argv[1]); |
270 | |
271 | Value *arguments = scope.alloc(nValues: 3); |
272 | arguments[2] = that; |
273 | for (uint i = 0; i < that->d()->esTable->size(); ++i) { |
274 | that->d()->esTable->iterate(idx: i, k: &arguments[1], v: &arguments[0]); // fill in key (0), value (1) |
275 | |
276 | callbackfn->call(thisObject: thisArg, argv: arguments, argc: 3); |
277 | CHECK_EXCEPTION(); |
278 | } |
279 | return Encode::undefined(); |
280 | } |
281 | |
282 | ReturnedValue MapPrototype::method_get(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
283 | { |
284 | Scope scope(b); |
285 | Scoped<MapObject> that(scope, thisObject); |
286 | if (!that || that->d()->isWeakMap) |
287 | return scope.engine->throwTypeError(); |
288 | |
289 | return that->d()->esTable->get(k: argc ? argv[0] : Value::undefinedValue()); |
290 | } |
291 | |
292 | ReturnedValue MapPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
293 | { |
294 | Scope scope(b); |
295 | Scoped<MapObject> that(scope, thisObject); |
296 | if (!that || that->d()->isWeakMap) |
297 | return scope.engine->throwTypeError(); |
298 | |
299 | return Encode(that->d()->esTable->has(k: argc ? argv[0] : Value::undefinedValue())); |
300 | } |
301 | |
302 | ReturnedValue MapPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int) |
303 | { |
304 | Scope scope(b); |
305 | Scoped<MapObject> that(scope, thisObject); |
306 | if (!that || that->d()->isWeakMap) |
307 | return scope.engine->throwTypeError(); |
308 | |
309 | Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(o: that)); |
310 | ao->d()->iterationKind = IteratorKind::KeyIteratorKind; |
311 | return ao->asReturnedValue(); |
312 | } |
313 | |
314 | ReturnedValue MapPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
315 | { |
316 | Scope scope(b); |
317 | Scoped<MapObject> that(scope, thisObject); |
318 | if (!that || that->d()->isWeakMap) |
319 | return scope.engine->throwTypeError(); |
320 | |
321 | that->d()->esTable->set(k: argc ? argv[0] : Value::undefinedValue(), v: argc > 1 ? argv[1] : Value::undefinedValue()); |
322 | return that.asReturnedValue(); |
323 | } |
324 | |
325 | ReturnedValue MapPrototype::method_get_size(const FunctionObject *b, const Value *thisObject, const Value *, int) |
326 | { |
327 | Scope scope(b); |
328 | Scoped<MapObject> that(scope, thisObject); |
329 | if (!that || that->d()->isWeakMap) |
330 | return scope.engine->throwTypeError(); |
331 | |
332 | return Encode(that->d()->esTable->size()); |
333 | } |
334 | |
335 | ReturnedValue MapPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) |
336 | { |
337 | Scope scope(b); |
338 | Scoped<MapObject> that(scope, thisObject); |
339 | if (!that || that->d()->isWeakMap) |
340 | return scope.engine->throwTypeError(); |
341 | |
342 | Scoped<MapIteratorObject> ao(scope, scope.engine->newMapIteratorObject(o: that)); |
343 | ao->d()->iterationKind = IteratorKind::ValueIteratorKind; |
344 | return ao->asReturnedValue(); |
345 | } |
346 | |