1/*
2 * Copyright (C) 2015-2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "GenericArguments.h"
29#include "JSCInlines.h"
30
31namespace JSC {
32
33template<typename Type>
34void GenericArguments<Type>::visitChildren(JSCell* thisCell, SlotVisitor& visitor)
35{
36 Type* thisObject = static_cast<Type*>(thisCell);
37 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
38 Base::visitChildren(thisCell, visitor);
39
40 if (thisObject->m_modifiedArgumentsDescriptor)
41 visitor.markAuxiliary(thisObject->m_modifiedArgumentsDescriptor.getUnsafe());
42}
43
44template<typename Type>
45bool GenericArguments<Type>::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName ident, PropertySlot& slot)
46{
47 Type* thisObject = jsCast<Type*>(object);
48 VM& vm = exec->vm();
49
50 if (!thisObject->overrodeThings()) {
51 if (ident == vm.propertyNames->length) {
52 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::DontEnum), jsNumber(thisObject->internalLength()));
53 return true;
54 }
55 if (ident == vm.propertyNames->callee) {
56 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::DontEnum), thisObject->callee());
57 return true;
58 }
59 if (ident == vm.propertyNames->iteratorSymbol) {
60 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::DontEnum), thisObject->globalObject(vm)->arrayProtoValuesFunction());
61 return true;
62 }
63 }
64
65 if (Optional<uint32_t> index = parseIndex(ident))
66 return GenericArguments<Type>::getOwnPropertySlotByIndex(thisObject, exec, *index, slot);
67
68 return Base::getOwnPropertySlot(thisObject, exec, ident, slot);
69}
70
71template<typename Type>
72bool GenericArguments<Type>::getOwnPropertySlotByIndex(JSObject* object, ExecState* exec, unsigned index, PropertySlot& slot)
73{
74 Type* thisObject = jsCast<Type*>(object);
75
76 if (!thisObject->isModifiedArgumentDescriptor(index) && thisObject->isMappedArgument(index)) {
77 slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::None), thisObject->getIndexQuickly(index));
78 return true;
79 }
80
81 bool result = Base::getOwnPropertySlotByIndex(object, exec, index, slot);
82
83 if (thisObject->isMappedArgument(index)) {
84 ASSERT(result);
85 slot.setValue(thisObject, slot.attributes(), thisObject->getIndexQuickly(index));
86 return true;
87 }
88
89 return result;
90}
91
92template<typename Type>
93void GenericArguments<Type>::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode)
94{
95 Type* thisObject = jsCast<Type*>(object);
96
97 if (array.includeStringProperties()) {
98 for (unsigned i = 0; i < thisObject->internalLength(); ++i) {
99 if (!thisObject->isMappedArgument(i))
100 continue;
101 array.add(Identifier::from(exec, i));
102 }
103 }
104
105 if (mode.includeDontEnumProperties() && !thisObject->overrodeThings()) {
106 VM& vm = exec->vm();
107 array.add(vm.propertyNames->length);
108 array.add(vm.propertyNames->callee);
109 if (array.includeSymbolProperties())
110 array.add(vm.propertyNames->iteratorSymbol);
111 }
112 Base::getOwnPropertyNames(thisObject, exec, array, mode);
113}
114
115template<typename Type>
116bool GenericArguments<Type>::put(JSCell* cell, ExecState* exec, PropertyName ident, JSValue value, PutPropertySlot& slot)
117{
118 Type* thisObject = jsCast<Type*>(cell);
119 VM& vm = exec->vm();
120
121 if (!thisObject->overrodeThings()
122 && (ident == vm.propertyNames->length
123 || ident == vm.propertyNames->callee
124 || ident == vm.propertyNames->iteratorSymbol)) {
125 thisObject->overrideThings(vm);
126 PutPropertySlot dummy = slot; // This put is not cacheable, so we shadow the slot that was given to us.
127 return Base::put(thisObject, exec, ident, value, dummy);
128 }
129
130 // https://tc39.github.io/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver
131 // Fall back to the OrdinarySet when the receiver is altered from the thisObject.
132 if (UNLIKELY(isThisValueAltered(slot, thisObject)))
133 return ordinarySetSlow(exec, thisObject, ident, value, slot.thisValue(), slot.isStrictMode());
134
135 Optional<uint32_t> index = parseIndex(ident);
136 if (index && thisObject->isMappedArgument(index.value())) {
137 thisObject->setIndexQuickly(vm, index.value(), value);
138 return true;
139 }
140
141 return Base::put(thisObject, exec, ident, value, slot);
142}
143
144template<typename Type>
145bool GenericArguments<Type>::putByIndex(JSCell* cell, ExecState* exec, unsigned index, JSValue value, bool shouldThrow)
146{
147 Type* thisObject = jsCast<Type*>(cell);
148 VM& vm = exec->vm();
149
150 if (thisObject->isMappedArgument(index)) {
151 thisObject->setIndexQuickly(vm, index, value);
152 return true;
153 }
154
155 return Base::putByIndex(cell, exec, index, value, shouldThrow);
156}
157
158template<typename Type>
159bool GenericArguments<Type>::deleteProperty(JSCell* cell, ExecState* exec, PropertyName ident)
160{
161 Type* thisObject = jsCast<Type*>(cell);
162 VM& vm = exec->vm();
163
164 if (!thisObject->overrodeThings()
165 && (ident == vm.propertyNames->length
166 || ident == vm.propertyNames->callee
167 || ident == vm.propertyNames->iteratorSymbol))
168 thisObject->overrideThings(vm);
169
170 if (Optional<uint32_t> index = parseIndex(ident))
171 return GenericArguments<Type>::deletePropertyByIndex(thisObject, exec, *index);
172
173 return Base::deleteProperty(thisObject, exec, ident);
174}
175
176template<typename Type>
177bool GenericArguments<Type>::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned index)
178{
179 Type* thisObject = jsCast<Type*>(cell);
180 VM& vm = exec->vm();
181
182 bool propertyMightBeInJSObjectStorage = thisObject->isModifiedArgumentDescriptor(index) || !thisObject->isMappedArgument(index);
183 bool deletedProperty = true;
184 if (propertyMightBeInJSObjectStorage)
185 deletedProperty = Base::deletePropertyByIndex(cell, exec, index);
186
187 if (deletedProperty) {
188 // Deleting an indexed property unconditionally unmaps it.
189 if (thisObject->isMappedArgument(index)) {
190 // We need to check that the property was mapped so we don't write to random memory.
191 thisObject->unmapArgument(vm, index);
192 }
193 thisObject->setModifiedArgumentDescriptor(vm, index);
194 }
195
196 return deletedProperty;
197}
198
199template<typename Type>
200bool GenericArguments<Type>::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName ident, const PropertyDescriptor& descriptor, bool shouldThrow)
201{
202 Type* thisObject = jsCast<Type*>(object);
203 VM& vm = exec->vm();
204 auto scope = DECLARE_THROW_SCOPE(vm);
205
206 if (ident == vm.propertyNames->length
207 || ident == vm.propertyNames->callee
208 || ident == vm.propertyNames->iteratorSymbol)
209 thisObject->overrideThingsIfNecessary(vm);
210 else {
211 Optional<uint32_t> optionalIndex = parseIndex(ident);
212 if (optionalIndex) {
213 uint32_t index = optionalIndex.value();
214 if (!descriptor.isAccessorDescriptor() && thisObject->isMappedArgument(optionalIndex.value())) {
215 // If the property is not deleted and we are using a non-accessor descriptor, then
216 // make sure that the aliased argument sees the value.
217 if (descriptor.value())
218 thisObject->setIndexQuickly(vm, index, descriptor.value());
219
220 // If the property is not deleted and we are using a non-accessor, writable,
221 // configurable and enumerable descriptor and isn't modified, then we are done.
222 // The argument continues to be aliased.
223 if (descriptor.writable() && descriptor.configurable() && descriptor.enumerable() && !thisObject->isModifiedArgumentDescriptor(index))
224 return true;
225
226 if (!thisObject->isModifiedArgumentDescriptor(index)) {
227 // If it is a new entry, we need to put direct to initialize argument[i] descriptor properly
228 JSValue value = thisObject->getIndexQuickly(index);
229 ASSERT(value);
230 object->putDirectMayBeIndex(exec, ident, value);
231 scope.assertNoException();
232
233 thisObject->setModifiedArgumentDescriptor(vm, index);
234 }
235 }
236
237 if (thisObject->isMappedArgument(index)) {
238 // Just unmap arguments if its descriptor contains {writable: false}.
239 // Check https://tc39.github.io/ecma262/#sec-createunmappedargumentsobject
240 // and https://tc39.github.io/ecma262/#sec-createmappedargumentsobject to verify that all data
241 // property from arguments object are {writable: true, configurable: true, enumerable: true} by default
242 if ((descriptor.writablePresent() && !descriptor.writable()) || descriptor.isAccessorDescriptor()) {
243 if (!descriptor.isAccessorDescriptor()) {
244 JSValue value = thisObject->getIndexQuickly(index);
245 ASSERT(value);
246 object->putDirectMayBeIndex(exec, ident, value);
247 scope.assertNoException();
248 }
249 thisObject->unmapArgument(vm, index);
250 thisObject->setModifiedArgumentDescriptor(vm, index);
251 }
252 }
253 }
254 }
255
256 // Now just let the normal object machinery do its thing.
257 RELEASE_AND_RETURN(scope, Base::defineOwnProperty(object, exec, ident, descriptor, shouldThrow));
258}
259
260template<typename Type>
261void GenericArguments<Type>::initModifiedArgumentsDescriptor(VM& vm, unsigned argsLength)
262{
263 RELEASE_ASSERT(!m_modifiedArgumentsDescriptor);
264
265 if (argsLength) {
266 void* backingStore = vm.gigacageAuxiliarySpace(m_modifiedArgumentsDescriptor.kind).allocateNonVirtual(vm, WTF::roundUpToMultipleOf<8>(argsLength), nullptr, AllocationFailureMode::Assert);
267 bool* modifiedArguments = static_cast<bool*>(backingStore);
268 m_modifiedArgumentsDescriptor.set(vm, this, modifiedArguments, argsLength);
269 for (unsigned i = argsLength; i--;)
270 modifiedArguments[i] = false;
271 }
272}
273
274template<typename Type>
275void GenericArguments<Type>::initModifiedArgumentsDescriptorIfNecessary(VM& vm, unsigned argsLength)
276{
277 if (!m_modifiedArgumentsDescriptor)
278 initModifiedArgumentsDescriptor(vm, argsLength);
279}
280
281template<typename Type>
282void GenericArguments<Type>::setModifiedArgumentDescriptor(VM& vm, unsigned index, unsigned length)
283{
284 initModifiedArgumentsDescriptorIfNecessary(vm, length);
285 if (index < length)
286 m_modifiedArgumentsDescriptor.at(index, length) = true;
287}
288
289template<typename Type>
290bool GenericArguments<Type>::isModifiedArgumentDescriptor(unsigned index, unsigned length)
291{
292 if (!m_modifiedArgumentsDescriptor)
293 return false;
294 if (index < length)
295 return m_modifiedArgumentsDescriptor.at(index, length);
296 return false;
297}
298
299template<typename Type>
300void GenericArguments<Type>::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length)
301{
302 VM& vm = exec->vm();
303 auto scope = DECLARE_THROW_SCOPE(vm);
304
305 Type* thisObject = static_cast<Type*>(this);
306 for (unsigned i = 0; i < length; ++i) {
307 if (thisObject->isMappedArgument(i + offset))
308 exec->r(firstElementDest + i) = thisObject->getIndexQuickly(i + offset);
309 else {
310 exec->r(firstElementDest + i) = get(exec, i + offset);
311 RETURN_IF_EXCEPTION(scope, void());
312 }
313 }
314}
315
316} // namespace JSC
317