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 | |
5 | #include "qv4setobject_p.h" |
6 | #include "qv4setiterator_p.h" |
7 | #include "qv4estable_p.h" |
8 | #include "qv4symbol_p.h" |
9 | |
10 | using namespace QV4; |
11 | |
12 | DEFINE_OBJECT_VTABLE(SetCtor); |
13 | DEFINE_OBJECT_VTABLE(WeakSetCtor); |
14 | DEFINE_OBJECT_VTABLE(SetObject); |
15 | |
16 | void Heap::WeakSetCtor::init(QV4::ExecutionContext *scope) |
17 | { |
18 | Heap::FunctionObject::init(scope, QStringLiteral("WeakSet" )); |
19 | } |
20 | |
21 | void Heap::SetCtor::init(QV4::ExecutionContext *scope) |
22 | { |
23 | Heap::FunctionObject::init(scope, QStringLiteral("Set" )); |
24 | } |
25 | |
26 | ReturnedValue WeakSetCtor::construct(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget, bool isWeak) |
27 | { |
28 | Scope scope(f); |
29 | Scoped<SetObject> a(scope, scope.engine->memoryManager->allocate<SetObject>()); |
30 | bool protoSet = false; |
31 | if (newTarget) |
32 | protoSet = a->setProtoFromNewTarget(newTarget); |
33 | if (!protoSet && isWeak) |
34 | a->setPrototypeOf(scope.engine->weakSetPrototype()); |
35 | a->d()->isWeakSet = isWeak; |
36 | |
37 | if (argc > 0) { |
38 | ScopedValue iterable(scope, argv[0]); |
39 | if (!iterable->isUndefined() && !iterable->isNull()) { |
40 | ScopedFunctionObject adder(scope, a->get(name: ScopedString(scope, scope.engine->newString(s: QString::fromLatin1(ba: "add" ))))); |
41 | if (!adder) |
42 | return scope.engine->throwTypeError(); |
43 | ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); |
44 | CHECK_EXCEPTION(); |
45 | if (!iter) |
46 | return a.asReturnedValue(); |
47 | |
48 | Value *nextValue = scope.alloc(nValues: 1); |
49 | ScopedValue done(scope); |
50 | forever { |
51 | done = Runtime::IteratorNext::call(scope.engine, iter, nextValue); |
52 | CHECK_EXCEPTION(); |
53 | if (done->toBoolean()) |
54 | return a.asReturnedValue(); |
55 | |
56 | adder->call(thisObject: a, argv: nextValue, argc: 1); |
57 | if (scope.hasException()) { |
58 | ScopedValue falsey(scope, Encode(false)); |
59 | return Runtime::IteratorClose::call(scope.engine, iter, falsey); |
60 | } |
61 | } |
62 | } |
63 | } |
64 | |
65 | return a.asReturnedValue(); |
66 | } |
67 | |
68 | ReturnedValue WeakSetCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
69 | { |
70 | return construct(f, argv, argc, newTarget, isWeak: true); |
71 | } |
72 | |
73 | ReturnedValue WeakSetCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) |
74 | { |
75 | Scope scope(f); |
76 | return scope.engine->throwTypeError(message: QString::fromLatin1(ba: "Set requires new" )); |
77 | } |
78 | |
79 | ReturnedValue SetCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
80 | { |
81 | return construct(f, argv, argc, newTarget, isWeak: false); |
82 | } |
83 | |
84 | void WeakSetPrototype::init(ExecutionEngine *engine, Object *ctor) |
85 | { |
86 | Scope scope(engine); |
87 | ScopedObject o(scope); |
88 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 0)); |
89 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
90 | defineDefaultProperty(name: engine->id_constructor(), value: (o = ctor)); |
91 | |
92 | defineDefaultProperty(QStringLiteral("add" ), code: method_add, argumentCount: 1); |
93 | defineDefaultProperty(QStringLiteral("delete" ), code: method_delete, argumentCount: 1); |
94 | defineDefaultProperty(QStringLiteral("has" ), code: method_has, argumentCount: 1); |
95 | |
96 | ScopedString val(scope, engine->newString(s: QLatin1String("WeakSet" ))); |
97 | defineReadonlyConfigurableProperty(name: engine->symbol_toStringTag(), value: val); |
98 | } |
99 | |
100 | ReturnedValue WeakSetPrototype::method_add(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
101 | { |
102 | Scope scope(b); |
103 | Scoped<SetObject> that(scope, thisObject); |
104 | if ((!that || !that->d()->isWeakSet) || |
105 | (!argc || !argv[0].isObject())) |
106 | return scope.engine->throwTypeError(); |
107 | |
108 | that->d()->esTable->set(k: argv[0], v: Value::undefinedValue()); |
109 | return that.asReturnedValue(); |
110 | } |
111 | |
112 | ReturnedValue WeakSetPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
113 | { |
114 | Scope scope(b); |
115 | Scoped<SetObject> that(scope, thisObject); |
116 | if (!that || !that->d()->isWeakSet) |
117 | return scope.engine->throwTypeError(); |
118 | if (!argc || !argv[0].isObject()) |
119 | return Encode(false); |
120 | |
121 | return Encode(that->d()->esTable->remove(k: argv[0])); |
122 | } |
123 | |
124 | ReturnedValue WeakSetPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
125 | { |
126 | Scope scope(b); |
127 | Scoped<SetObject> that(scope, thisObject); |
128 | if (!that || !that->d()->isWeakSet) |
129 | return scope.engine->throwTypeError(); |
130 | if (!argc || !argv[0].isObject()) |
131 | return Encode(false); |
132 | |
133 | return Encode(that->d()->esTable->has(k: argv[0])); |
134 | } |
135 | |
136 | void SetPrototype::init(ExecutionEngine *engine, Object *ctor) |
137 | { |
138 | Scope scope(engine); |
139 | ScopedObject o(scope); |
140 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 0)); |
141 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
142 | ctor->addSymbolSpecies(); |
143 | defineDefaultProperty(name: engine->id_constructor(), value: (o = ctor)); |
144 | |
145 | defineDefaultProperty(QStringLiteral("add" ), code: method_add, argumentCount: 1); |
146 | defineDefaultProperty(QStringLiteral("clear" ), code: method_clear, argumentCount: 0); |
147 | defineDefaultProperty(QStringLiteral("delete" ), code: method_delete, argumentCount: 1); |
148 | defineDefaultProperty(QStringLiteral("entries" ), code: method_entries, argumentCount: 0); |
149 | defineDefaultProperty(QStringLiteral("forEach" ), code: method_forEach, argumentCount: 1); |
150 | defineDefaultProperty(QStringLiteral("has" ), code: method_has, argumentCount: 1); |
151 | defineAccessorProperty(QStringLiteral("size" ), getter: method_get_size, setter: nullptr); |
152 | |
153 | // Per the spec, the value for 'keys' is the same as 'values'. |
154 | ScopedString valString(scope, scope.engine->newIdentifier(QStringLiteral("values" ))); |
155 | ScopedFunctionObject valuesFn(scope, FunctionObject::createBuiltinFunction(engine, nameOrSymbol: valString, code: SetPrototype::method_values, argumentCount: 0)); |
156 | defineDefaultProperty(QStringLiteral("keys" ), value: valuesFn); |
157 | defineDefaultProperty(QStringLiteral("values" ), value: valuesFn); |
158 | |
159 | defineDefaultProperty(name: engine->symbol_iterator(), value: valuesFn); |
160 | |
161 | ScopedString val(scope, engine->newString(s: QLatin1String("Set" ))); |
162 | defineReadonlyConfigurableProperty(name: engine->symbol_toStringTag(), value: val); |
163 | } |
164 | |
165 | void Heap::SetObject::init() |
166 | { |
167 | Object::init(); |
168 | esTable = new ESTable(); |
169 | } |
170 | |
171 | void Heap::SetObject::destroy() |
172 | { |
173 | delete esTable; |
174 | esTable = 0; |
175 | } |
176 | |
177 | void Heap::SetObject::removeUnmarkedKeys() |
178 | { |
179 | esTable->removeUnmarkedKeys(); |
180 | } |
181 | |
182 | void Heap::SetObject::markObjects(Heap::Base *that, MarkStack *markStack) |
183 | { |
184 | SetObject *s = static_cast<SetObject *>(that); |
185 | s->esTable->markObjects(s: markStack, isWeakMap: s->isWeakSet); |
186 | Object::markObjects(base: that, stack: markStack); |
187 | } |
188 | |
189 | ReturnedValue SetPrototype::method_add(const FunctionObject *b, const Value *thisObject, const Value *argv, int) |
190 | { |
191 | Scope scope(b); |
192 | Scoped<SetObject> that(scope, thisObject); |
193 | if (!that || that->d()->isWeakSet) |
194 | return scope.engine->throwTypeError(); |
195 | |
196 | that->d()->esTable->set(k: argv[0], v: Value::undefinedValue()); |
197 | return that.asReturnedValue(); |
198 | } |
199 | |
200 | ReturnedValue SetPrototype::method_clear(const FunctionObject *b, const Value *thisObject, const Value *, int) |
201 | { |
202 | Scope scope(b); |
203 | Scoped<SetObject> that(scope, thisObject); |
204 | if (!that || that->d()->isWeakSet) |
205 | return scope.engine->throwTypeError(); |
206 | |
207 | that->d()->esTable->clear(); |
208 | return Encode::undefined(); |
209 | } |
210 | |
211 | ReturnedValue SetPrototype::method_delete(const FunctionObject *b, const Value *thisObject, const Value *argv, int) |
212 | { |
213 | Scope scope(b); |
214 | Scoped<SetObject> that(scope, thisObject); |
215 | if (!that || that->d()->isWeakSet) |
216 | return scope.engine->throwTypeError(); |
217 | |
218 | return Encode(that->d()->esTable->remove(k: argv[0])); |
219 | } |
220 | |
221 | ReturnedValue SetPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int) |
222 | { |
223 | Scope scope(b); |
224 | Scoped<SetObject> that(scope, thisObject); |
225 | if (!that || that->d()->isWeakSet) |
226 | return scope.engine->throwTypeError(); |
227 | |
228 | Scoped<SetIteratorObject> ao(scope, scope.engine->newSetIteratorObject(o: that)); |
229 | ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind; |
230 | return ao->asReturnedValue(); |
231 | } |
232 | |
233 | ReturnedValue SetPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
234 | { |
235 | Scope scope(b); |
236 | Scoped<SetObject> that(scope, thisObject); |
237 | if (!that || that->d()->isWeakSet) |
238 | return scope.engine->throwTypeError(); |
239 | |
240 | ScopedFunctionObject callbackfn(scope, argv[0]); |
241 | if (!callbackfn) |
242 | return scope.engine->throwTypeError(); |
243 | |
244 | ScopedValue thisArg(scope, Value::undefinedValue()); |
245 | if (argc > 1) |
246 | thisArg = ScopedValue(scope, argv[1]); |
247 | |
248 | Value *arguments = scope.alloc(nValues: 3); |
249 | for (uint i = 0; i < that->d()->esTable->size(); ++i) { |
250 | that->d()->esTable->iterate(idx: i, k: &arguments[0], v: &arguments[1]); // fill in key (0), value (1) |
251 | arguments[1] = arguments[0]; // but for set, we want to return the key twice; value is always undefined. |
252 | |
253 | arguments[2] = that; |
254 | callbackfn->call(thisObject: thisArg, argv: arguments, argc: 3); |
255 | CHECK_EXCEPTION(); |
256 | } |
257 | return Encode::undefined(); |
258 | } |
259 | |
260 | ReturnedValue SetPrototype::method_has(const FunctionObject *b, const Value *thisObject, const Value *argv, int) |
261 | { |
262 | Scope scope(b); |
263 | Scoped<SetObject> that(scope, thisObject); |
264 | if (!that || that->d()->isWeakSet) |
265 | return scope.engine->throwTypeError(); |
266 | |
267 | return Encode(that->d()->esTable->has(k: argv[0])); |
268 | } |
269 | |
270 | ReturnedValue SetPrototype::method_get_size(const FunctionObject *b, const Value *thisObject, const Value *, int) |
271 | { |
272 | Scope scope(b); |
273 | Scoped<SetObject> that(scope, thisObject); |
274 | if (!that || that->d()->isWeakSet) |
275 | return scope.engine->throwTypeError(); |
276 | |
277 | return Encode(that->d()->esTable->size()); |
278 | } |
279 | |
280 | ReturnedValue SetPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int) |
281 | { |
282 | Scope scope(b); |
283 | Scoped<SetObject> that(scope, thisObject); |
284 | if (!that || that->d()->isWeakSet) |
285 | return scope.engine->throwTypeError(); |
286 | |
287 | Scoped<SetIteratorObject> ao(scope, scope.engine->newSetIteratorObject(o: that)); |
288 | ao->d()->iterationKind = IteratorKind::ValueIteratorKind; |
289 | return ao->asReturnedValue(); |
290 | } |
291 | |