|
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 "jsobj.h" |
|
13 |
|
14 #include "gc/Barrier.h" |
|
15 |
|
16 namespace js { |
|
17 |
|
18 class AbstractFramePtr; |
|
19 class ScriptFrameIter; |
|
20 |
|
21 namespace jit { |
|
22 class IonJSFrameLayout; |
|
23 } |
|
24 |
|
25 /* |
|
26 * ArgumentsData stores the initial indexed arguments provided to the |
|
27 * corresponding and that function itself. It is used to store arguments[i] |
|
28 * and arguments.callee -- up until the corresponding property is modified, |
|
29 * when the relevant value is flagged to memorialize the modification. |
|
30 */ |
|
31 struct ArgumentsData |
|
32 { |
|
33 /* |
|
34 * numArgs = Max(numFormalArgs, numActualArgs) |
|
35 * The array 'args' has numArgs elements. |
|
36 */ |
|
37 unsigned numArgs; |
|
38 |
|
39 /* |
|
40 * arguments.callee, or MagicValue(JS_OVERWRITTEN_CALLEE) if |
|
41 * arguments.callee has been modified. |
|
42 */ |
|
43 HeapValue callee; |
|
44 |
|
45 /* The script for the function containing this arguments object. */ |
|
46 JSScript *script; |
|
47 |
|
48 /* |
|
49 * Pointer to an array of bits indicating, for every argument in 'slots', |
|
50 * whether the element has been deleted. See isElementDeleted comment. |
|
51 */ |
|
52 size_t *deletedBits; |
|
53 |
|
54 /* |
|
55 * This array holds either the current argument value or the magic |
|
56 * forwarding value. The latter means that the function has both a |
|
57 * CallObject and an ArgumentsObject AND the particular formal variable is |
|
58 * aliased by the CallObject. In such cases, the CallObject holds the |
|
59 * canonical value so any element access to the arguments object should load |
|
60 * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT). |
|
61 */ |
|
62 HeapValue args[1]; |
|
63 |
|
64 /* For jit use: */ |
|
65 static ptrdiff_t offsetOfArgs() { return offsetof(ArgumentsData, args); } |
|
66 }; |
|
67 |
|
68 // Maximum supported value of arguments.length. This bounds the maximum |
|
69 // number of arguments that can be supplied to Function.prototype.apply. |
|
70 // This value also bounds the number of elements parsed in an array |
|
71 // initialiser. |
|
72 static const unsigned ARGS_LENGTH_MAX = 500 * 1000; |
|
73 |
|
74 /* |
|
75 * ArgumentsObject instances represent |arguments| objects created to store |
|
76 * function arguments when a function is called. It's expensive to create such |
|
77 * objects if they're never used, so they're only created when they are |
|
78 * potentially used. |
|
79 * |
|
80 * Arguments objects are complicated because, for non-strict mode code, they |
|
81 * must alias any named arguments which were provided to the function. Gnarly |
|
82 * example: |
|
83 * |
|
84 * function f(a, b, c, d) |
|
85 * { |
|
86 * arguments[0] = "seta"; |
|
87 * assertEq(a, "seta"); |
|
88 * b = "setb"; |
|
89 * assertEq(arguments[1], "setb"); |
|
90 * c = "setc"; |
|
91 * assertEq(arguments[2], undefined); |
|
92 * arguments[3] = "setd"; |
|
93 * assertEq(d, undefined); |
|
94 * } |
|
95 * f("arga", "argb"); |
|
96 * |
|
97 * ES5's strict mode behaves more sanely, and named arguments don't alias |
|
98 * elements of an arguments object. |
|
99 * |
|
100 * ArgumentsObject instances use the following reserved slots: |
|
101 * |
|
102 * INITIAL_LENGTH_SLOT |
|
103 * Stores the initial value of arguments.length, plus a bit indicating |
|
104 * whether arguments.length has been modified. Use initialLength() and |
|
105 * hasOverriddenLength() to access these values. If arguments.length has |
|
106 * been modified, then the current value of arguments.length is stored in |
|
107 * another slot associated with a new property. |
|
108 * DATA_SLOT |
|
109 * Stores an ArgumentsData*, described above. |
|
110 */ |
|
111 class ArgumentsObject : public JSObject |
|
112 { |
|
113 protected: |
|
114 static const uint32_t INITIAL_LENGTH_SLOT = 0; |
|
115 static const uint32_t DATA_SLOT = 1; |
|
116 static const uint32_t MAYBE_CALL_SLOT = 2; |
|
117 |
|
118 public: |
|
119 static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1; |
|
120 static const uint32_t PACKED_BITS_COUNT = 1; |
|
121 |
|
122 protected: |
|
123 template <typename CopyArgs> |
|
124 static ArgumentsObject *create(JSContext *cx, HandleScript script, HandleFunction callee, |
|
125 unsigned numActuals, CopyArgs ©); |
|
126 |
|
127 ArgumentsData *data() const { |
|
128 return reinterpret_cast<ArgumentsData *>(getFixedSlot(DATA_SLOT).toPrivate()); |
|
129 } |
|
130 |
|
131 public: |
|
132 static const uint32_t RESERVED_SLOTS = 3; |
|
133 static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; |
|
134 |
|
135 /* Create an arguments object for a frame that is expecting them. */ |
|
136 static ArgumentsObject *createExpected(JSContext *cx, AbstractFramePtr frame); |
|
137 |
|
138 /* |
|
139 * Purposefully disconnect the returned arguments object from the frame |
|
140 * by always creating a new copy that does not alias formal parameters. |
|
141 * This allows function-local analysis to determine that formals are |
|
142 * not aliased and generally simplifies arguments objects. |
|
143 */ |
|
144 static ArgumentsObject *createUnexpected(JSContext *cx, ScriptFrameIter &iter); |
|
145 static ArgumentsObject *createUnexpected(JSContext *cx, AbstractFramePtr frame); |
|
146 #if defined(JS_ION) |
|
147 static ArgumentsObject *createForIon(JSContext *cx, jit::IonJSFrameLayout *frame, |
|
148 HandleObject scopeChain); |
|
149 #endif |
|
150 |
|
151 /* |
|
152 * Return the initial length of the arguments. This may differ from the |
|
153 * current value of arguments.length! |
|
154 */ |
|
155 uint32_t initialLength() const { |
|
156 uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >> PACKED_BITS_COUNT; |
|
157 JS_ASSERT(argc <= ARGS_LENGTH_MAX); |
|
158 return argc; |
|
159 } |
|
160 |
|
161 /* The script for the function containing this arguments object. */ |
|
162 JSScript *containingScript() const { |
|
163 return data()->script; |
|
164 } |
|
165 |
|
166 /* True iff arguments.length has been assigned or its attributes changed. */ |
|
167 bool hasOverriddenLength() const { |
|
168 const Value &v = getFixedSlot(INITIAL_LENGTH_SLOT); |
|
169 return v.toInt32() & LENGTH_OVERRIDDEN_BIT; |
|
170 } |
|
171 |
|
172 void markLengthOverridden() { |
|
173 uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT; |
|
174 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); |
|
175 } |
|
176 |
|
177 /* |
|
178 * Because the arguments object is a real object, its elements may be |
|
179 * deleted. This is implemented by setting a 'deleted' flag for the arg |
|
180 * which is read by argument object resolve and getter/setter hooks. |
|
181 * |
|
182 * NB: an element, once deleted, stays deleted. Thus: |
|
183 * |
|
184 * function f(x) { delete arguments[0]; arguments[0] = 42; return x } |
|
185 * assertEq(f(1), 1); |
|
186 * |
|
187 * This works because, once a property is deleted from an arguments object, |
|
188 * it gets regular properties with regular getters/setters that don't alias |
|
189 * ArgumentsData::slots. |
|
190 */ |
|
191 bool isElementDeleted(uint32_t i) const { |
|
192 JS_ASSERT(i < data()->numArgs); |
|
193 if (i >= initialLength()) |
|
194 return false; |
|
195 return IsBitArrayElementSet(data()->deletedBits, initialLength(), i); |
|
196 } |
|
197 |
|
198 bool isAnyElementDeleted() const { |
|
199 return IsAnyBitArrayElementSet(data()->deletedBits, initialLength()); |
|
200 } |
|
201 |
|
202 void markElementDeleted(uint32_t i) { |
|
203 SetBitArrayElement(data()->deletedBits, initialLength(), i); |
|
204 } |
|
205 |
|
206 /* |
|
207 * An ArgumentsObject serves two roles: |
|
208 * - a real object, accessed through regular object operations, e.g.., |
|
209 * JSObject::getElement corresponding to 'arguments[i]'; |
|
210 * - a VM-internal data structure, storing the value of arguments (formal |
|
211 * and actual) that are accessed directly by the VM when a reading the |
|
212 * value of a formal parameter. |
|
213 * There are two ways to access the ArgumentsData::args corresponding to |
|
214 * these two use cases: |
|
215 * - object access should use elements(i) which will take care of |
|
216 * forwarding when the value is the magic forwarding value; |
|
217 * - VM argument access should use arg(i) which will assert that the |
|
218 * value is not the magic forwarding value (since, if such forwarding was |
|
219 * needed, the frontend should have emitted JSOP_GETALIASEDVAR). |
|
220 */ |
|
221 const Value &element(uint32_t i) const; |
|
222 |
|
223 inline void setElement(JSContext *cx, uint32_t i, const Value &v); |
|
224 |
|
225 const Value &arg(unsigned i) const { |
|
226 JS_ASSERT(i < data()->numArgs); |
|
227 const Value &v = data()->args[i]; |
|
228 JS_ASSERT(!v.isMagic()); |
|
229 return v; |
|
230 } |
|
231 |
|
232 void setArg(unsigned i, const Value &v) { |
|
233 JS_ASSERT(i < data()->numArgs); |
|
234 HeapValue &lhs = data()->args[i]; |
|
235 JS_ASSERT(!lhs.isMagic()); |
|
236 lhs = v; |
|
237 } |
|
238 |
|
239 /* |
|
240 * Attempt to speedily and efficiently access the i-th element of this |
|
241 * arguments object. Return true if the element was speedily returned. |
|
242 * Return false if the element must be looked up more slowly using |
|
243 * getProperty or some similar method. The second overload copies the |
|
244 * elements [start, start + count) into the locations starting at 'vp'. |
|
245 * |
|
246 * NB: Returning false does not indicate error! |
|
247 */ |
|
248 bool maybeGetElement(uint32_t i, MutableHandleValue vp) { |
|
249 if (i >= initialLength() || isElementDeleted(i)) |
|
250 return false; |
|
251 vp.set(element(i)); |
|
252 return true; |
|
253 } |
|
254 |
|
255 inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value *vp); |
|
256 |
|
257 /* |
|
258 * Measures things hanging off this ArgumentsObject that are counted by the |
|
259 * |miscSize| argument in JSObject::sizeOfExcludingThis(). |
|
260 */ |
|
261 size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const { |
|
262 return mallocSizeOf(data()); |
|
263 } |
|
264 |
|
265 static void finalize(FreeOp *fop, JSObject *obj); |
|
266 static void trace(JSTracer *trc, JSObject *obj); |
|
267 |
|
268 /* For jit use: */ |
|
269 static size_t getDataSlotOffset() { |
|
270 return getFixedSlotOffset(DATA_SLOT); |
|
271 } |
|
272 static size_t getInitialLengthSlotOffset() { |
|
273 return getFixedSlotOffset(INITIAL_LENGTH_SLOT); |
|
274 } |
|
275 |
|
276 static void MaybeForwardToCallObject(AbstractFramePtr frame, JSObject *obj, ArgumentsData *data); |
|
277 #if defined(JS_ION) |
|
278 static void MaybeForwardToCallObject(jit::IonJSFrameLayout *frame, HandleObject callObj, |
|
279 JSObject *obj, ArgumentsData *data); |
|
280 #endif |
|
281 }; |
|
282 |
|
283 class NormalArgumentsObject : public ArgumentsObject |
|
284 { |
|
285 public: |
|
286 static const Class class_; |
|
287 |
|
288 /* |
|
289 * Stores arguments.callee, or MagicValue(JS_ARGS_HOLE) if the callee has |
|
290 * been cleared. |
|
291 */ |
|
292 const js::Value &callee() const { |
|
293 return data()->callee; |
|
294 } |
|
295 |
|
296 /* Clear the location storing arguments.callee's initial value. */ |
|
297 void clearCallee() { |
|
298 data()->callee.set(zone(), MagicValue(JS_OVERWRITTEN_CALLEE)); |
|
299 } |
|
300 }; |
|
301 |
|
302 class StrictArgumentsObject : public ArgumentsObject |
|
303 { |
|
304 public: |
|
305 static const Class class_; |
|
306 }; |
|
307 |
|
308 } // namespace js |
|
309 |
|
310 template<> |
|
311 inline bool |
|
312 JSObject::is<js::ArgumentsObject>() const |
|
313 { |
|
314 return is<js::NormalArgumentsObject>() || is<js::StrictArgumentsObject>(); |
|
315 } |
|
316 |
|
317 #endif /* vm_ArgumentsObject_h */ |