/src/hermes/lib/VM/JSLib/Map.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | //===----------------------------------------------------------------------===// |
9 | | /// \file |
10 | | /// ES6.0 23.1 Initialize the Map constructor. |
11 | | //===----------------------------------------------------------------------===// |
12 | | |
13 | | #include "JSLibInternal.h" |
14 | | |
15 | | #include "hermes/VM/StringPrimitive.h" |
16 | | |
17 | | namespace hermes { |
18 | | namespace vm { |
19 | | |
20 | 94 | Handle<JSObject> createMapConstructor(Runtime &runtime) { |
21 | 94 | auto mapPrototype = Handle<JSObject>::vmcast(&runtime.mapPrototype); |
22 | | |
23 | | // Map.prototype.xxx methods. |
24 | 94 | defineMethod( |
25 | 94 | runtime, |
26 | 94 | mapPrototype, |
27 | 94 | Predefined::getSymbolID(Predefined::clear), |
28 | 94 | nullptr, |
29 | 94 | mapPrototypeClear, |
30 | 94 | 0); |
31 | | |
32 | 94 | defineMethod( |
33 | 94 | runtime, |
34 | 94 | mapPrototype, |
35 | 94 | Predefined::getSymbolID(Predefined::deleteStr), |
36 | 94 | nullptr, |
37 | 94 | mapPrototypeDelete, |
38 | 94 | 1); |
39 | | |
40 | 94 | defineMethod( |
41 | 94 | runtime, |
42 | 94 | mapPrototype, |
43 | 94 | Predefined::getSymbolID(Predefined::entries), |
44 | 94 | nullptr, |
45 | 94 | mapPrototypeEntries, |
46 | 94 | 0); |
47 | | |
48 | 94 | defineMethod( |
49 | 94 | runtime, |
50 | 94 | mapPrototype, |
51 | 94 | Predefined::getSymbolID(Predefined::forEach), |
52 | 94 | nullptr, |
53 | 94 | mapPrototypeForEach, |
54 | 94 | 1); |
55 | | |
56 | 94 | defineMethod( |
57 | 94 | runtime, |
58 | 94 | mapPrototype, |
59 | 94 | Predefined::getSymbolID(Predefined::get), |
60 | 94 | nullptr, |
61 | 94 | mapPrototypeGet, |
62 | 94 | 1); |
63 | | |
64 | 94 | defineMethod( |
65 | 94 | runtime, |
66 | 94 | mapPrototype, |
67 | 94 | Predefined::getSymbolID(Predefined::has), |
68 | 94 | nullptr, |
69 | 94 | mapPrototypeHas, |
70 | 94 | 1); |
71 | | |
72 | 94 | defineMethod( |
73 | 94 | runtime, |
74 | 94 | mapPrototype, |
75 | 94 | Predefined::getSymbolID(Predefined::keys), |
76 | 94 | nullptr, |
77 | 94 | mapPrototypeKeys, |
78 | 94 | 0); |
79 | | |
80 | 94 | defineMethod( |
81 | 94 | runtime, |
82 | 94 | mapPrototype, |
83 | 94 | Predefined::getSymbolID(Predefined::set), |
84 | 94 | nullptr, |
85 | 94 | mapPrototypeSet, |
86 | 94 | 2); |
87 | | |
88 | 94 | defineAccessor( |
89 | 94 | runtime, |
90 | 94 | mapPrototype, |
91 | 94 | Predefined::getSymbolID(Predefined::size), |
92 | 94 | nullptr, |
93 | 94 | mapPrototypeSizeGetter, |
94 | 94 | nullptr, |
95 | 94 | false, |
96 | 94 | true); |
97 | | |
98 | 94 | defineMethod( |
99 | 94 | runtime, |
100 | 94 | mapPrototype, |
101 | 94 | Predefined::getSymbolID(Predefined::values), |
102 | 94 | nullptr, |
103 | 94 | mapPrototypeValues, |
104 | 94 | 0); |
105 | | |
106 | 94 | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
107 | | |
108 | 94 | PseudoHandle<> propValue = runtime.ignoreAllocationFailure( |
109 | 94 | JSObject::getNamed_RJS( |
110 | 94 | mapPrototype, runtime, Predefined::getSymbolID(Predefined::entries))); |
111 | 94 | runtime.ignoreAllocationFailure( |
112 | 94 | JSObject::defineOwnProperty( |
113 | 94 | mapPrototype, |
114 | 94 | runtime, |
115 | 94 | Predefined::getSymbolID(Predefined::SymbolIterator), |
116 | 94 | dpf, |
117 | 94 | runtime.makeHandle<NativeFunction>(propValue.get()))); |
118 | | |
119 | 94 | dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); |
120 | 94 | dpf.writable = 0; |
121 | 94 | dpf.enumerable = 0; |
122 | 94 | defineProperty( |
123 | 94 | runtime, |
124 | 94 | mapPrototype, |
125 | 94 | Predefined::getSymbolID(Predefined::SymbolToStringTag), |
126 | 94 | runtime.getPredefinedStringHandle(Predefined::Map), |
127 | 94 | dpf); |
128 | | |
129 | 94 | auto cons = defineSystemConstructor<JSMap>( |
130 | 94 | runtime, |
131 | 94 | Predefined::getSymbolID(Predefined::Map), |
132 | 94 | mapConstructor, |
133 | 94 | mapPrototype, |
134 | 94 | 0, |
135 | 94 | CellKind::JSMapKind); |
136 | | |
137 | 94 | return cons; |
138 | 94 | } |
139 | | |
140 | | CallResult<HermesValue> |
141 | 0 | mapConstructor(void *, Runtime &runtime, NativeArgs args) { |
142 | 0 | GCScope gcScope{runtime}; |
143 | 0 | if (LLVM_UNLIKELY(!args.isConstructorCall())) { |
144 | 0 | return runtime.raiseTypeError("Constructor Map requires 'new'"); |
145 | 0 | } |
146 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
147 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
148 | 0 | return runtime.raiseTypeError("Map Constructor only applies to Map object"); |
149 | 0 | } |
150 | 0 | JSMap::initializeStorage(selfHandle, runtime); |
151 | 0 | if (args.getArgCount() == 0 || args.getArg(0).isUndefined() || |
152 | 0 | args.getArg(0).isNull()) { |
153 | 0 | return selfHandle.getHermesValue(); |
154 | 0 | } |
155 | | |
156 | 0 | auto propRes = JSObject::getNamed_RJS( |
157 | 0 | selfHandle, runtime, Predefined::getSymbolID(Predefined::set)); |
158 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
159 | 0 | return ExecutionStatus::EXCEPTION; |
160 | 0 | } |
161 | | |
162 | | // ES6.0 23.1.1.1.7: Cache adder across all iterations of the loop. |
163 | 0 | auto adder = |
164 | 0 | Handle<Callable>::dyn_vmcast(runtime.makeHandle(std::move(*propRes))); |
165 | 0 | if (!adder) { |
166 | 0 | return runtime.raiseTypeError("Property 'set' for Map is not callable"); |
167 | 0 | } |
168 | | |
169 | 0 | return addEntriesFromIterable( |
170 | 0 | runtime, |
171 | 0 | selfHandle, |
172 | 0 | args.getArgHandle(0), |
173 | 0 | [&runtime, selfHandle, adder](Runtime &, Handle<> key, Handle<> value) { |
174 | 0 | return Callable::executeCall2( |
175 | 0 | adder, |
176 | 0 | runtime, |
177 | 0 | selfHandle, |
178 | 0 | key.getHermesValue(), |
179 | 0 | value.getHermesValue()) |
180 | 0 | .getStatus(); |
181 | 0 | }); |
182 | 0 | } |
183 | | |
184 | | CallResult<HermesValue> |
185 | 0 | mapPrototypeClear(void *, Runtime &runtime, NativeArgs args) { |
186 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
187 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
188 | 0 | return runtime.raiseTypeError( |
189 | 0 | "Non-Map object called on Map.prototype.clear"); |
190 | 0 | } |
191 | 0 | JSMap::clear(selfHandle, runtime); |
192 | 0 | return HermesValue::encodeUndefinedValue(); |
193 | 0 | } |
194 | | |
195 | | CallResult<HermesValue> |
196 | 0 | mapPrototypeDelete(void *, Runtime &runtime, NativeArgs args) { |
197 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
198 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
199 | 0 | return runtime.raiseTypeError( |
200 | 0 | "Non-Map object called on Map.prototype.delete"); |
201 | 0 | } |
202 | 0 | return HermesValue::encodeBoolValue( |
203 | 0 | JSMap::deleteKey(selfHandle, runtime, args.getArgHandle(0))); |
204 | 0 | } |
205 | | |
206 | | CallResult<HermesValue> |
207 | 0 | mapPrototypeEntries(void *, Runtime &runtime, NativeArgs args) { |
208 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
209 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
210 | 0 | return runtime.raiseTypeError( |
211 | 0 | "Non-Map object called on Map.prototype.entries"); |
212 | 0 | } |
213 | 0 | auto iterator = runtime.makeHandle( |
214 | 0 | JSMapIterator::create( |
215 | 0 | runtime, Handle<JSObject>::vmcast(&runtime.mapIteratorPrototype))); |
216 | 0 | iterator->initializeIterator(runtime, selfHandle, IterationKind::Entry); |
217 | 0 | return iterator.getHermesValue(); |
218 | 0 | } |
219 | | |
220 | | CallResult<HermesValue> |
221 | 0 | mapPrototypeForEach(void *, Runtime &runtime, NativeArgs args) { |
222 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
223 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
224 | 0 | return runtime.raiseTypeError( |
225 | 0 | "Non-Map object called on Map.prototype.forEach"); |
226 | 0 | } |
227 | 0 | auto callbackfn = args.dyncastArg<Callable>(0); |
228 | 0 | if (LLVM_UNLIKELY(!callbackfn)) { |
229 | 0 | return runtime.raiseTypeError( |
230 | 0 | "callbackfn must be Callable in Map.prototype.forEach"); |
231 | 0 | } |
232 | 0 | auto thisArg = args.getArgHandle(1); |
233 | 0 | if (JSMap::forEach(selfHandle, runtime, callbackfn, thisArg) == |
234 | 0 | ExecutionStatus::EXCEPTION) |
235 | 0 | return ExecutionStatus::EXCEPTION; |
236 | 0 | return HermesValue::encodeUndefinedValue(); |
237 | 0 | } |
238 | | |
239 | | CallResult<HermesValue> |
240 | 0 | mapPrototypeGet(void *, Runtime &runtime, NativeArgs args) { |
241 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
242 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
243 | 0 | return runtime.raiseTypeError("Non-Map object called on Map.prototype.get"); |
244 | 0 | } |
245 | 0 | return JSMap::getValue(selfHandle, runtime, args.getArgHandle(0)); |
246 | 0 | } |
247 | | |
248 | | CallResult<HermesValue> |
249 | 0 | mapPrototypeHas(void *, Runtime &runtime, NativeArgs args) { |
250 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
251 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
252 | 0 | return runtime.raiseTypeError("Non-Map object called on Map.prototype.has"); |
253 | 0 | } |
254 | 0 | return HermesValue::encodeBoolValue( |
255 | 0 | JSMap::hasKey(selfHandle, runtime, args.getArgHandle(0))); |
256 | 0 | } |
257 | | |
258 | | CallResult<HermesValue> |
259 | 0 | mapPrototypeKeys(void *, Runtime &runtime, NativeArgs args) { |
260 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
261 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
262 | 0 | return runtime.raiseTypeError( |
263 | 0 | "Non-Map object called on Map.prototype.keys"); |
264 | 0 | } |
265 | | |
266 | 0 | auto iterator = runtime.makeHandle( |
267 | 0 | JSMapIterator::create( |
268 | 0 | runtime, Handle<JSObject>::vmcast(&runtime.mapIteratorPrototype))); |
269 | 0 | iterator->initializeIterator(runtime, selfHandle, IterationKind::Key); |
270 | 0 | return iterator.getHermesValue(); |
271 | 0 | } |
272 | | |
273 | | // ES12 23.1.3.9 Map.prototype.set ( key, value ) |
274 | | CallResult<HermesValue> |
275 | 0 | mapPrototypeSet(void *, Runtime &runtime, NativeArgs args) { |
276 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
277 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
278 | 0 | return runtime.raiseTypeError("Non-Map object called on Map.prototype.set"); |
279 | 0 | } |
280 | 0 | auto keyHandle = args.getArgHandle(0); |
281 | | // 5. If key is -0, set key to +0. |
282 | | // N.B. in the case of Map, only key should be normalized but not the value. |
283 | 0 | auto key = keyHandle->isNumber() && keyHandle->getNumber() == 0 |
284 | 0 | ? HandleRootOwner::getZeroValue() |
285 | 0 | : keyHandle; |
286 | 0 | JSMap::addValue(selfHandle, runtime, key, args.getArgHandle(1)); |
287 | 0 | return selfHandle.getHermesValue(); |
288 | 0 | } |
289 | | |
290 | | CallResult<HermesValue> |
291 | 0 | mapPrototypeSizeGetter(void *, Runtime &runtime, NativeArgs args) { |
292 | 0 | auto self = dyn_vmcast<JSMap>(args.getThisArg()); |
293 | 0 | if (LLVM_UNLIKELY(!self)) { |
294 | 0 | return runtime.raiseTypeError( |
295 | 0 | "Non-Map object called on Map.prototype.size"); |
296 | 0 | } |
297 | 0 | return HermesValue::encodeUntrustedNumberValue(JSMap::getSize(self, runtime)); |
298 | 0 | } |
299 | | |
300 | | CallResult<HermesValue> |
301 | 0 | mapPrototypeValues(void *, Runtime &runtime, NativeArgs args) { |
302 | 0 | auto selfHandle = args.dyncastThis<JSMap>(); |
303 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
304 | 0 | return runtime.raiseTypeError( |
305 | 0 | "Non-Map object called on Map.prototype.values"); |
306 | 0 | } |
307 | 0 | auto iterator = runtime.makeHandle( |
308 | 0 | JSMapIterator::create( |
309 | 0 | runtime, Handle<JSObject>::vmcast(&runtime.mapIteratorPrototype))); |
310 | 0 | iterator->initializeIterator(runtime, selfHandle, IterationKind::Value); |
311 | 0 | return iterator.getHermesValue(); |
312 | 0 | } |
313 | | |
314 | 94 | Handle<JSObject> createMapIteratorPrototype(Runtime &runtime) { |
315 | 94 | auto parentHandle = runtime.makeHandle( |
316 | 94 | JSObject::create( |
317 | 94 | runtime, Handle<JSObject>::vmcast(&runtime.iteratorPrototype))); |
318 | 94 | defineMethod( |
319 | 94 | runtime, |
320 | 94 | parentHandle, |
321 | 94 | Predefined::getSymbolID(Predefined::next), |
322 | 94 | nullptr, |
323 | 94 | mapIteratorPrototypeNext, |
324 | 94 | 0); |
325 | | |
326 | 94 | auto dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); |
327 | 94 | dpf.writable = 0; |
328 | 94 | dpf.enumerable = 0; |
329 | 94 | defineProperty( |
330 | 94 | runtime, |
331 | 94 | parentHandle, |
332 | 94 | Predefined::getSymbolID(Predefined::SymbolToStringTag), |
333 | 94 | runtime.getPredefinedStringHandle(Predefined::MapIterator), |
334 | 94 | dpf); |
335 | | |
336 | 94 | return parentHandle; |
337 | 94 | } |
338 | | |
339 | | CallResult<HermesValue> |
340 | 0 | mapIteratorPrototypeNext(void *, Runtime &runtime, NativeArgs args) { |
341 | 0 | auto selfHandle = args.dyncastThis<JSMapIterator>(); |
342 | 0 | if (LLVM_UNLIKELY(!selfHandle)) { |
343 | 0 | return runtime.raiseTypeError( |
344 | 0 | "Non-MapIterator object called on MapIterator.prototype.next"); |
345 | 0 | } |
346 | | |
347 | 0 | auto cr = JSMapIterator::nextElement(selfHandle, runtime); |
348 | 0 | if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) { |
349 | 0 | return ExecutionStatus::EXCEPTION; |
350 | 0 | } |
351 | 0 | return *cr; |
352 | 0 | } |
353 | | } // namespace vm |
354 | | } // namespace hermes |