Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef vm_ArgumentsObject_h
8 : #define vm_ArgumentsObject_h
9 :
10 : #include "mozilla/MemoryReporting.h"
11 :
12 : #include "gc/Barrier.h"
13 : #include "vm/NativeObject.h"
14 :
15 : namespace js {
16 :
17 : class AbstractFramePtr;
18 : class ScriptFrameIter;
19 :
20 : namespace jit {
21 : class JitFrameLayout;
22 : } // namespace jit
23 :
24 : // RareArgumentsData stores the deleted-elements bits for an arguments object.
25 : // Because |delete arguments[i]| is uncommon, we allocate this data the first
26 : // time an element is deleted.
27 : class RareArgumentsData
28 : {
29 : // Pointer to an array of bits indicating, for every argument in
30 : // [0, initialLength) whether the element has been deleted. See
31 : // ArgumentsObject::isElementDeleted comment.
32 : size_t deletedBits_[1];
33 :
34 : RareArgumentsData() = default;
35 : RareArgumentsData(const RareArgumentsData&) = delete;
36 : void operator=(const RareArgumentsData&) = delete;
37 :
38 : public:
39 : static RareArgumentsData* create(JSContext* cx, ArgumentsObject* obj);
40 : static size_t bytesRequired(size_t numActuals);
41 :
42 0 : bool isAnyElementDeleted(size_t len) const {
43 0 : return IsAnyBitArrayElementSet(deletedBits_, len);
44 : }
45 0 : bool isElementDeleted(size_t len, size_t i) const {
46 0 : MOZ_ASSERT(i < len);
47 0 : return IsBitArrayElementSet(deletedBits_, len, i);
48 : }
49 0 : void markElementDeleted(size_t len, size_t i) {
50 0 : MOZ_ASSERT(i < len);
51 0 : SetBitArrayElement(deletedBits_, len, i);
52 0 : }
53 : };
54 :
55 : // ArgumentsData stores the initial indexed arguments provided to a function
56 : // call. It is used to store arguments[i] -- up until the corresponding
57 : // property is modified, when the relevant value is flagged to memorialize the
58 : // modification.
59 : struct ArgumentsData
60 : {
61 : /*
62 : * numArgs = Max(numFormalArgs, numActualArgs)
63 : * The array 'args' has numArgs elements.
64 : */
65 : uint32_t numArgs;
66 :
67 : RareArgumentsData* rareData;
68 :
69 : /*
70 : * This array holds either the current argument value or the magic
71 : * forwarding value. The latter means that the function has both a
72 : * CallObject and an ArgumentsObject AND the particular formal variable is
73 : * aliased by the CallObject. In such cases, the CallObject holds the
74 : * canonical value so any element access to the arguments object should load
75 : * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT).
76 : */
77 : GCPtrValue args[1];
78 :
79 : /* For jit use: */
80 0 : static ptrdiff_t offsetOfArgs() { return offsetof(ArgumentsData, args); }
81 :
82 : /* Iterate args. */
83 3 : GCPtrValue* begin() { return args; }
84 : const GCPtrValue* begin() const { return args; }
85 : GCPtrValue* end() { return args + numArgs; }
86 : const GCPtrValue* end() const { return args + numArgs; }
87 :
88 130 : static size_t bytesRequired(size_t numArgs) {
89 130 : return offsetof(ArgumentsData, args) + numArgs * sizeof(Value);
90 : }
91 : };
92 :
93 : // Maximum supported value of arguments.length. This bounds the maximum
94 : // number of arguments that can be supplied to Function.prototype.apply.
95 : // This value also bounds the number of elements parsed in an array
96 : // initializer.
97 : // NB: keep this in sync with the copy in builtin/SelfHostingDefines.h.
98 : static const unsigned ARGS_LENGTH_MAX = 500 * 1000;
99 :
100 : /*
101 : * ArgumentsObject instances represent |arguments| objects created to store
102 : * function arguments when a function is called. It's expensive to create such
103 : * objects if they're never used, so they're only created when they are
104 : * potentially used.
105 : *
106 : * Arguments objects are complicated because, for non-strict mode code, they
107 : * must alias any named arguments which were provided to the function. Gnarly
108 : * example:
109 : *
110 : * function f(a, b, c, d)
111 : * {
112 : * arguments[0] = "seta";
113 : * assertEq(a, "seta");
114 : * b = "setb";
115 : * assertEq(arguments[1], "setb");
116 : * c = "setc";
117 : * assertEq(arguments[2], undefined);
118 : * arguments[3] = "setd";
119 : * assertEq(d, undefined);
120 : * }
121 : * f("arga", "argb");
122 : *
123 : * ES5's strict mode behaves more sanely, and named arguments don't alias
124 : * elements of an arguments object.
125 : *
126 : * ArgumentsObject instances use the following reserved slots:
127 : *
128 : * INITIAL_LENGTH_SLOT
129 : * Stores the initial value of arguments.length, plus a bit indicating
130 : * whether arguments.length and/or arguments[@@iterator] have been
131 : * modified. Use initialLength(), hasOverriddenLength(), and
132 : * hasOverriddenIterator() to access these values. If arguments.length has
133 : * been modified, then the current value of arguments.length is stored in
134 : * another slot associated with a new property.
135 : * DATA_SLOT
136 : * Stores an ArgumentsData*, described above.
137 : * MAYBE_CALL_SLOT
138 : * Stores the CallObject, if the callee has aliased bindings. See
139 : * the ArgumentsData::args comment.
140 : * CALLEE_SLOT
141 : * Stores the initial arguments.callee. This value can be overridden on
142 : * mapped arguments objects, see hasOverriddenCallee.
143 : */
144 : class ArgumentsObject : public NativeObject
145 : {
146 : protected:
147 : static const uint32_t INITIAL_LENGTH_SLOT = 0;
148 : static const uint32_t DATA_SLOT = 1;
149 : static const uint32_t MAYBE_CALL_SLOT = 2;
150 : static const uint32_t CALLEE_SLOT = 3;
151 :
152 : public:
153 : static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1;
154 : static const uint32_t ITERATOR_OVERRIDDEN_BIT = 0x2;
155 : static const uint32_t ELEMENT_OVERRIDDEN_BIT = 0x4;
156 : static const uint32_t CALLEE_OVERRIDDEN_BIT = 0x8;
157 : static const uint32_t PACKED_BITS_COUNT = 4;
158 :
159 : static_assert(ARGS_LENGTH_MAX <= (UINT32_MAX >> PACKED_BITS_COUNT),
160 : "Max arguments length must fit in available bits");
161 :
162 : protected:
163 : template <typename CopyArgs>
164 : static ArgumentsObject* create(JSContext* cx, HandleFunction callee, unsigned numActuals,
165 : CopyArgs& copy);
166 :
167 1385 : ArgumentsData* data() const {
168 1385 : return reinterpret_cast<ArgumentsData*>(getFixedSlot(DATA_SLOT).toPrivate());
169 : }
170 :
171 555 : RareArgumentsData* maybeRareData() const {
172 555 : return data()->rareData;
173 : }
174 :
175 : MOZ_MUST_USE bool createRareData(JSContext* cx);
176 :
177 0 : RareArgumentsData* getOrCreateRareData(JSContext* cx) {
178 0 : if (!data()->rareData && !createRareData(cx))
179 0 : return nullptr;
180 0 : return data()->rareData;
181 : }
182 :
183 : static bool obj_delProperty(JSContext* cx, HandleObject obj, HandleId id,
184 : ObjectOpResult& result);
185 :
186 : static bool obj_mayResolve(const JSAtomState& names, jsid id, JSObject*);
187 :
188 : public:
189 : static const uint32_t RESERVED_SLOTS = 4;
190 : static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND;
191 :
192 : /* Create an arguments object for a frame that is expecting them. */
193 : static ArgumentsObject* createExpected(JSContext* cx, AbstractFramePtr frame);
194 :
195 : /*
196 : * Purposefully disconnect the returned arguments object from the frame
197 : * by always creating a new copy that does not alias formal parameters.
198 : * This allows function-local analysis to determine that formals are
199 : * not aliased and generally simplifies arguments objects.
200 : */
201 : static ArgumentsObject* createUnexpected(JSContext* cx, ScriptFrameIter& iter);
202 : static ArgumentsObject* createUnexpected(JSContext* cx, AbstractFramePtr frame);
203 : static ArgumentsObject* createForIon(JSContext* cx, jit::JitFrameLayout* frame,
204 : HandleObject scopeChain);
205 :
206 : /*
207 : * Allocate ArgumentsData and fill reserved slots after allocating an
208 : * ArgumentsObject in Ion code.
209 : */
210 : static ArgumentsObject* finishForIon(JSContext* cx, jit::JitFrameLayout* frame,
211 : JSObject* scopeChain, ArgumentsObject* obj);
212 :
213 : static ArgumentsObject* createTemplateObject(JSContext* cx, bool mapped);
214 :
215 : /*
216 : * Return the initial length of the arguments. This may differ from the
217 : * current value of arguments.length!
218 : */
219 1130 : uint32_t initialLength() const {
220 1130 : uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >> PACKED_BITS_COUNT;
221 1130 : MOZ_ASSERT(argc <= ARGS_LENGTH_MAX);
222 1130 : return argc;
223 : }
224 :
225 : /* True iff arguments.length has been assigned or its attributes changed. */
226 307 : bool hasOverriddenLength() const {
227 307 : const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
228 307 : return v.toInt32() & LENGTH_OVERRIDDEN_BIT;
229 : }
230 :
231 17 : void markLengthOverridden() {
232 17 : uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT;
233 17 : setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
234 17 : }
235 :
236 : /*
237 : * Create the default "length" property and set LENGTH_OVERRIDDEN_BIT.
238 : */
239 : static bool reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj);
240 :
241 : /* True iff arguments[@@iterator] has been assigned or its attributes
242 : * changed. */
243 0 : bool hasOverriddenIterator() const {
244 0 : const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
245 0 : return v.toInt32() & ITERATOR_OVERRIDDEN_BIT;
246 : }
247 :
248 0 : void markIteratorOverridden() {
249 0 : uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ITERATOR_OVERRIDDEN_BIT;
250 0 : setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
251 0 : }
252 :
253 : /*
254 : * Create the default @@iterator property and set ITERATOR_OVERRIDDEN_BIT.
255 : */
256 : static bool reifyIterator(JSContext* cx, Handle<ArgumentsObject*> obj);
257 :
258 : /* True iff any element has been assigned or its attributes
259 : * changed. */
260 0 : bool hasOverriddenElement() const {
261 0 : const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
262 0 : return v.toInt32() & ELEMENT_OVERRIDDEN_BIT;
263 : }
264 :
265 17 : void markElementOverridden() {
266 17 : uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ELEMENT_OVERRIDDEN_BIT;
267 17 : setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
268 17 : }
269 :
270 : /*
271 : * Because the arguments object is a real object, its elements may be
272 : * deleted. This is implemented by setting a 'deleted' flag for the arg
273 : * which is read by argument object resolve and getter/setter hooks.
274 : *
275 : * NB: an element, once deleted, stays deleted. Thus:
276 : *
277 : * function f(x) { delete arguments[0]; arguments[0] = 42; return x }
278 : * assertEq(f(1), 1);
279 : *
280 : * This works because, once a property is deleted from an arguments object,
281 : * it gets regular properties with regular getters/setters that don't alias
282 : * ArgumentsData::slots.
283 : */
284 552 : bool isElementDeleted(uint32_t i) const {
285 552 : MOZ_ASSERT(i < data()->numArgs);
286 552 : if (i >= initialLength())
287 0 : return false;
288 552 : return maybeRareData() && maybeRareData()->isElementDeleted(initialLength(), i);
289 : }
290 :
291 0 : bool isAnyElementDeleted() const {
292 0 : return maybeRareData() && maybeRareData()->isAnyElementDeleted(initialLength());
293 : }
294 :
295 : bool markElementDeleted(JSContext* cx, uint32_t i);
296 :
297 : /*
298 : * An ArgumentsObject serves two roles:
299 : * - a real object, accessed through regular object operations, e.g..,
300 : * GetElement corresponding to 'arguments[i]';
301 : * - a VM-internal data structure, storing the value of arguments (formal
302 : * and actual) that are accessed directly by the VM when a reading the
303 : * value of a formal parameter.
304 : * There are two ways to access the ArgumentsData::args corresponding to
305 : * these two use cases:
306 : * - object access should use elements(i) which will take care of
307 : * forwarding when the value is the magic forwarding value;
308 : * - VM argument access should use arg(i) which will assert that the
309 : * value is not the magic forwarding value (since, if such forwarding was
310 : * needed, the frontend should have emitted JSOP_GETALIASEDVAR).
311 : */
312 : const Value& element(uint32_t i) const;
313 :
314 : inline void setElement(JSContext* cx, uint32_t i, const Value& v);
315 :
316 7 : const Value& arg(unsigned i) const {
317 7 : MOZ_ASSERT(i < data()->numArgs);
318 7 : const Value& v = data()->args[i];
319 7 : MOZ_ASSERT(!v.isMagic());
320 7 : return v;
321 : }
322 :
323 2 : void setArg(unsigned i, const Value& v) {
324 2 : MOZ_ASSERT(i < data()->numArgs);
325 2 : GCPtrValue& lhs = data()->args[i];
326 2 : MOZ_ASSERT(!lhs.isMagic());
327 2 : lhs = v;
328 2 : }
329 :
330 : /*
331 : * Attempt to speedily and efficiently access the i-th element of this
332 : * arguments object. Return true if the element was speedily returned.
333 : * Return false if the element must be looked up more slowly using
334 : * getProperty or some similar method. The second overload copies the
335 : * elements [start, start + count) into the locations starting at 'vp'.
336 : *
337 : * NB: Returning false does not indicate error!
338 : */
339 75 : bool maybeGetElement(uint32_t i, MutableHandleValue vp) {
340 75 : if (i >= initialLength() || isElementDeleted(i))
341 0 : return false;
342 75 : vp.set(element(i));
343 75 : return true;
344 : }
345 :
346 : inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value* vp);
347 :
348 : /*
349 : * Measures things hanging off this ArgumentsObject that are counted by the
350 : * |miscSize| argument in JSObject::sizeOfExcludingThis().
351 : */
352 0 : size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const {
353 0 : if (!data()) // Template arguments objects have no data.
354 0 : return 0;
355 0 : return mallocSizeOf(data()) + mallocSizeOf(maybeRareData());
356 : }
357 0 : size_t sizeOfData() const {
358 0 : return ArgumentsData::bytesRequired(data()->numArgs) +
359 0 : (maybeRareData() ? RareArgumentsData::bytesRequired(initialLength()) : 0);
360 : }
361 :
362 : static void finalize(FreeOp* fop, JSObject* obj);
363 : static void trace(JSTracer* trc, JSObject* obj);
364 : static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);
365 :
366 : /* For jit use: */
367 0 : static size_t getDataSlotOffset() {
368 0 : return getFixedSlotOffset(DATA_SLOT);
369 : }
370 1 : static size_t getInitialLengthSlotOffset() {
371 1 : return getFixedSlotOffset(INITIAL_LENGTH_SLOT);
372 : }
373 :
374 14 : static Value MagicEnvSlotValue(uint32_t slot) {
375 : // When forwarding slots to a backing CallObject, the slot numbers are
376 : // stored as uint32 magic values. This raises an ambiguity if we have
377 : // also copied JS_OPTIMIZED_OUT magic from a JIT frame or
378 : // JS_UNINITIALIZED_LEXICAL magic on the CallObject. To distinguish
379 : // normal magic values (those with a JSWhyMagic) and uint32 magic
380 : // values, we add the maximum JSWhyMagic value to the slot
381 : // number. This is safe as ARGS_LENGTH_MAX is well below UINT32_MAX.
382 : JS_STATIC_ASSERT(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
383 14 : return JS::MagicValueUint32(slot + JS_WHY_MAGIC_COUNT);
384 : }
385 0 : static uint32_t SlotFromMagicScopeSlotValue(const Value& v) {
386 : JS_STATIC_ASSERT(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
387 0 : return v.magicUint32() - JS_WHY_MAGIC_COUNT;
388 : }
389 242 : static bool IsMagicScopeSlotValue(const Value& v) {
390 242 : return v.isMagic() && v.magicUint32() > JS_WHY_MAGIC_COUNT;
391 : }
392 :
393 : static void MaybeForwardToCallObject(AbstractFramePtr frame, ArgumentsObject* obj,
394 : ArgumentsData* data);
395 : static void MaybeForwardToCallObject(jit::JitFrameLayout* frame, HandleObject callObj,
396 : ArgumentsObject* obj, ArgumentsData* data);
397 : };
398 :
399 : class MappedArgumentsObject : public ArgumentsObject
400 : {
401 : static const ClassOps classOps_;
402 : static const ObjectOps objectOps_;
403 :
404 : public:
405 : static const Class class_;
406 :
407 66 : JSFunction& callee() const {
408 66 : return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
409 : }
410 :
411 0 : bool hasOverriddenCallee() const {
412 0 : const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
413 0 : return v.toInt32() & CALLEE_OVERRIDDEN_BIT;
414 : }
415 :
416 0 : void markCalleeOverridden() {
417 0 : uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | CALLEE_OVERRIDDEN_BIT;
418 0 : setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
419 0 : }
420 :
421 : private:
422 : static bool obj_enumerate(JSContext* cx, HandleObject obj);
423 : static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
424 : static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
425 : Handle<JS::PropertyDescriptor> desc, ObjectOpResult& result);
426 : };
427 :
428 : class UnmappedArgumentsObject : public ArgumentsObject
429 : {
430 : static const ClassOps classOps_;
431 :
432 : public:
433 : static const Class class_;
434 :
435 : private:
436 : static bool obj_enumerate(JSContext* cx, HandleObject obj);
437 : static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
438 : };
439 :
440 : } // namespace js
441 :
442 : template<>
443 : inline bool
444 141565 : JSObject::is<js::ArgumentsObject>() const
445 : {
446 141565 : return is<js::MappedArgumentsObject>() || is<js::UnmappedArgumentsObject>();
447 : }
448 :
449 : #endif /* vm_ArgumentsObject_h */
|