| |
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 #include "vm/ScopeObject-inl.h" |
| |
8 |
| |
9 #include "mozilla/PodOperations.h" |
| |
10 |
| |
11 #include "jscompartment.h" |
| |
12 #include "jsiter.h" |
| |
13 |
| |
14 #include "vm/ArgumentsObject.h" |
| |
15 #include "vm/GlobalObject.h" |
| |
16 #include "vm/ProxyObject.h" |
| |
17 #include "vm/Shape.h" |
| |
18 #include "vm/Xdr.h" |
| |
19 |
| |
20 #include "jsatominlines.h" |
| |
21 #include "jsobjinlines.h" |
| |
22 #include "jsscriptinlines.h" |
| |
23 |
| |
24 #include "vm/Stack-inl.h" |
| |
25 |
| |
26 using namespace js; |
| |
27 using namespace js::types; |
| |
28 |
| |
29 using mozilla::PodZero; |
| |
30 |
| |
31 typedef Rooted<ArgumentsObject *> RootedArgumentsObject; |
| |
32 typedef MutableHandle<ArgumentsObject *> MutableHandleArgumentsObject; |
| |
33 |
| |
34 /*****************************************************************************/ |
| |
35 |
| |
36 static JSObject * |
| |
37 InnermostStaticScope(JSScript *script, jsbytecode *pc) |
| |
38 { |
| |
39 JS_ASSERT(script->containsPC(pc)); |
| |
40 JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD); |
| |
41 |
| |
42 NestedScopeObject *scope = script->getStaticScope(pc); |
| |
43 if (scope) |
| |
44 return scope; |
| |
45 return script->functionNonDelazifying(); |
| |
46 } |
| |
47 |
| |
48 Shape * |
| |
49 js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc) |
| |
50 { |
| |
51 StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc)); |
| |
52 uint32_t hops = ScopeCoordinate(pc).hops(); |
| |
53 while (true) { |
| |
54 JS_ASSERT(!ssi.done()); |
| |
55 if (ssi.hasDynamicScopeObject()) { |
| |
56 if (!hops) |
| |
57 break; |
| |
58 hops--; |
| |
59 } |
| |
60 ssi++; |
| |
61 } |
| |
62 return ssi.scopeShape(); |
| |
63 } |
| |
64 |
| |
65 static const uint32_t SCOPE_COORDINATE_NAME_THRESHOLD = 20; |
| |
66 |
| |
67 void |
| |
68 ScopeCoordinateNameCache::purge() |
| |
69 { |
| |
70 shape = nullptr; |
| |
71 if (map.initialized()) |
| |
72 map.finish(); |
| |
73 } |
| |
74 |
| |
75 PropertyName * |
| |
76 js::ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc) |
| |
77 { |
| |
78 Shape *shape = ScopeCoordinateToStaticScopeShape(script, pc); |
| |
79 if (shape != cache.shape && shape->slot() >= SCOPE_COORDINATE_NAME_THRESHOLD) { |
| |
80 cache.purge(); |
| |
81 if (cache.map.init(shape->slot())) { |
| |
82 cache.shape = shape; |
| |
83 Shape::Range<NoGC> r(shape); |
| |
84 while (!r.empty()) { |
| |
85 if (!cache.map.putNew(r.front().slot(), r.front().propid())) { |
| |
86 cache.purge(); |
| |
87 break; |
| |
88 } |
| |
89 r.popFront(); |
| |
90 } |
| |
91 } |
| |
92 } |
| |
93 |
| |
94 jsid id; |
| |
95 ScopeCoordinate sc(pc); |
| |
96 if (shape == cache.shape) { |
| |
97 ScopeCoordinateNameCache::Map::Ptr p = cache.map.lookup(sc.slot()); |
| |
98 id = p->value(); |
| |
99 } else { |
| |
100 Shape::Range<NoGC> r(shape); |
| |
101 while (r.front().slot() != sc.slot()) |
| |
102 r.popFront(); |
| |
103 id = r.front().propidRaw(); |
| |
104 } |
| |
105 |
| |
106 /* Beware nameless destructuring formal. */ |
| |
107 if (!JSID_IS_ATOM(id)) |
| |
108 return script->runtimeFromAnyThread()->commonNames->empty; |
| |
109 return JSID_TO_ATOM(id)->asPropertyName(); |
| |
110 } |
| |
111 |
| |
112 JSScript * |
| |
113 js::ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc) |
| |
114 { |
| |
115 StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc)); |
| |
116 uint32_t hops = ScopeCoordinate(pc).hops(); |
| |
117 while (true) { |
| |
118 if (ssi.hasDynamicScopeObject()) { |
| |
119 if (!hops) |
| |
120 break; |
| |
121 hops--; |
| |
122 } |
| |
123 ssi++; |
| |
124 } |
| |
125 if (ssi.type() != StaticScopeIter<NoGC>::FUNCTION) |
| |
126 return nullptr; |
| |
127 return ssi.funScript(); |
| |
128 } |
| |
129 |
| |
130 /*****************************************************************************/ |
| |
131 |
| |
132 void |
| |
133 ScopeObject::setEnclosingScope(HandleObject obj) |
| |
134 { |
| |
135 JS_ASSERT_IF(obj->is<CallObject>() || obj->is<DeclEnvObject>() || obj->is<BlockObject>(), |
| |
136 obj->isDelegate()); |
| |
137 setFixedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*obj)); |
| |
138 } |
| |
139 |
| |
140 CallObject * |
| |
141 CallObject::create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots) |
| |
142 { |
| |
143 MOZ_ASSERT(!type->singleton(), |
| |
144 "passed a singleton type to create() (use createSingleton() " |
| |
145 "instead)"); |
| |
146 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| |
147 MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); |
| |
148 kind = gc::GetBackgroundAllocKind(kind); |
| |
149 |
| |
150 JSObject *obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, type, slots); |
| |
151 if (!obj) |
| |
152 return nullptr; |
| |
153 |
| |
154 return &obj->as<CallObject>(); |
| |
155 } |
| |
156 |
| |
157 CallObject * |
| |
158 CallObject::createSingleton(JSContext *cx, HandleShape shape, HeapSlot *slots) |
| |
159 { |
| |
160 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| |
161 MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); |
| |
162 kind = gc::GetBackgroundAllocKind(kind); |
| |
163 |
| |
164 RootedTypeObject type(cx, cx->getSingletonType(&class_, nullptr)); |
| |
165 if (!type) |
| |
166 return nullptr; |
| |
167 RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, type, slots)); |
| |
168 if (!obj) |
| |
169 return nullptr; |
| |
170 |
| |
171 MOZ_ASSERT(obj->hasSingletonType(), |
| |
172 "type created inline above must be a singleton"); |
| |
173 |
| |
174 return &obj->as<CallObject>(); |
| |
175 } |
| |
176 |
| |
177 /* |
| |
178 * Create a CallObject for a JSScript that is not initialized to any particular |
| |
179 * callsite. This object can either be initialized (with an enclosing scope and |
| |
180 * callee) or used as a template for jit compilation. |
| |
181 */ |
| |
182 CallObject * |
| |
183 CallObject::createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap) |
| |
184 { |
| |
185 RootedShape shape(cx, script->bindings.callObjShape()); |
| |
186 JS_ASSERT(shape->getObjectClass() == &class_); |
| |
187 |
| |
188 RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); |
| |
189 if (!type) |
| |
190 return nullptr; |
| |
191 |
| |
192 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| |
193 JS_ASSERT(CanBeFinalizedInBackground(kind, &class_)); |
| |
194 kind = gc::GetBackgroundAllocKind(kind); |
| |
195 |
| |
196 JSObject *obj = JSObject::create(cx, kind, heap, shape, type); |
| |
197 if (!obj) |
| |
198 return nullptr; |
| |
199 |
| |
200 return &obj->as<CallObject>(); |
| |
201 } |
| |
202 |
| |
203 /* |
| |
204 * Construct a call object for the given bindings. If this is a call object |
| |
205 * for a function invocation, callee should be the function being called. |
| |
206 * Otherwise it must be a call object for eval of strict mode code, and callee |
| |
207 * must be null. |
| |
208 */ |
| |
209 CallObject * |
| |
210 CallObject::create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee) |
| |
211 { |
| |
212 gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap; |
| |
213 CallObject *callobj = CallObject::createTemplateObject(cx, script, heap); |
| |
214 if (!callobj) |
| |
215 return nullptr; |
| |
216 |
| |
217 callobj->as<ScopeObject>().setEnclosingScope(enclosing); |
| |
218 callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee)); |
| |
219 |
| |
220 if (script->treatAsRunOnce()) { |
| |
221 Rooted<CallObject*> ncallobj(cx, callobj); |
| |
222 if (!JSObject::setSingletonType(cx, ncallobj)) |
| |
223 return nullptr; |
| |
224 return ncallobj; |
| |
225 } |
| |
226 |
| |
227 return callobj; |
| |
228 } |
| |
229 |
| |
230 CallObject * |
| |
231 CallObject::createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee) |
| |
232 { |
| |
233 RootedObject scopeChain(cx, enclosing); |
| |
234 JS_ASSERT(scopeChain); |
| |
235 |
| |
236 /* |
| |
237 * For a named function expression Call's parent points to an environment |
| |
238 * object holding function's name. |
| |
239 */ |
| |
240 if (callee->isNamedLambda()) { |
| |
241 scopeChain = DeclEnvObject::create(cx, scopeChain, callee); |
| |
242 if (!scopeChain) |
| |
243 return nullptr; |
| |
244 } |
| |
245 |
| |
246 RootedScript script(cx, callee->nonLazyScript()); |
| |
247 return create(cx, script, scopeChain, callee); |
| |
248 } |
| |
249 |
| |
250 CallObject * |
| |
251 CallObject::createForFunction(JSContext *cx, AbstractFramePtr frame) |
| |
252 { |
| |
253 JS_ASSERT(frame.isNonEvalFunctionFrame()); |
| |
254 assertSameCompartment(cx, frame); |
| |
255 |
| |
256 RootedObject scopeChain(cx, frame.scopeChain()); |
| |
257 RootedFunction callee(cx, frame.callee()); |
| |
258 |
| |
259 CallObject *callobj = createForFunction(cx, scopeChain, callee); |
| |
260 if (!callobj) |
| |
261 return nullptr; |
| |
262 |
| |
263 /* Copy in the closed-over formal arguments. */ |
| |
264 for (AliasedFormalIter i(frame.script()); i; i++) { |
| |
265 callobj->setAliasedVar(cx, i, i->name(), |
| |
266 frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); |
| |
267 } |
| |
268 |
| |
269 return callobj; |
| |
270 } |
| |
271 |
| |
272 CallObject * |
| |
273 CallObject::createForStrictEval(JSContext *cx, AbstractFramePtr frame) |
| |
274 { |
| |
275 JS_ASSERT(frame.isStrictEvalFrame()); |
| |
276 JS_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterFrame() == frame.asInterpreterFrame()); |
| |
277 JS_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterRegs().pc == frame.script()->code()); |
| |
278 |
| |
279 RootedFunction callee(cx); |
| |
280 RootedScript script(cx, frame.script()); |
| |
281 RootedObject scopeChain(cx, frame.scopeChain()); |
| |
282 return create(cx, script, scopeChain, callee); |
| |
283 } |
| |
284 |
| |
285 const Class CallObject::class_ = { |
| |
286 "Call", |
| |
287 JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS), |
| |
288 JS_PropertyStub, /* addProperty */ |
| |
289 JS_DeletePropertyStub, /* delProperty */ |
| |
290 JS_PropertyStub, /* getProperty */ |
| |
291 JS_StrictPropertyStub, /* setProperty */ |
| |
292 JS_EnumerateStub, |
| |
293 JS_ResolveStub, |
| |
294 nullptr /* convert: Leave it nullptr so we notice if calls ever escape */ |
| |
295 }; |
| |
296 |
| |
297 const Class DeclEnvObject::class_ = { |
| |
298 js_Object_str, |
| |
299 JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) | |
| |
300 JSCLASS_HAS_CACHED_PROTO(JSProto_Object), |
| |
301 JS_PropertyStub, /* addProperty */ |
| |
302 JS_DeletePropertyStub, /* delProperty */ |
| |
303 JS_PropertyStub, /* getProperty */ |
| |
304 JS_StrictPropertyStub, /* setProperty */ |
| |
305 JS_EnumerateStub, |
| |
306 JS_ResolveStub, |
| |
307 JS_ConvertStub |
| |
308 }; |
| |
309 |
| |
310 /* |
| |
311 * Create a DeclEnvObject for a JSScript that is not initialized to any |
| |
312 * particular callsite. This object can either be initialized (with an enclosing |
| |
313 * scope and callee) or used as a template for jit compilation. |
| |
314 */ |
| |
315 DeclEnvObject * |
| |
316 DeclEnvObject::createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap) |
| |
317 { |
| |
318 JS_ASSERT(IsNurseryAllocable(FINALIZE_KIND)); |
| |
319 |
| |
320 RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); |
| |
321 if (!type) |
| |
322 return nullptr; |
| |
323 |
| |
324 RootedShape emptyDeclEnvShape(cx); |
| |
325 emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &class_, nullptr, |
| |
326 cx->global(), nullptr, FINALIZE_KIND, |
| |
327 BaseShape::DELEGATE); |
| |
328 if (!emptyDeclEnvShape) |
| |
329 return nullptr; |
| |
330 |
| |
331 RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, heap, emptyDeclEnvShape, type)); |
| |
332 if (!obj) |
| |
333 return nullptr; |
| |
334 |
| |
335 // Assign a fixed slot to a property with the same name as the lambda. |
| |
336 Rooted<jsid> id(cx, AtomToId(fun->atom())); |
| |
337 const Class *clasp = obj->getClass(); |
| |
338 unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY; |
| |
339 if (!JSObject::putProperty<SequentialExecution>(cx, obj, id, clasp->getProperty, |
| |
340 clasp->setProperty, lambdaSlot(), attrs, 0)) { |
| |
341 return nullptr; |
| |
342 } |
| |
343 |
| |
344 JS_ASSERT(!obj->hasDynamicSlots()); |
| |
345 return &obj->as<DeclEnvObject>(); |
| |
346 } |
| |
347 |
| |
348 DeclEnvObject * |
| |
349 DeclEnvObject::create(JSContext *cx, HandleObject enclosing, HandleFunction callee) |
| |
350 { |
| |
351 RootedObject obj(cx, createTemplateObject(cx, callee, gc::DefaultHeap)); |
| |
352 if (!obj) |
| |
353 return nullptr; |
| |
354 |
| |
355 obj->as<ScopeObject>().setEnclosingScope(enclosing); |
| |
356 obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee)); |
| |
357 return &obj->as<DeclEnvObject>(); |
| |
358 } |
| |
359 |
| |
360 template<XDRMode mode> |
| |
361 bool |
| |
362 js::XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope, StaticWithObject **objp) |
| |
363 { |
| |
364 if (mode == XDR_DECODE) { |
| |
365 JSContext *cx = xdr->cx(); |
| |
366 Rooted<StaticWithObject*> obj(cx, StaticWithObject::create(cx)); |
| |
367 if (!obj) |
| |
368 return false; |
| |
369 obj->initEnclosingNestedScope(enclosingScope); |
| |
370 *objp = obj; |
| |
371 } |
| |
372 // For encoding, there is nothing to do. The only information that is |
| |
373 // encoded by a StaticWithObject is its presence on the scope chain, and the |
| |
374 // script XDR handler already takes care of that. |
| |
375 |
| |
376 return true; |
| |
377 } |
| |
378 |
| |
379 template bool |
| |
380 js::XDRStaticWithObject(XDRState<XDR_ENCODE> *, HandleObject, StaticWithObject **); |
| |
381 |
| |
382 template bool |
| |
383 js::XDRStaticWithObject(XDRState<XDR_DECODE> *, HandleObject, StaticWithObject **); |
| |
384 |
| |
385 StaticWithObject * |
| |
386 StaticWithObject::create(ExclusiveContext *cx) |
| |
387 { |
| |
388 RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); |
| |
389 if (!type) |
| |
390 return nullptr; |
| |
391 |
| |
392 RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr), |
| |
393 nullptr, nullptr, FINALIZE_KIND)); |
| |
394 if (!shape) |
| |
395 return nullptr; |
| |
396 |
| |
397 RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, type)); |
| |
398 if (!obj) |
| |
399 return nullptr; |
| |
400 |
| |
401 return &obj->as<StaticWithObject>(); |
| |
402 } |
| |
403 |
| |
404 static JSObject * |
| |
405 CloneStaticWithObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticWithObject*> srcWith) |
| |
406 { |
| |
407 Rooted<StaticWithObject*> clone(cx, StaticWithObject::create(cx)); |
| |
408 if (!clone) |
| |
409 return nullptr; |
| |
410 |
| |
411 clone->initEnclosingNestedScope(enclosingScope); |
| |
412 |
| |
413 return clone; |
| |
414 } |
| |
415 |
| |
416 DynamicWithObject * |
| |
417 DynamicWithObject::create(JSContext *cx, HandleObject object, HandleObject enclosing, |
| |
418 HandleObject staticWith) |
| |
419 { |
| |
420 JS_ASSERT(staticWith->is<StaticWithObject>()); |
| |
421 RootedTypeObject type(cx, cx->getNewType(&class_, staticWith.get())); |
| |
422 if (!type) |
| |
423 return nullptr; |
| |
424 |
| |
425 RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(staticWith), |
| |
426 &enclosing->global(), nullptr, |
| |
427 FINALIZE_KIND)); |
| |
428 if (!shape) |
| |
429 return nullptr; |
| |
430 |
| |
431 RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, type)); |
| |
432 if (!obj) |
| |
433 return nullptr; |
| |
434 |
| |
435 JSObject *thisp = JSObject::thisObject(cx, object); |
| |
436 if (!thisp) |
| |
437 return nullptr; |
| |
438 |
| |
439 obj->as<ScopeObject>().setEnclosingScope(enclosing); |
| |
440 obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object)); |
| |
441 obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp)); |
| |
442 |
| |
443 return &obj->as<DynamicWithObject>(); |
| |
444 } |
| |
445 |
| |
446 static bool |
| |
447 with_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| |
448 MutableHandleObject objp, MutableHandleShape propp) |
| |
449 { |
| |
450 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
451 return JSObject::lookupGeneric(cx, actual, id, objp, propp); |
| |
452 } |
| |
453 |
| |
454 static bool |
| |
455 with_LookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| |
456 MutableHandleObject objp, MutableHandleShape propp) |
| |
457 { |
| |
458 Rooted<jsid> id(cx, NameToId(name)); |
| |
459 return with_LookupGeneric(cx, obj, id, objp, propp); |
| |
460 } |
| |
461 |
| |
462 static bool |
| |
463 with_LookupElement(JSContext *cx, HandleObject obj, uint32_t index, |
| |
464 MutableHandleObject objp, MutableHandleShape propp) |
| |
465 { |
| |
466 RootedId id(cx); |
| |
467 if (!IndexToId(cx, index, &id)) |
| |
468 return false; |
| |
469 return with_LookupGeneric(cx, obj, id, objp, propp); |
| |
470 } |
| |
471 |
| |
472 static bool |
| |
473 with_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, |
| |
474 MutableHandleValue vp) |
| |
475 { |
| |
476 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
477 return JSObject::getGeneric(cx, actual, actual, id, vp); |
| |
478 } |
| |
479 |
| |
480 static bool |
| |
481 with_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name, |
| |
482 MutableHandleValue vp) |
| |
483 { |
| |
484 RootedId id(cx, NameToId(name)); |
| |
485 return with_GetGeneric(cx, obj, receiver, id, vp); |
| |
486 } |
| |
487 |
| |
488 static bool |
| |
489 with_GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, |
| |
490 MutableHandleValue vp) |
| |
491 { |
| |
492 RootedId id(cx); |
| |
493 if (!IndexToId(cx, index, &id)) |
| |
494 return false; |
| |
495 return with_GetGeneric(cx, obj, receiver, id, vp); |
| |
496 } |
| |
497 |
| |
498 static bool |
| |
499 with_SetGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| |
500 MutableHandleValue vp, bool strict) |
| |
501 { |
| |
502 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
503 return JSObject::setGeneric(cx, actual, actual, id, vp, strict); |
| |
504 } |
| |
505 |
| |
506 static bool |
| |
507 with_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| |
508 MutableHandleValue vp, bool strict) |
| |
509 { |
| |
510 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
511 return JSObject::setProperty(cx, actual, actual, name, vp, strict); |
| |
512 } |
| |
513 |
| |
514 static bool |
| |
515 with_SetElement(JSContext *cx, HandleObject obj, uint32_t index, |
| |
516 MutableHandleValue vp, bool strict) |
| |
517 { |
| |
518 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
519 return JSObject::setElement(cx, actual, actual, index, vp, strict); |
| |
520 } |
| |
521 |
| |
522 static bool |
| |
523 with_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| |
524 { |
| |
525 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
526 return JSObject::getGenericAttributes(cx, actual, id, attrsp); |
| |
527 } |
| |
528 |
| |
529 static bool |
| |
530 with_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| |
531 { |
| |
532 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
533 return JSObject::setGenericAttributes(cx, actual, id, attrsp); |
| |
534 } |
| |
535 |
| |
536 static bool |
| |
537 with_DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| |
538 bool *succeeded) |
| |
539 { |
| |
540 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
541 return JSObject::deleteProperty(cx, actual, name, succeeded); |
| |
542 } |
| |
543 |
| |
544 static bool |
| |
545 with_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, |
| |
546 bool *succeeded) |
| |
547 { |
| |
548 RootedObject actual(cx, &obj->as<DynamicWithObject>().object()); |
| |
549 return JSObject::deleteElement(cx, actual, index, succeeded); |
| |
550 } |
| |
551 |
| |
552 static JSObject * |
| |
553 with_ThisObject(JSContext *cx, HandleObject obj) |
| |
554 { |
| |
555 return &obj->as<DynamicWithObject>().withThis(); |
| |
556 } |
| |
557 |
| |
558 const Class StaticWithObject::class_ = { |
| |
559 "WithTemplate", |
| |
560 JSCLASS_IMPLEMENTS_BARRIERS | |
| |
561 JSCLASS_HAS_RESERVED_SLOTS(StaticWithObject::RESERVED_SLOTS) | |
| |
562 JSCLASS_IS_ANONYMOUS, |
| |
563 JS_PropertyStub, /* addProperty */ |
| |
564 JS_DeletePropertyStub, /* delProperty */ |
| |
565 JS_PropertyStub, /* getProperty */ |
| |
566 JS_StrictPropertyStub, /* setProperty */ |
| |
567 JS_EnumerateStub, |
| |
568 JS_ResolveStub, |
| |
569 JS_ConvertStub |
| |
570 }; |
| |
571 |
| |
572 const Class DynamicWithObject::class_ = { |
| |
573 "With", |
| |
574 JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) | |
| |
575 JSCLASS_IS_ANONYMOUS, |
| |
576 JS_PropertyStub, /* addProperty */ |
| |
577 JS_DeletePropertyStub, /* delProperty */ |
| |
578 JS_PropertyStub, /* getProperty */ |
| |
579 JS_StrictPropertyStub, /* setProperty */ |
| |
580 JS_EnumerateStub, |
| |
581 JS_ResolveStub, |
| |
582 JS_ConvertStub, |
| |
583 nullptr, /* finalize */ |
| |
584 nullptr, /* call */ |
| |
585 nullptr, /* hasInstance */ |
| |
586 nullptr, /* construct */ |
| |
587 nullptr, /* trace */ |
| |
588 JS_NULL_CLASS_SPEC, |
| |
589 JS_NULL_CLASS_EXT, |
| |
590 { |
| |
591 with_LookupGeneric, |
| |
592 with_LookupProperty, |
| |
593 with_LookupElement, |
| |
594 nullptr, /* defineGeneric */ |
| |
595 nullptr, /* defineProperty */ |
| |
596 nullptr, /* defineElement */ |
| |
597 with_GetGeneric, |
| |
598 with_GetProperty, |
| |
599 with_GetElement, |
| |
600 with_SetGeneric, |
| |
601 with_SetProperty, |
| |
602 with_SetElement, |
| |
603 with_GetGenericAttributes, |
| |
604 with_SetGenericAttributes, |
| |
605 with_DeleteProperty, |
| |
606 with_DeleteElement, |
| |
607 nullptr, nullptr, /* watch/unwatch */ |
| |
608 nullptr, /* slice */ |
| |
609 nullptr, /* enumerate (native enumeration of target doesn't work) */ |
| |
610 with_ThisObject, |
| |
611 } |
| |
612 }; |
| |
613 |
| |
614 /*****************************************************************************/ |
| |
615 |
| |
616 ClonedBlockObject * |
| |
617 ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame) |
| |
618 { |
| |
619 assertSameCompartment(cx, frame); |
| |
620 JS_ASSERT(block->getClass() == &BlockObject::class_); |
| |
621 |
| |
622 RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, block.get())); |
| |
623 if (!type) |
| |
624 return nullptr; |
| |
625 |
| |
626 RootedShape shape(cx, block->lastProperty()); |
| |
627 |
| |
628 RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, type)); |
| |
629 if (!obj) |
| |
630 return nullptr; |
| |
631 |
| |
632 /* Set the parent if necessary, as for call objects. */ |
| |
633 if (&frame.scopeChain()->global() != obj->getParent()) { |
| |
634 JS_ASSERT(obj->getParent() == nullptr); |
| |
635 Rooted<GlobalObject*> global(cx, &frame.scopeChain()->global()); |
| |
636 if (!JSObject::setParent(cx, obj, global)) |
| |
637 return nullptr; |
| |
638 } |
| |
639 |
| |
640 JS_ASSERT(!obj->inDictionaryMode()); |
| |
641 JS_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS); |
| |
642 |
| |
643 obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); |
| |
644 |
| |
645 /* |
| |
646 * Copy in the closed-over locals. Closed-over locals don't need |
| |
647 * any fixup since the initial value is 'undefined'. |
| |
648 */ |
| |
649 unsigned nvars = block->numVariables(); |
| |
650 for (unsigned i = 0; i < nvars; ++i) { |
| |
651 if (block->isAliased(i)) { |
| |
652 Value &val = frame.unaliasedLocal(block->blockIndexToLocalIndex(i)); |
| |
653 obj->as<ClonedBlockObject>().setVar(i, val); |
| |
654 } |
| |
655 } |
| |
656 |
| |
657 JS_ASSERT(obj->isDelegate()); |
| |
658 |
| |
659 return &obj->as<ClonedBlockObject>(); |
| |
660 } |
| |
661 |
| |
662 void |
| |
663 ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) |
| |
664 { |
| |
665 StaticBlockObject &block = staticBlock(); |
| |
666 for (unsigned i = 0; i < numVariables(); ++i) { |
| |
667 if (!block.isAliased(i)) { |
| |
668 Value &val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i)); |
| |
669 setVar(i, val, DONT_CHECK_ALIASING); |
| |
670 } |
| |
671 } |
| |
672 } |
| |
673 |
| |
674 StaticBlockObject * |
| |
675 StaticBlockObject::create(ExclusiveContext *cx) |
| |
676 { |
| |
677 RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, nullptr)); |
| |
678 if (!type) |
| |
679 return nullptr; |
| |
680 |
| |
681 RootedShape emptyBlockShape(cx); |
| |
682 emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockObject::class_, nullptr, nullptr, |
| |
683 nullptr, FINALIZE_KIND, BaseShape::DELEGATE); |
| |
684 if (!emptyBlockShape) |
| |
685 return nullptr; |
| |
686 |
| |
687 JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, type); |
| |
688 if (!obj) |
| |
689 return nullptr; |
| |
690 |
| |
691 return &obj->as<StaticBlockObject>(); |
| |
692 } |
| |
693 |
| |
694 /* static */ Shape * |
| |
695 StaticBlockObject::addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id, |
| |
696 unsigned index, bool *redeclared) |
| |
697 { |
| |
698 JS_ASSERT(JSID_IS_ATOM(id)); |
| |
699 JS_ASSERT(index < LOCAL_INDEX_LIMIT); |
| |
700 |
| |
701 *redeclared = false; |
| |
702 |
| |
703 /* Inline JSObject::addProperty in order to trap the redefinition case. */ |
| |
704 Shape **spp; |
| |
705 if (Shape::search(cx, block->lastProperty(), id, &spp, true)) { |
| |
706 *redeclared = true; |
| |
707 return nullptr; |
| |
708 } |
| |
709 |
| |
710 /* |
| |
711 * Don't convert this object to dictionary mode so that we can clone the |
| |
712 * block's shape later. |
| |
713 */ |
| |
714 uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index; |
| |
715 return JSObject::addPropertyInternal<SequentialExecution>(cx, block, id, |
| |
716 /* getter = */ nullptr, |
| |
717 /* setter = */ nullptr, |
| |
718 slot, |
| |
719 JSPROP_ENUMERATE | JSPROP_PERMANENT, |
| |
720 /* attrs = */ 0, |
| |
721 spp, |
| |
722 /* allowDictionary = */ false); |
| |
723 } |
| |
724 |
| |
725 const Class BlockObject::class_ = { |
| |
726 "Block", |
| |
727 JSCLASS_IMPLEMENTS_BARRIERS | |
| |
728 JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) | |
| |
729 JSCLASS_IS_ANONYMOUS, |
| |
730 JS_PropertyStub, /* addProperty */ |
| |
731 JS_DeletePropertyStub, /* delProperty */ |
| |
732 JS_PropertyStub, /* getProperty */ |
| |
733 JS_StrictPropertyStub, /* setProperty */ |
| |
734 JS_EnumerateStub, |
| |
735 JS_ResolveStub, |
| |
736 JS_ConvertStub |
| |
737 }; |
| |
738 |
| |
739 template<XDRMode mode> |
| |
740 bool |
| |
741 js::XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, |
| |
742 StaticBlockObject **objp) |
| |
743 { |
| |
744 /* NB: Keep this in sync with CloneStaticBlockObject. */ |
| |
745 |
| |
746 JSContext *cx = xdr->cx(); |
| |
747 |
| |
748 Rooted<StaticBlockObject*> obj(cx); |
| |
749 uint32_t count = 0, offset = 0; |
| |
750 |
| |
751 if (mode == XDR_ENCODE) { |
| |
752 obj = *objp; |
| |
753 count = obj->numVariables(); |
| |
754 offset = obj->localOffset(); |
| |
755 } |
| |
756 |
| |
757 if (mode == XDR_DECODE) { |
| |
758 obj = StaticBlockObject::create(cx); |
| |
759 if (!obj) |
| |
760 return false; |
| |
761 obj->initEnclosingNestedScope(enclosingScope); |
| |
762 *objp = obj; |
| |
763 } |
| |
764 |
| |
765 if (!xdr->codeUint32(&count)) |
| |
766 return false; |
| |
767 if (!xdr->codeUint32(&offset)) |
| |
768 return false; |
| |
769 |
| |
770 /* |
| |
771 * XDR the block object's properties. We know that there are 'count' |
| |
772 * properties to XDR, stored as id/aliased pairs. (The empty string as |
| |
773 * id indicates an int id.) |
| |
774 */ |
| |
775 if (mode == XDR_DECODE) { |
| |
776 obj->setLocalOffset(offset); |
| |
777 |
| |
778 for (unsigned i = 0; i < count; i++) { |
| |
779 RootedAtom atom(cx); |
| |
780 if (!XDRAtom(xdr, &atom)) |
| |
781 return false; |
| |
782 |
| |
783 RootedId id(cx, atom != cx->runtime()->emptyString |
| |
784 ? AtomToId(atom) |
| |
785 : INT_TO_JSID(i)); |
| |
786 |
| |
787 bool redeclared; |
| |
788 if (!StaticBlockObject::addVar(cx, obj, id, i, &redeclared)) { |
| |
789 JS_ASSERT(!redeclared); |
| |
790 return false; |
| |
791 } |
| |
792 |
| |
793 uint32_t aliased; |
| |
794 if (!xdr->codeUint32(&aliased)) |
| |
795 return false; |
| |
796 |
| |
797 JS_ASSERT(aliased == 0 || aliased == 1); |
| |
798 obj->setAliased(i, !!aliased); |
| |
799 } |
| |
800 } else { |
| |
801 AutoShapeVector shapes(cx); |
| |
802 if (!shapes.growBy(count)) |
| |
803 return false; |
| |
804 |
| |
805 for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) |
| |
806 shapes[obj->shapeToIndex(r.front())] = &r.front(); |
| |
807 |
| |
808 RootedShape shape(cx); |
| |
809 RootedId propid(cx); |
| |
810 RootedAtom atom(cx); |
| |
811 for (unsigned i = 0; i < count; i++) { |
| |
812 shape = shapes[i]; |
| |
813 JS_ASSERT(shape->hasDefaultGetter()); |
| |
814 JS_ASSERT(obj->shapeToIndex(*shape) == i); |
| |
815 |
| |
816 propid = shape->propid(); |
| |
817 JS_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid)); |
| |
818 |
| |
819 atom = JSID_IS_ATOM(propid) |
| |
820 ? JSID_TO_ATOM(propid) |
| |
821 : cx->runtime()->emptyString; |
| |
822 if (!XDRAtom(xdr, &atom)) |
| |
823 return false; |
| |
824 |
| |
825 uint32_t aliased = obj->isAliased(i); |
| |
826 if (!xdr->codeUint32(&aliased)) |
| |
827 return false; |
| |
828 } |
| |
829 } |
| |
830 return true; |
| |
831 } |
| |
832 |
| |
833 template bool |
| |
834 js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, StaticBlockObject **); |
| |
835 |
| |
836 template bool |
| |
837 js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, StaticBlockObject **); |
| |
838 |
| |
839 static JSObject * |
| |
840 CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock) |
| |
841 { |
| |
842 /* NB: Keep this in sync with XDRStaticBlockObject. */ |
| |
843 |
| |
844 Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx)); |
| |
845 if (!clone) |
| |
846 return nullptr; |
| |
847 |
| |
848 clone->initEnclosingNestedScope(enclosingScope); |
| |
849 clone->setLocalOffset(srcBlock->localOffset()); |
| |
850 |
| |
851 /* Shape::Range is reverse order, so build a list in forward order. */ |
| |
852 AutoShapeVector shapes(cx); |
| |
853 if (!shapes.growBy(srcBlock->numVariables())) |
| |
854 return nullptr; |
| |
855 |
| |
856 for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront()) |
| |
857 shapes[srcBlock->shapeToIndex(r.front())] = &r.front(); |
| |
858 |
| |
859 for (Shape **p = shapes.begin(); p != shapes.end(); ++p) { |
| |
860 RootedId id(cx, (*p)->propid()); |
| |
861 unsigned i = srcBlock->shapeToIndex(**p); |
| |
862 |
| |
863 bool redeclared; |
| |
864 if (!StaticBlockObject::addVar(cx, clone, id, i, &redeclared)) { |
| |
865 JS_ASSERT(!redeclared); |
| |
866 return nullptr; |
| |
867 } |
| |
868 |
| |
869 clone->setAliased(i, srcBlock->isAliased(i)); |
| |
870 } |
| |
871 |
| |
872 return clone; |
| |
873 } |
| |
874 |
| |
875 JSObject * |
| |
876 js::CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> srcBlock) |
| |
877 { |
| |
878 if (srcBlock->is<StaticBlockObject>()) { |
| |
879 Rooted<StaticBlockObject *> blockObj(cx, &srcBlock->as<StaticBlockObject>()); |
| |
880 return CloneStaticBlockObject(cx, enclosingScope, blockObj); |
| |
881 } else { |
| |
882 Rooted<StaticWithObject *> withObj(cx, &srcBlock->as<StaticWithObject>()); |
| |
883 return CloneStaticWithObject(cx, enclosingScope, withObj); |
| |
884 } |
| |
885 } |
| |
886 |
| |
887 /*****************************************************************************/ |
| |
888 |
| |
889 // Any name atom for a function which will be added as a DeclEnv object to the |
| |
890 // scope chain above call objects for fun. |
| |
891 static inline JSAtom * |
| |
892 CallObjectLambdaName(JSFunction &fun) |
| |
893 { |
| |
894 return fun.isNamedLambda() ? fun.atom() : nullptr; |
| |
895 } |
| |
896 |
| |
897 ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx |
| |
898 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| |
899 : cx(cx), |
| |
900 frame_(si.frame_), |
| |
901 cur_(cx, si.cur_), |
| |
902 staticScope_(cx, si.staticScope_), |
| |
903 type_(si.type_), |
| |
904 hasScopeObject_(si.hasScopeObject_) |
| |
905 { |
| |
906 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| |
907 } |
| |
908 |
| |
909 ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx |
| |
910 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| |
911 : cx(cx), |
| |
912 frame_(NullFramePtr()), |
| |
913 cur_(cx, &enclosingScope), |
| |
914 staticScope_(cx, nullptr), |
| |
915 type_(Type(-1)) |
| |
916 { |
| |
917 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| |
918 } |
| |
919 |
| |
920 ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx |
| |
921 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| |
922 : cx(cx), |
| |
923 frame_(frame), |
| |
924 cur_(cx, frame.scopeChain()), |
| |
925 staticScope_(cx, frame.script()->getStaticScope(pc)) |
| |
926 { |
| |
927 assertSameCompartment(cx, frame); |
| |
928 settle(); |
| |
929 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| |
930 } |
| |
931 |
| |
932 ScopeIter::ScopeIter(const ScopeIterVal &val, JSContext *cx |
| |
933 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| |
934 : cx(cx), |
| |
935 frame_(val.frame_), |
| |
936 cur_(cx, val.cur_), |
| |
937 staticScope_(cx, val.staticScope_), |
| |
938 type_(val.type_), |
| |
939 hasScopeObject_(val.hasScopeObject_) |
| |
940 { |
| |
941 assertSameCompartment(cx, val.frame_); |
| |
942 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| |
943 } |
| |
944 |
| |
945 ScopeObject & |
| |
946 ScopeIter::scope() const |
| |
947 { |
| |
948 JS_ASSERT(hasScopeObject()); |
| |
949 return cur_->as<ScopeObject>(); |
| |
950 } |
| |
951 |
| |
952 ScopeIter & |
| |
953 ScopeIter::operator++() |
| |
954 { |
| |
955 JS_ASSERT(!done()); |
| |
956 switch (type_) { |
| |
957 case Call: |
| |
958 if (hasScopeObject_) { |
| |
959 cur_ = &cur_->as<CallObject>().enclosingScope(); |
| |
960 if (CallObjectLambdaName(*frame_.fun())) |
| |
961 cur_ = &cur_->as<DeclEnvObject>().enclosingScope(); |
| |
962 } |
| |
963 frame_ = NullFramePtr(); |
| |
964 break; |
| |
965 case Block: |
| |
966 JS_ASSERT(staticScope_ && staticScope_->is<StaticBlockObject>()); |
| |
967 staticScope_ = staticScope_->as<StaticBlockObject>().enclosingNestedScope(); |
| |
968 if (hasScopeObject_) |
| |
969 cur_ = &cur_->as<ClonedBlockObject>().enclosingScope(); |
| |
970 settle(); |
| |
971 break; |
| |
972 case With: |
| |
973 JS_ASSERT(staticScope_ && staticScope_->is<StaticWithObject>()); |
| |
974 JS_ASSERT(hasScopeObject_); |
| |
975 staticScope_ = staticScope_->as<StaticWithObject>().enclosingNestedScope(); |
| |
976 cur_ = &cur_->as<DynamicWithObject>().enclosingScope(); |
| |
977 settle(); |
| |
978 break; |
| |
979 case StrictEvalScope: |
| |
980 if (hasScopeObject_) |
| |
981 cur_ = &cur_->as<CallObject>().enclosingScope(); |
| |
982 frame_ = NullFramePtr(); |
| |
983 break; |
| |
984 } |
| |
985 return *this; |
| |
986 } |
| |
987 |
| |
988 void |
| |
989 ScopeIter::settle() |
| |
990 { |
| |
991 /* |
| |
992 * Given an iterator state (cur_, staticScope_), figure out which (potentially |
| |
993 * optimized) scope the iterator should report. Thus, the result is a pair |
| |
994 * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the |
| |
995 * scope object has been optimized away and does not exist on the scope |
| |
996 * chain. Beware: while ScopeIter iterates over the scopes of a single |
| |
997 * frame, the scope chain (pointed to by cur_) continues into the scopes of |
| |
998 * enclosing frames. Thus, it is important not to look at cur_ until it is |
| |
999 * certain that cur_ points to a scope object in the current frame. In |
| |
1000 * particular, there are three tricky corner cases: |
| |
1001 * - non-heavyweight functions; |
| |
1002 * - non-strict direct eval. |
| |
1003 * - heavyweight functions observed before the prologue has finished; |
| |
1004 * In all cases, cur_ can already be pointing into an enclosing frame's |
| |
1005 * scope chain. Furthermore, in the first two cases: even if cur_ points |
| |
1006 * into an enclosing frame's scope chain, the current frame may still have |
| |
1007 * uncloned blocks. In the last case, since we haven't entered the |
| |
1008 * function, we simply return a ScopeIter where done() == true. |
| |
1009 * |
| |
1010 * Note: DebugScopeObject falls nicely into this plan: since they are only |
| |
1011 * ever introduced as the *enclosing* scope of a frame, they should never |
| |
1012 * show up in scope iteration and fall into the final non-scope case. |
| |
1013 */ |
| |
1014 if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) { |
| |
1015 if (staticScope_) { |
| |
1016 // If staticScope_ were a StaticWithObject, the function would be |
| |
1017 // heavyweight. |
| |
1018 JS_ASSERT(staticScope_->is<StaticBlockObject>()); |
| |
1019 type_ = Block; |
| |
1020 hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone(); |
| |
1021 } else { |
| |
1022 type_ = Call; |
| |
1023 hasScopeObject_ = false; |
| |
1024 } |
| |
1025 } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) { |
| |
1026 if (staticScope_) { |
| |
1027 JS_ASSERT(staticScope_->is<StaticBlockObject>()); |
| |
1028 JS_ASSERT(!staticScope_->as<StaticBlockObject>().needsClone()); |
| |
1029 type_ = Block; |
| |
1030 hasScopeObject_ = false; |
| |
1031 } else { |
| |
1032 frame_ = NullFramePtr(); |
| |
1033 } |
| |
1034 } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) { |
| |
1035 JS_ASSERT(cur_ == frame_.fun()->environment()); |
| |
1036 frame_ = NullFramePtr(); |
| |
1037 } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) { |
| |
1038 JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx)); |
| |
1039 frame_ = NullFramePtr(); |
| |
1040 } else if (staticScope_) { |
| |
1041 if (staticScope_->is<StaticWithObject>()) { |
| |
1042 JS_ASSERT(cur_); |
| |
1043 JS_ASSERT(cur_->as<DynamicWithObject>().staticScope() == staticScope_); |
| |
1044 type_ = With; |
| |
1045 hasScopeObject_ = true; |
| |
1046 } else { |
| |
1047 type_ = Block; |
| |
1048 hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone(); |
| |
1049 JS_ASSERT_IF(hasScopeObject_, |
| |
1050 cur_->as<ClonedBlockObject>().staticBlock() == *staticScope_); |
| |
1051 } |
| |
1052 } else if (cur_->is<CallObject>()) { |
| |
1053 CallObject &callobj = cur_->as<CallObject>(); |
| |
1054 type_ = callobj.isForEval() ? StrictEvalScope : Call; |
| |
1055 hasScopeObject_ = true; |
| |
1056 JS_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script()); |
| |
1057 } else { |
| |
1058 JS_ASSERT(!cur_->is<ScopeObject>()); |
| |
1059 JS_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerFrame()); |
| |
1060 frame_ = NullFramePtr(); |
| |
1061 } |
| |
1062 } |
| |
1063 |
| |
1064 /* static */ HashNumber |
| |
1065 ScopeIterKey::hash(ScopeIterKey si) |
| |
1066 { |
| |
1067 /* hasScopeObject_ is determined by the other fields. */ |
| |
1068 return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_; |
| |
1069 } |
| |
1070 |
| |
1071 /* static */ bool |
| |
1072 ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2) |
| |
1073 { |
| |
1074 /* hasScopeObject_ is determined by the other fields. */ |
| |
1075 return si1.frame_ == si2.frame_ && |
| |
1076 (!si1.frame_ || |
| |
1077 (si1.cur_ == si2.cur_ && |
| |
1078 si1.staticScope_ == si2.staticScope_ && |
| |
1079 si1.type_ == si2.type_)); |
| |
1080 } |
| |
1081 |
| |
1082 // Live ScopeIter values may be added to DebugScopes::liveScopes, as |
| |
1083 // ScopeIterVal instances. They need to have write barriers when they are added |
| |
1084 // to the hash table, but no barriers when rehashing inside GC. It's a nasty |
| |
1085 // hack, but the important thing is that ScopeIterKey and ScopeIterVal need to |
| |
1086 // alias each other. |
| |
1087 void ScopeIterVal::staticAsserts() { |
| |
1088 static_assert(sizeof(ScopeIterVal) == sizeof(ScopeIterKey), |
| |
1089 "ScopeIterVal must be same size of ScopeIterKey"); |
| |
1090 static_assert(offsetof(ScopeIterVal, cur_) == offsetof(ScopeIterKey, cur_), |
| |
1091 "ScopeIterVal.cur_ must alias ScopeIterKey.cur_"); |
| |
1092 static_assert(offsetof(ScopeIterVal, staticScope_) == offsetof(ScopeIterKey, staticScope_), |
| |
1093 "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_"); |
| |
1094 } |
| |
1095 |
| |
1096 /*****************************************************************************/ |
| |
1097 |
| |
1098 namespace { |
| |
1099 |
| |
1100 /* |
| |
1101 * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a |
| |
1102 * custom handler (rather than trying to reuse js::Wrapper) gives us several |
| |
1103 * important abilities: |
| |
1104 * - We want to pass the ScopeObject as the receiver to forwarded scope |
| |
1105 * property ops on aliased variables so that Call/Block/With ops do not all |
| |
1106 * require a 'normalization' step. |
| |
1107 * - The debug scope proxy can directly manipulate the stack frame to allow |
| |
1108 * the debugger to read/write args/locals that were otherwise unaliased. |
| |
1109 * - The debug scope proxy can store unaliased variables after the stack frame |
| |
1110 * is popped so that they may still be read/written by the debugger. |
| |
1111 * - The engine has made certain assumptions about the possible reads/writes |
| |
1112 * in a scope. DebugScopeProxy allows us to prevent the debugger from |
| |
1113 * breaking those assumptions. |
| |
1114 * - The engine makes optimizations that are observable to the debugger. The |
| |
1115 * proxy can either hide these optimizations or make the situation more |
| |
1116 * clear to the debugger. An example is 'arguments'. |
| |
1117 */ |
| |
1118 class DebugScopeProxy : public BaseProxyHandler |
| |
1119 { |
| |
1120 enum Action { SET, GET }; |
| |
1121 |
| |
1122 enum AccessResult { |
| |
1123 ACCESS_UNALIASED, |
| |
1124 ACCESS_GENERIC, |
| |
1125 ACCESS_LOST |
| |
1126 }; |
| |
1127 |
| |
1128 /* |
| |
1129 * This function handles access to unaliased locals/formals. Since they are |
| |
1130 * unaliased, the values of these variables are not stored in the slots of |
| |
1131 * the normal Call/BlockObject scope objects and thus must be recovered |
| |
1132 * from somewhere else: |
| |
1133 * + if the invocation for which the scope was created is still executing, |
| |
1134 * there is a JS frame live on the stack holding the values; |
| |
1135 * + if the invocation for which the scope was created finished executing: |
| |
1136 * - and there was a DebugScopeObject associated with scope, then the |
| |
1137 * DebugScopes::onPop(Call|Block) handler copied out the unaliased |
| |
1138 * variables: |
| |
1139 * . for block scopes, the unaliased values were copied directly |
| |
1140 * into the block object, since there is a slot allocated for every |
| |
1141 * block binding, regardless of whether it is aliased; |
| |
1142 * . for function scopes, a dense array is created in onPopCall to hold |
| |
1143 * the unaliased values and attached to the DebugScopeObject; |
| |
1144 * - and there was not a DebugScopeObject yet associated with the |
| |
1145 * scope, then the unaliased values are lost and not recoverable. |
| |
1146 * |
| |
1147 * Callers should check accessResult for non-failure results: |
| |
1148 * - ACCESS_UNALIASED if the access was unaliased and completed |
| |
1149 * - ACCESS_GENERIC if the access was aliased or the property not found |
| |
1150 * - ACCESS_LOST if the value has been lost to the debugger |
| |
1151 */ |
| |
1152 bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope, |
| |
1153 Handle<ScopeObject*> scope, jsid id, Action action, |
| |
1154 MutableHandleValue vp, AccessResult *accessResult) |
| |
1155 { |
| |
1156 JS_ASSERT(&debugScope->scope() == scope); |
| |
1157 *accessResult = ACCESS_GENERIC; |
| |
1158 ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope); |
| |
1159 |
| |
1160 /* Handle unaliased formals, vars, and consts at function scope. */ |
| |
1161 if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| |
1162 CallObject &callobj = scope->as<CallObject>(); |
| |
1163 RootedScript script(cx, callobj.callee().nonLazyScript()); |
| |
1164 if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) |
| |
1165 return false; |
| |
1166 |
| |
1167 Bindings &bindings = script->bindings; |
| |
1168 BindingIter bi(script); |
| |
1169 while (bi && NameToId(bi->name()) != id) |
| |
1170 bi++; |
| |
1171 if (!bi) |
| |
1172 return true; |
| |
1173 |
| |
1174 if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { |
| |
1175 uint32_t i = bi.frameIndex(); |
| |
1176 if (script->varIsAliased(i)) |
| |
1177 return true; |
| |
1178 |
| |
1179 if (maybeLiveScope) { |
| |
1180 AbstractFramePtr frame = maybeLiveScope->frame(); |
| |
1181 if (action == GET) |
| |
1182 vp.set(frame.unaliasedVar(i)); |
| |
1183 else |
| |
1184 frame.unaliasedVar(i) = vp; |
| |
1185 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { |
| |
1186 if (action == GET) |
| |
1187 vp.set(snapshot->getDenseElement(bindings.numArgs() + i)); |
| |
1188 else |
| |
1189 snapshot->setDenseElement(bindings.numArgs() + i, vp); |
| |
1190 } else { |
| |
1191 /* The unaliased value has been lost to the debugger. */ |
| |
1192 if (action == GET) { |
| |
1193 *accessResult = ACCESS_LOST; |
| |
1194 return true; |
| |
1195 } |
| |
1196 } |
| |
1197 } else { |
| |
1198 JS_ASSERT(bi->kind() == Binding::ARGUMENT); |
| |
1199 unsigned i = bi.frameIndex(); |
| |
1200 if (script->formalIsAliased(i)) |
| |
1201 return true; |
| |
1202 |
| |
1203 if (maybeLiveScope) { |
| |
1204 AbstractFramePtr frame = maybeLiveScope->frame(); |
| |
1205 if (script->argsObjAliasesFormals() && frame.hasArgsObj()) { |
| |
1206 if (action == GET) |
| |
1207 vp.set(frame.argsObj().arg(i)); |
| |
1208 else |
| |
1209 frame.argsObj().setArg(i, vp); |
| |
1210 } else { |
| |
1211 if (action == GET) |
| |
1212 vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING)); |
| |
1213 else |
| |
1214 frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp; |
| |
1215 } |
| |
1216 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { |
| |
1217 if (action == GET) |
| |
1218 vp.set(snapshot->getDenseElement(i)); |
| |
1219 else |
| |
1220 snapshot->setDenseElement(i, vp); |
| |
1221 } else { |
| |
1222 /* The unaliased value has been lost to the debugger. */ |
| |
1223 if (action == GET) { |
| |
1224 *accessResult = ACCESS_LOST; |
| |
1225 return true; |
| |
1226 } |
| |
1227 } |
| |
1228 |
| |
1229 if (action == SET) |
| |
1230 TypeScript::SetArgument(cx, script, i, vp); |
| |
1231 } |
| |
1232 |
| |
1233 *accessResult = ACCESS_UNALIASED; |
| |
1234 return true; |
| |
1235 } |
| |
1236 |
| |
1237 /* Handle unaliased let and catch bindings at block scope. */ |
| |
1238 if (scope->is<ClonedBlockObject>()) { |
| |
1239 Rooted<ClonedBlockObject *> block(cx, &scope->as<ClonedBlockObject>()); |
| |
1240 Shape *shape = block->lastProperty()->search(cx, id); |
| |
1241 if (!shape) |
| |
1242 return true; |
| |
1243 |
| |
1244 unsigned i = block->staticBlock().shapeToIndex(*shape); |
| |
1245 if (block->staticBlock().isAliased(i)) |
| |
1246 return true; |
| |
1247 |
| |
1248 if (maybeLiveScope) { |
| |
1249 AbstractFramePtr frame = maybeLiveScope->frame(); |
| |
1250 uint32_t local = block->staticBlock().blockIndexToLocalIndex(i); |
| |
1251 JS_ASSERT(local < frame.script()->nfixed()); |
| |
1252 if (action == GET) |
| |
1253 vp.set(frame.unaliasedLocal(local)); |
| |
1254 else |
| |
1255 frame.unaliasedLocal(local) = vp; |
| |
1256 } else { |
| |
1257 if (action == GET) |
| |
1258 vp.set(block->var(i, DONT_CHECK_ALIASING)); |
| |
1259 else |
| |
1260 block->setVar(i, vp, DONT_CHECK_ALIASING); |
| |
1261 } |
| |
1262 |
| |
1263 *accessResult = ACCESS_UNALIASED; |
| |
1264 return true; |
| |
1265 } |
| |
1266 |
| |
1267 /* The rest of the internal scopes do not have unaliased vars. */ |
| |
1268 JS_ASSERT(scope->is<DeclEnvObject>() || scope->is<DynamicWithObject>() || |
| |
1269 scope->as<CallObject>().isForEval()); |
| |
1270 return true; |
| |
1271 } |
| |
1272 |
| |
1273 static bool isArguments(JSContext *cx, jsid id) |
| |
1274 { |
| |
1275 return id == NameToId(cx->names().arguments); |
| |
1276 } |
| |
1277 |
| |
1278 static bool isFunctionScope(ScopeObject &scope) |
| |
1279 { |
| |
1280 return scope.is<CallObject>() && !scope.as<CallObject>().isForEval(); |
| |
1281 } |
| |
1282 |
| |
1283 /* |
| |
1284 * In theory, every function scope contains an 'arguments' bindings. |
| |
1285 * However, the engine only adds a binding if 'arguments' is used in the |
| |
1286 * function body. Thus, from the debugger's perspective, 'arguments' may be |
| |
1287 * missing from the list of bindings. |
| |
1288 */ |
| |
1289 static bool isMissingArgumentsBinding(ScopeObject &scope) |
| |
1290 { |
| |
1291 return isFunctionScope(scope) && |
| |
1292 !scope.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding(); |
| |
1293 } |
| |
1294 |
| |
1295 /* |
| |
1296 * This function checks if an arguments object needs to be created when |
| |
1297 * the debugger requests 'arguments' for a function scope where the |
| |
1298 * arguments object has been optimized away (either because the binding is |
| |
1299 * missing altogether or because !ScriptAnalysis::needsArgsObj). |
| |
1300 */ |
| |
1301 static bool isMissingArguments(JSContext *cx, jsid id, ScopeObject &scope) |
| |
1302 { |
| |
1303 return isArguments(cx, id) && isFunctionScope(scope) && |
| |
1304 !scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj(); |
| |
1305 } |
| |
1306 |
| |
1307 /* |
| |
1308 * Create a missing arguments object. If the function returns true but |
| |
1309 * argsObj is null, it means the scope is dead. |
| |
1310 */ |
| |
1311 static bool createMissingArguments(JSContext *cx, jsid id, ScopeObject &scope, |
| |
1312 MutableHandleArgumentsObject argsObj) |
| |
1313 { |
| |
1314 MOZ_ASSERT(isMissingArguments(cx, id, scope)); |
| |
1315 argsObj.set(nullptr); |
| |
1316 |
| |
1317 ScopeIterVal *maybeScope = DebugScopes::hasLiveScope(scope); |
| |
1318 if (!maybeScope) |
| |
1319 return true; |
| |
1320 |
| |
1321 argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame())); |
| |
1322 return !!argsObj; |
| |
1323 } |
| |
1324 |
| |
1325 public: |
| |
1326 static int family; |
| |
1327 static DebugScopeProxy singleton; |
| |
1328 |
| |
1329 DebugScopeProxy() : BaseProxyHandler(&family) {} |
| |
1330 |
| |
1331 bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE |
| |
1332 { |
| |
1333 // always [[Extensible]], can't be made non-[[Extensible]], like most |
| |
1334 // proxies |
| |
1335 *extensible = true; |
| |
1336 return true; |
| |
1337 } |
| |
1338 |
| |
1339 bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE |
| |
1340 { |
| |
1341 // See above. |
| |
1342 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY); |
| |
1343 return false; |
| |
1344 } |
| |
1345 |
| |
1346 bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, |
| |
1347 MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE |
| |
1348 { |
| |
1349 return getOwnPropertyDescriptor(cx, proxy, id, desc); |
| |
1350 } |
| |
1351 |
| |
1352 bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, |
| |
1353 MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE |
| |
1354 { |
| |
1355 Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| |
1356 Rooted<ScopeObject*> scope(cx, &debugScope->scope()); |
| |
1357 |
| |
1358 if (isMissingArguments(cx, id, *scope)) { |
| |
1359 RootedArgumentsObject argsObj(cx); |
| |
1360 if (!createMissingArguments(cx, id, *scope, &argsObj)) |
| |
1361 return false; |
| |
1362 |
| |
1363 if (!argsObj) { |
| |
1364 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, |
| |
1365 "Debugger scope"); |
| |
1366 return false; |
| |
1367 } |
| |
1368 |
| |
1369 desc.object().set(debugScope); |
| |
1370 desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); |
| |
1371 desc.value().setObject(*argsObj); |
| |
1372 desc.setGetter(nullptr); |
| |
1373 desc.setSetter(nullptr); |
| |
1374 return true; |
| |
1375 } |
| |
1376 |
| |
1377 RootedValue v(cx); |
| |
1378 AccessResult access; |
| |
1379 if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access)) |
| |
1380 return false; |
| |
1381 |
| |
1382 switch (access) { |
| |
1383 case ACCESS_UNALIASED: |
| |
1384 desc.object().set(debugScope); |
| |
1385 desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); |
| |
1386 desc.value().set(v); |
| |
1387 desc.setGetter(nullptr); |
| |
1388 desc.setSetter(nullptr); |
| |
1389 return true; |
| |
1390 case ACCESS_GENERIC: |
| |
1391 return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc); |
| |
1392 case ACCESS_LOST: |
| |
1393 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); |
| |
1394 return false; |
| |
1395 default: |
| |
1396 MOZ_ASSUME_UNREACHABLE("bad AccessResult"); |
| |
1397 } |
| |
1398 } |
| |
1399 |
| |
1400 bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, |
| |
1401 MutableHandleValue vp) MOZ_OVERRIDE |
| |
1402 { |
| |
1403 Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| |
1404 Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
1405 |
| |
1406 if (isMissingArguments(cx, id, *scope)) { |
| |
1407 RootedArgumentsObject argsObj(cx); |
| |
1408 if (!createMissingArguments(cx, id, *scope, &argsObj)) |
| |
1409 return false; |
| |
1410 |
| |
1411 if (!argsObj) { |
| |
1412 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, |
| |
1413 "Debugger scope"); |
| |
1414 return false; |
| |
1415 } |
| |
1416 |
| |
1417 vp.setObject(*argsObj); |
| |
1418 return true; |
| |
1419 } |
| |
1420 |
| |
1421 AccessResult access; |
| |
1422 if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) |
| |
1423 return false; |
| |
1424 |
| |
1425 switch (access) { |
| |
1426 case ACCESS_UNALIASED: |
| |
1427 return true; |
| |
1428 case ACCESS_GENERIC: |
| |
1429 return JSObject::getGeneric(cx, scope, scope, id, vp); |
| |
1430 case ACCESS_LOST: |
| |
1431 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); |
| |
1432 return false; |
| |
1433 default: |
| |
1434 MOZ_ASSUME_UNREACHABLE("bad AccessResult"); |
| |
1435 } |
| |
1436 } |
| |
1437 |
| |
1438 /* |
| |
1439 * Like 'get', but returns sentinel values instead of throwing on |
| |
1440 * exceptional cases. |
| |
1441 */ |
| |
1442 bool getMaybeSentinelValue(JSContext *cx, Handle<DebugScopeObject *> debugScope, HandleId id, |
| |
1443 MutableHandleValue vp) |
| |
1444 { |
| |
1445 Rooted<ScopeObject*> scope(cx, &debugScope->scope()); |
| |
1446 |
| |
1447 if (isMissingArguments(cx, id, *scope)) { |
| |
1448 RootedArgumentsObject argsObj(cx); |
| |
1449 if (!createMissingArguments(cx, id, *scope, &argsObj)) |
| |
1450 return false; |
| |
1451 vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS)); |
| |
1452 return true; |
| |
1453 } |
| |
1454 |
| |
1455 AccessResult access; |
| |
1456 if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) |
| |
1457 return false; |
| |
1458 |
| |
1459 switch (access) { |
| |
1460 case ACCESS_UNALIASED: |
| |
1461 return true; |
| |
1462 case ACCESS_GENERIC: |
| |
1463 return JSObject::getGeneric(cx, scope, scope, id, vp); |
| |
1464 case ACCESS_LOST: |
| |
1465 vp.setMagic(JS_OPTIMIZED_OUT); |
| |
1466 return true; |
| |
1467 default: |
| |
1468 MOZ_ASSUME_UNREACHABLE("bad AccessResult"); |
| |
1469 } |
| |
1470 } |
| |
1471 |
| |
1472 bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict, |
| |
1473 MutableHandleValue vp) MOZ_OVERRIDE |
| |
1474 { |
| |
1475 Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| |
1476 Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
1477 |
| |
1478 AccessResult access; |
| |
1479 if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, vp, &access)) |
| |
1480 return false; |
| |
1481 |
| |
1482 switch (access) { |
| |
1483 case ACCESS_UNALIASED: |
| |
1484 return true; |
| |
1485 case ACCESS_GENERIC: |
| |
1486 return JSObject::setGeneric(cx, scope, scope, id, vp, strict); |
| |
1487 default: |
| |
1488 MOZ_ASSUME_UNREACHABLE("bad AccessResult"); |
| |
1489 } |
| |
1490 } |
| |
1491 |
| |
1492 bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id, |
| |
1493 MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE |
| |
1494 { |
| |
1495 Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
1496 |
| |
1497 bool found; |
| |
1498 if (!has(cx, proxy, id, &found)) |
| |
1499 return false; |
| |
1500 if (found) |
| |
1501 return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); |
| |
1502 |
| |
1503 return JS_DefinePropertyById(cx, scope, id, desc.value(), desc.getter(), desc.setter(), |
| |
1504 desc.attributes()); |
| |
1505 } |
| |
1506 |
| |
1507 bool getScopePropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props, |
| |
1508 unsigned flags) |
| |
1509 { |
| |
1510 Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
1511 |
| |
1512 if (isMissingArgumentsBinding(*scope)) { |
| |
1513 if (!props.append(NameToId(cx->names().arguments))) |
| |
1514 return false; |
| |
1515 } |
| |
1516 |
| |
1517 // DynamicWithObject isn't a very good proxy. It doesn't have a |
| |
1518 // JSNewEnumerateOp implementation, because if it just delegated to the |
| |
1519 // target object, the object would indicate that native enumeration is |
| |
1520 // the thing to do, but native enumeration over the DynamicWithObject |
| |
1521 // wrapper yields no properties. So instead here we hack around the |
| |
1522 // issue, and punch a hole through to the with object target. |
| |
1523 Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>() |
| |
1524 ? &scope->as<DynamicWithObject>().object() : scope)); |
| |
1525 if (!GetPropertyNames(cx, target, flags, &props)) |
| |
1526 return false; |
| |
1527 |
| |
1528 /* |
| |
1529 * Function scopes are optimized to not contain unaliased variables so |
| |
1530 * they must be manually appended here. |
| |
1531 */ |
| |
1532 if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| |
1533 RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript()); |
| |
1534 for (BindingIter bi(script); bi; bi++) { |
| |
1535 if (!bi->aliased() && !props.append(NameToId(bi->name()))) |
| |
1536 return false; |
| |
1537 } |
| |
1538 } |
| |
1539 |
| |
1540 return true; |
| |
1541 } |
| |
1542 |
| |
1543 bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE |
| |
1544 { |
| |
1545 return getScopePropertyNames(cx, proxy, props, JSITER_OWNONLY); |
| |
1546 } |
| |
1547 |
| |
1548 bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE |
| |
1549 { |
| |
1550 return getScopePropertyNames(cx, proxy, props, 0); |
| |
1551 } |
| |
1552 |
| |
1553 bool has(JSContext *cx, HandleObject proxy, HandleId id_, bool *bp) MOZ_OVERRIDE |
| |
1554 { |
| |
1555 RootedId id(cx, id_); |
| |
1556 ScopeObject &scopeObj = proxy->as<DebugScopeObject>().scope(); |
| |
1557 |
| |
1558 if (isArguments(cx, id) && isFunctionScope(scopeObj)) { |
| |
1559 *bp = true; |
| |
1560 return true; |
| |
1561 } |
| |
1562 |
| |
1563 bool found; |
| |
1564 RootedObject scope(cx, &scopeObj); |
| |
1565 if (!JS_HasPropertyById(cx, scope, id, &found)) |
| |
1566 return false; |
| |
1567 |
| |
1568 /* |
| |
1569 * Function scopes are optimized to not contain unaliased variables so |
| |
1570 * a manual search is necessary. |
| |
1571 */ |
| |
1572 if (!found && scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| |
1573 RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript()); |
| |
1574 for (BindingIter bi(script); bi; bi++) { |
| |
1575 if (!bi->aliased() && NameToId(bi->name()) == id) { |
| |
1576 found = true; |
| |
1577 break; |
| |
1578 } |
| |
1579 } |
| |
1580 } |
| |
1581 |
| |
1582 *bp = found; |
| |
1583 return true; |
| |
1584 } |
| |
1585 |
| |
1586 bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE |
| |
1587 { |
| |
1588 RootedValue idval(cx, IdToValue(id)); |
| |
1589 return js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_CANT_DELETE, |
| |
1590 JSDVG_IGNORE_STACK, idval, NullPtr(), nullptr, nullptr); |
| |
1591 } |
| |
1592 }; |
| |
1593 |
| |
1594 } /* anonymous namespace */ |
| |
1595 |
| |
1596 int DebugScopeProxy::family = 0; |
| |
1597 DebugScopeProxy DebugScopeProxy::singleton; |
| |
1598 |
| |
1599 /* static */ DebugScopeObject * |
| |
1600 DebugScopeObject::create(JSContext *cx, ScopeObject &scope, HandleObject enclosing) |
| |
1601 { |
| |
1602 JS_ASSERT(scope.compartment() == cx->compartment()); |
| |
1603 RootedValue priv(cx, ObjectValue(scope)); |
| |
1604 JSObject *obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv, |
| |
1605 nullptr /* proto */, &scope.global()); |
| |
1606 if (!obj) |
| |
1607 return nullptr; |
| |
1608 |
| |
1609 JS_ASSERT(!enclosing->is<ScopeObject>()); |
| |
1610 |
| |
1611 DebugScopeObject *debugScope = &obj->as<DebugScopeObject>(); |
| |
1612 debugScope->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing)); |
| |
1613 debugScope->setExtra(SNAPSHOT_EXTRA, NullValue()); |
| |
1614 |
| |
1615 return debugScope; |
| |
1616 } |
| |
1617 |
| |
1618 ScopeObject & |
| |
1619 DebugScopeObject::scope() const |
| |
1620 { |
| |
1621 return target()->as<ScopeObject>(); |
| |
1622 } |
| |
1623 |
| |
1624 JSObject & |
| |
1625 DebugScopeObject::enclosingScope() const |
| |
1626 { |
| |
1627 return extra(ENCLOSING_EXTRA).toObject(); |
| |
1628 } |
| |
1629 |
| |
1630 JSObject * |
| |
1631 DebugScopeObject::maybeSnapshot() const |
| |
1632 { |
| |
1633 JS_ASSERT(!scope().as<CallObject>().isForEval()); |
| |
1634 return extra(SNAPSHOT_EXTRA).toObjectOrNull(); |
| |
1635 } |
| |
1636 |
| |
1637 void |
| |
1638 DebugScopeObject::initSnapshot(JSObject &o) |
| |
1639 { |
| |
1640 JS_ASSERT(maybeSnapshot() == nullptr); |
| |
1641 setExtra(SNAPSHOT_EXTRA, ObjectValue(o)); |
| |
1642 } |
| |
1643 |
| |
1644 bool |
| |
1645 DebugScopeObject::isForDeclarative() const |
| |
1646 { |
| |
1647 ScopeObject &s = scope(); |
| |
1648 return s.is<CallObject>() || s.is<BlockObject>() || s.is<DeclEnvObject>(); |
| |
1649 } |
| |
1650 |
| |
1651 bool |
| |
1652 DebugScopeObject::getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp) |
| |
1653 { |
| |
1654 Rooted<DebugScopeObject *> self(cx, this); |
| |
1655 return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp); |
| |
1656 } |
| |
1657 |
| |
1658 bool |
| |
1659 js_IsDebugScopeSlow(ProxyObject *proxy) |
| |
1660 { |
| |
1661 JS_ASSERT(proxy->hasClass(&ProxyObject::uncallableClass_)); |
| |
1662 return proxy->handler() == &DebugScopeProxy::singleton; |
| |
1663 } |
| |
1664 |
| |
1665 /*****************************************************************************/ |
| |
1666 |
| |
1667 /* static */ MOZ_ALWAYS_INLINE void |
| |
1668 DebugScopes::proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map, |
| |
1669 const EncapsulatedPtr<JSObject> &key) |
| |
1670 { |
| |
1671 #ifdef JSGC_GENERATIONAL |
| |
1672 /* |
| |
1673 * Strip the barriers from the type before inserting into the store buffer. |
| |
1674 * This will automatically ensure that barriers do not fire during GC. |
| |
1675 * |
| |
1676 * Some compilers complain about instantiating the WeakMap class for |
| |
1677 * unbarriered type arguments, so we cast to a HashMap instead. Because of |
| |
1678 * WeakMap's multiple inheritace, We need to do this in two stages, first to |
| |
1679 * the HashMap base class and then to the unbarriered version. |
| |
1680 */ |
| |
1681 ObjectWeakMap::Base *baseHashMap = static_cast<ObjectWeakMap::Base *>(map); |
| |
1682 |
| |
1683 typedef HashMap<JSObject *, JSObject *> UnbarrieredMap; |
| |
1684 UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap); |
| |
1685 |
| |
1686 typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref; |
| |
1687 if (key && IsInsideNursery(rt, key)) |
| |
1688 rt->gcStoreBuffer.putGeneric(Ref(unbarrieredMap, key.get())); |
| |
1689 #endif |
| |
1690 } |
| |
1691 |
| |
1692 #ifdef JSGC_GENERATIONAL |
| |
1693 class DebugScopes::MissingScopesRef : public gc::BufferableRef |
| |
1694 { |
| |
1695 MissingScopeMap *map; |
| |
1696 ScopeIterKey key; |
| |
1697 |
| |
1698 public: |
| |
1699 MissingScopesRef(MissingScopeMap *m, const ScopeIterKey &k) : map(m), key(k) {} |
| |
1700 |
| |
1701 void mark(JSTracer *trc) { |
| |
1702 ScopeIterKey prior = key; |
| |
1703 MissingScopeMap::Ptr p = map->lookup(key); |
| |
1704 if (!p) |
| |
1705 return; |
| |
1706 trc->setTracingLocation(&const_cast<ScopeIterKey &>(p->key()).enclosingScope()); |
| |
1707 Mark(trc, &key.enclosingScope(), "MissingScopesRef"); |
| |
1708 map->rekeyIfMoved(prior, key); |
| |
1709 } |
| |
1710 }; |
| |
1711 #endif |
| |
1712 |
| |
1713 /* static */ MOZ_ALWAYS_INLINE void |
| |
1714 DebugScopes::missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map, |
| |
1715 const ScopeIterKey &key) |
| |
1716 { |
| |
1717 #ifdef JSGC_GENERATIONAL |
| |
1718 if (key.enclosingScope() && IsInsideNursery(rt, key.enclosingScope())) |
| |
1719 rt->gcStoreBuffer.putGeneric(MissingScopesRef(map, key)); |
| |
1720 #endif |
| |
1721 } |
| |
1722 |
| |
1723 /* static */ MOZ_ALWAYS_INLINE void |
| |
1724 DebugScopes::liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, ScopeObject *key) |
| |
1725 { |
| |
1726 #ifdef JSGC_GENERATIONAL |
| |
1727 // As above. Otherwise, barriers could fire during GC when moving the |
| |
1728 // value. |
| |
1729 typedef HashMap<ScopeObject *, |
| |
1730 ScopeIterKey, |
| |
1731 DefaultHasher<ScopeObject *>, |
| |
1732 RuntimeAllocPolicy> UnbarrieredLiveScopeMap; |
| |
1733 typedef gc::HashKeyRef<UnbarrieredLiveScopeMap, ScopeObject *> Ref; |
| |
1734 if (key && IsInsideNursery(rt, key)) |
| |
1735 rt->gcStoreBuffer.putGeneric(Ref(reinterpret_cast<UnbarrieredLiveScopeMap *>(map), key)); |
| |
1736 #endif |
| |
1737 } |
| |
1738 |
| |
1739 DebugScopes::DebugScopes(JSContext *cx) |
| |
1740 : proxiedScopes(cx), |
| |
1741 missingScopes(cx->runtime()), |
| |
1742 liveScopes(cx->runtime()) |
| |
1743 {} |
| |
1744 |
| |
1745 DebugScopes::~DebugScopes() |
| |
1746 { |
| |
1747 JS_ASSERT(missingScopes.empty()); |
| |
1748 WeakMapBase::removeWeakMapFromList(&proxiedScopes); |
| |
1749 } |
| |
1750 |
| |
1751 bool |
| |
1752 DebugScopes::init() |
| |
1753 { |
| |
1754 if (!liveScopes.init() || |
| |
1755 !proxiedScopes.init() || |
| |
1756 !missingScopes.init()) |
| |
1757 { |
| |
1758 return false; |
| |
1759 } |
| |
1760 return true; |
| |
1761 } |
| |
1762 |
| |
1763 void |
| |
1764 DebugScopes::mark(JSTracer *trc) |
| |
1765 { |
| |
1766 proxiedScopes.trace(trc); |
| |
1767 } |
| |
1768 |
| |
1769 void |
| |
1770 DebugScopes::sweep(JSRuntime *rt) |
| |
1771 { |
| |
1772 /* |
| |
1773 * missingScopes points to debug scopes weakly so that debug scopes can be |
| |
1774 * released more eagerly. |
| |
1775 */ |
| |
1776 for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { |
| |
1777 DebugScopeObject **debugScope = e.front().value().unsafeGet(); |
| |
1778 if (IsObjectAboutToBeFinalized(debugScope)) { |
| |
1779 /* |
| |
1780 * Note that onPopCall and onPopBlock rely on missingScopes to find |
| |
1781 * scope objects that we synthesized for the debugger's sake, and |
| |
1782 * clean up the synthetic scope objects' entries in liveScopes. So |
| |
1783 * if we remove an entry frcom missingScopes here, we must also |
| |
1784 * remove the corresponding liveScopes entry. |
| |
1785 * |
| |
1786 * Since the DebugScopeObject is the only thing using its scope |
| |
1787 * object, and the DSO is about to be finalized, you might assume |
| |
1788 * that the synthetic SO is also about to be finalized too, and thus |
| |
1789 * the loop below will take care of things. But complex GC behavior |
| |
1790 * means that marks are only conservative approximations of |
| |
1791 * liveness; we should assume that anything could be marked. |
| |
1792 * |
| |
1793 * Thus, we must explicitly remove the entries from both liveScopes |
| |
1794 * and missingScopes here. |
| |
1795 */ |
| |
1796 liveScopes.remove(&(*debugScope)->scope()); |
| |
1797 e.removeFront(); |
| |
1798 } |
| |
1799 } |
| |
1800 |
| |
1801 for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { |
| |
1802 ScopeObject *scope = e.front().key(); |
| |
1803 |
| |
1804 /* |
| |
1805 * Scopes can be finalized when a debugger-synthesized ScopeObject is |
| |
1806 * no longer reachable via its DebugScopeObject. |
| |
1807 */ |
| |
1808 if (IsObjectAboutToBeFinalized(&scope)) { |
| |
1809 e.removeFront(); |
| |
1810 continue; |
| |
1811 } |
| |
1812 } |
| |
1813 } |
| |
1814 |
| |
1815 #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) |
| |
1816 void |
| |
1817 DebugScopes::checkHashTablesAfterMovingGC(JSRuntime *runtime) |
| |
1818 { |
| |
1819 /* |
| |
1820 * This is called at the end of StoreBuffer::mark() to check that our |
| |
1821 * postbarriers have worked and that no hashtable keys (or values) are left |
| |
1822 * pointing into the nursery. |
| |
1823 */ |
| |
1824 JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtime); |
| |
1825 for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) { |
| |
1826 JS_ASSERT(!IsInsideNursery(rt, r.front().key().get())); |
| |
1827 JS_ASSERT(!IsInsideNursery(rt, r.front().value().get())); |
| |
1828 } |
| |
1829 for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) { |
| |
1830 JS_ASSERT(!IsInsideNursery(rt, r.front().key().cur())); |
| |
1831 JS_ASSERT(!IsInsideNursery(rt, r.front().key().staticScope())); |
| |
1832 JS_ASSERT(!IsInsideNursery(rt, r.front().value().get())); |
| |
1833 } |
| |
1834 for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) { |
| |
1835 JS_ASSERT(!IsInsideNursery(rt, r.front().key())); |
| |
1836 JS_ASSERT(!IsInsideNursery(rt, r.front().value().cur_.get())); |
| |
1837 JS_ASSERT(!IsInsideNursery(rt, r.front().value().staticScope_.get())); |
| |
1838 } |
| |
1839 } |
| |
1840 #endif |
| |
1841 |
| |
1842 /* |
| |
1843 * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode |
| |
1844 * (in particular, JS_GetFrameScopeChain does not require debug mode). Since |
| |
1845 * DebugScopes::onPop* are only called in debug mode, this means we cannot |
| |
1846 * use any of the maps in DebugScopes. This will produce debug scope chains |
| |
1847 * that do not obey the debugger invariants but that is just fine. |
| |
1848 */ |
| |
1849 static bool |
| |
1850 CanUseDebugScopeMaps(JSContext *cx) |
| |
1851 { |
| |
1852 return cx->compartment()->debugMode(); |
| |
1853 } |
| |
1854 |
| |
1855 DebugScopes * |
| |
1856 DebugScopes::ensureCompartmentData(JSContext *cx) |
| |
1857 { |
| |
1858 JSCompartment *c = cx->compartment(); |
| |
1859 if (c->debugScopes) |
| |
1860 return c->debugScopes; |
| |
1861 |
| |
1862 c->debugScopes = cx->runtime()->new_<DebugScopes>(cx); |
| |
1863 if (c->debugScopes && c->debugScopes->init()) |
| |
1864 return c->debugScopes; |
| |
1865 |
| |
1866 js_ReportOutOfMemory(cx); |
| |
1867 return nullptr; |
| |
1868 } |
| |
1869 |
| |
1870 DebugScopeObject * |
| |
1871 DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope) |
| |
1872 { |
| |
1873 DebugScopes *scopes = scope.compartment()->debugScopes; |
| |
1874 if (!scopes) |
| |
1875 return nullptr; |
| |
1876 |
| |
1877 if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&scope)) { |
| |
1878 JS_ASSERT(CanUseDebugScopeMaps(cx)); |
| |
1879 return &p->value()->as<DebugScopeObject>(); |
| |
1880 } |
| |
1881 |
| |
1882 return nullptr; |
| |
1883 } |
| |
1884 |
| |
1885 bool |
| |
1886 DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope) |
| |
1887 { |
| |
1888 JS_ASSERT(cx->compartment() == scope.compartment()); |
| |
1889 JS_ASSERT(cx->compartment() == debugScope.compartment()); |
| |
1890 |
| |
1891 if (!CanUseDebugScopeMaps(cx)) |
| |
1892 return true; |
| |
1893 |
| |
1894 DebugScopes *scopes = ensureCompartmentData(cx); |
| |
1895 if (!scopes) |
| |
1896 return false; |
| |
1897 |
| |
1898 JS_ASSERT(!scopes->proxiedScopes.has(&scope)); |
| |
1899 if (!scopes->proxiedScopes.put(&scope, &debugScope)) { |
| |
1900 js_ReportOutOfMemory(cx); |
| |
1901 return false; |
| |
1902 } |
| |
1903 |
| |
1904 proxiedScopesPostWriteBarrier(cx->runtime(), &scopes->proxiedScopes, &scope); |
| |
1905 return true; |
| |
1906 } |
| |
1907 |
| |
1908 DebugScopeObject * |
| |
1909 DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) |
| |
1910 { |
| |
1911 JS_ASSERT(!si.hasScopeObject()); |
| |
1912 |
| |
1913 DebugScopes *scopes = cx->compartment()->debugScopes; |
| |
1914 if (!scopes) |
| |
1915 return nullptr; |
| |
1916 |
| |
1917 if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| |
1918 JS_ASSERT(CanUseDebugScopeMaps(cx)); |
| |
1919 return p->value(); |
| |
1920 } |
| |
1921 return nullptr; |
| |
1922 } |
| |
1923 |
| |
1924 bool |
| |
1925 DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope) |
| |
1926 { |
| |
1927 JS_ASSERT(!si.hasScopeObject()); |
| |
1928 JS_ASSERT(cx->compartment() == debugScope.compartment()); |
| |
1929 JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); |
| |
1930 |
| |
1931 if (!CanUseDebugScopeMaps(cx)) |
| |
1932 return true; |
| |
1933 |
| |
1934 DebugScopes *scopes = ensureCompartmentData(cx); |
| |
1935 if (!scopes) |
| |
1936 return false; |
| |
1937 |
| |
1938 JS_ASSERT(!scopes->missingScopes.has(si)); |
| |
1939 if (!scopes->missingScopes.put(si, &debugScope)) { |
| |
1940 js_ReportOutOfMemory(cx); |
| |
1941 return false; |
| |
1942 } |
| |
1943 missingScopesPostWriteBarrier(cx->runtime(), &scopes->missingScopes, si); |
| |
1944 |
| |
1945 JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); |
| |
1946 if (!scopes->liveScopes.put(&debugScope.scope(), si)) { |
| |
1947 js_ReportOutOfMemory(cx); |
| |
1948 return false; |
| |
1949 } |
| |
1950 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope()); |
| |
1951 |
| |
1952 return true; |
| |
1953 } |
| |
1954 |
| |
1955 void |
| |
1956 DebugScopes::onPopCall(AbstractFramePtr frame, JSContext *cx) |
| |
1957 { |
| |
1958 JS_ASSERT(!frame.isYielding()); |
| |
1959 assertSameCompartment(cx, frame); |
| |
1960 |
| |
1961 DebugScopes *scopes = cx->compartment()->debugScopes; |
| |
1962 if (!scopes) |
| |
1963 return; |
| |
1964 |
| |
1965 Rooted<DebugScopeObject*> debugScope(cx, nullptr); |
| |
1966 |
| |
1967 if (frame.fun()->isHeavyweight()) { |
| |
1968 /* |
| |
1969 * The frame may be observed before the prologue has created the |
| |
1970 * CallObject. See ScopeIter::settle. |
| |
1971 */ |
| |
1972 if (!frame.hasCallObj()) |
| |
1973 return; |
| |
1974 |
| |
1975 CallObject &callobj = frame.scopeChain()->as<CallObject>(); |
| |
1976 scopes->liveScopes.remove(&callobj); |
| |
1977 if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj)) |
| |
1978 debugScope = &p->value()->as<DebugScopeObject>(); |
| |
1979 } else { |
| |
1980 ScopeIter si(frame, frame.script()->main(), cx); |
| |
1981 if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| |
1982 debugScope = p->value(); |
| |
1983 scopes->liveScopes.remove(&debugScope->scope().as<CallObject>()); |
| |
1984 scopes->missingScopes.remove(p); |
| |
1985 } |
| |
1986 } |
| |
1987 |
| |
1988 /* |
| |
1989 * When the JS stack frame is popped, the values of unaliased variables |
| |
1990 * are lost. If there is any debug scope referring to this scope, save a |
| |
1991 * copy of the unaliased variables' values in an array for later debugger |
| |
1992 * access via DebugScopeProxy::handleUnaliasedAccess. |
| |
1993 * |
| |
1994 * Note: since it is simplest for this function to be infallible, failure |
| |
1995 * in this code will be silently ignored. This does not break any |
| |
1996 * invariants since DebugScopeObject::maybeSnapshot can already be nullptr. |
| |
1997 */ |
| |
1998 if (debugScope) { |
| |
1999 /* |
| |
2000 * Copy all frame values into the snapshot, regardless of |
| |
2001 * aliasing. This unnecessarily includes aliased variables |
| |
2002 * but it simplifies later indexing logic. |
| |
2003 */ |
| |
2004 AutoValueVector vec(cx); |
| |
2005 if (!frame.copyRawFrameSlots(&vec) || vec.length() == 0) |
| |
2006 return; |
| |
2007 |
| |
2008 /* |
| |
2009 * Copy in formals that are not aliased via the scope chain |
| |
2010 * but are aliased via the arguments object. |
| |
2011 */ |
| |
2012 RootedScript script(cx, frame.script()); |
| |
2013 if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) { |
| |
2014 for (unsigned i = 0; i < frame.numFormalArgs(); ++i) { |
| |
2015 if (script->formalLivesInArgumentsObject(i)) |
| |
2016 vec[i] = frame.argsObj().arg(i); |
| |
2017 } |
| |
2018 } |
| |
2019 |
| |
2020 /* |
| |
2021 * Use a dense array as storage (since proxies do not have trace |
| |
2022 * hooks). This array must not escape into the wild. |
| |
2023 */ |
| |
2024 RootedObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); |
| |
2025 if (!snapshot) { |
| |
2026 cx->clearPendingException(); |
| |
2027 return; |
| |
2028 } |
| |
2029 |
| |
2030 debugScope->initSnapshot(*snapshot); |
| |
2031 } |
| |
2032 } |
| |
2033 |
| |
2034 void |
| |
2035 DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) |
| |
2036 { |
| |
2037 assertSameCompartment(cx, frame); |
| |
2038 |
| |
2039 DebugScopes *scopes = cx->compartment()->debugScopes; |
| |
2040 if (!scopes) |
| |
2041 return; |
| |
2042 |
| |
2043 ScopeIter si(frame, pc, cx); |
| |
2044 onPopBlock(cx, si); |
| |
2045 } |
| |
2046 |
| |
2047 void |
| |
2048 DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si) |
| |
2049 { |
| |
2050 DebugScopes *scopes = cx->compartment()->debugScopes; |
| |
2051 if (!scopes) |
| |
2052 return; |
| |
2053 |
| |
2054 JS_ASSERT(si.type() == ScopeIter::Block); |
| |
2055 |
| |
2056 if (si.staticBlock().needsClone()) { |
| |
2057 ClonedBlockObject &clone = si.scope().as<ClonedBlockObject>(); |
| |
2058 clone.copyUnaliasedValues(si.frame()); |
| |
2059 scopes->liveScopes.remove(&clone); |
| |
2060 } else { |
| |
2061 if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| |
2062 ClonedBlockObject &clone = p->value()->scope().as<ClonedBlockObject>(); |
| |
2063 clone.copyUnaliasedValues(si.frame()); |
| |
2064 scopes->liveScopes.remove(&clone); |
| |
2065 scopes->missingScopes.remove(p); |
| |
2066 } |
| |
2067 } |
| |
2068 } |
| |
2069 |
| |
2070 void |
| |
2071 DebugScopes::onPopWith(AbstractFramePtr frame) |
| |
2072 { |
| |
2073 DebugScopes *scopes = frame.compartment()->debugScopes; |
| |
2074 if (scopes) |
| |
2075 scopes->liveScopes.remove(&frame.scopeChain()->as<DynamicWithObject>()); |
| |
2076 } |
| |
2077 |
| |
2078 void |
| |
2079 DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame) |
| |
2080 { |
| |
2081 DebugScopes *scopes = frame.compartment()->debugScopes; |
| |
2082 if (!scopes) |
| |
2083 return; |
| |
2084 |
| |
2085 /* |
| |
2086 * The stack frame may be observed before the prologue has created the |
| |
2087 * CallObject. See ScopeIter::settle. |
| |
2088 */ |
| |
2089 if (frame.hasCallObj()) |
| |
2090 scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>()); |
| |
2091 } |
| |
2092 |
| |
2093 void |
| |
2094 DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c) |
| |
2095 { |
| |
2096 DebugScopes *scopes = c->debugScopes; |
| |
2097 if (scopes) { |
| |
2098 scopes->proxiedScopes.clear(); |
| |
2099 scopes->missingScopes.clear(); |
| |
2100 scopes->liveScopes.clear(); |
| |
2101 } |
| |
2102 } |
| |
2103 |
| |
2104 bool |
| |
2105 DebugScopes::updateLiveScopes(JSContext *cx) |
| |
2106 { |
| |
2107 JS_CHECK_RECURSION(cx, return false); |
| |
2108 |
| |
2109 /* |
| |
2110 * Note that we must always update the top frame's scope objects' entries |
| |
2111 * in liveScopes because we can't be sure code hasn't run in that frame to |
| |
2112 * change the scope chain since we were last called. The fp->prevUpToDate() |
| |
2113 * flag indicates whether the scopes of frames older than fp are already |
| |
2114 * included in liveScopes. It might seem simpler to have fp instead carry a |
| |
2115 * flag indicating whether fp itself is accurately described, but then we |
| |
2116 * would need to clear that flag whenever fp ran code. By storing the 'up |
| |
2117 * to date' bit for fp->prev() in fp, simply popping fp effectively clears |
| |
2118 * the flag for us, at exactly the time when execution resumes fp->prev(). |
| |
2119 */ |
| |
2120 for (AllFramesIter i(cx); !i.done(); ++i) { |
| |
2121 if (!i.hasUsableAbstractFramePtr()) |
| |
2122 continue; |
| |
2123 |
| |
2124 AbstractFramePtr frame = i.abstractFramePtr(); |
| |
2125 if (frame.scopeChain()->compartment() != cx->compartment()) |
| |
2126 continue; |
| |
2127 |
| |
2128 if (frame.isFunctionFrame() && frame.callee()->isGenerator()) |
| |
2129 continue; |
| |
2130 |
| |
2131 for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) { |
| |
2132 if (si.hasScopeObject()) { |
| |
2133 JS_ASSERT(si.scope().compartment() == cx->compartment()); |
| |
2134 DebugScopes *scopes = ensureCompartmentData(cx); |
| |
2135 if (!scopes) |
| |
2136 return false; |
| |
2137 if (!scopes->liveScopes.put(&si.scope(), si)) |
| |
2138 return false; |
| |
2139 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope()); |
| |
2140 } |
| |
2141 } |
| |
2142 |
| |
2143 if (frame.prevUpToDate()) |
| |
2144 return true; |
| |
2145 JS_ASSERT(frame.scopeChain()->compartment()->debugMode()); |
| |
2146 frame.setPrevUpToDate(); |
| |
2147 } |
| |
2148 |
| |
2149 return true; |
| |
2150 } |
| |
2151 |
| |
2152 ScopeIterVal* |
| |
2153 DebugScopes::hasLiveScope(ScopeObject &scope) |
| |
2154 { |
| |
2155 DebugScopes *scopes = scope.compartment()->debugScopes; |
| |
2156 if (!scopes) |
| |
2157 return nullptr; |
| |
2158 |
| |
2159 if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) |
| |
2160 return &p->value(); |
| |
2161 |
| |
2162 return nullptr; |
| |
2163 } |
| |
2164 |
| |
2165 /*****************************************************************************/ |
| |
2166 |
| |
2167 static JSObject * |
| |
2168 GetDebugScope(JSContext *cx, const ScopeIter &si); |
| |
2169 |
| |
2170 static DebugScopeObject * |
| |
2171 GetDebugScopeForScope(JSContext *cx, Handle<ScopeObject*> scope, const ScopeIter &enclosing) |
| |
2172 { |
| |
2173 if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope)) |
| |
2174 return debugScope; |
| |
2175 |
| |
2176 RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing)); |
| |
2177 if (!enclosingDebug) |
| |
2178 return nullptr; |
| |
2179 |
| |
2180 JSObject &maybeDecl = scope->enclosingScope(); |
| |
2181 if (maybeDecl.is<DeclEnvObject>()) { |
| |
2182 JS_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee())); |
| |
2183 enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug); |
| |
2184 if (!enclosingDebug) |
| |
2185 return nullptr; |
| |
2186 } |
| |
2187 |
| |
2188 DebugScopeObject *debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug); |
| |
2189 if (!debugScope) |
| |
2190 return nullptr; |
| |
2191 |
| |
2192 if (!DebugScopes::addDebugScope(cx, *scope, *debugScope)) |
| |
2193 return nullptr; |
| |
2194 |
| |
2195 return debugScope; |
| |
2196 } |
| |
2197 |
| |
2198 static DebugScopeObject * |
| |
2199 GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) |
| |
2200 { |
| |
2201 if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si)) |
| |
2202 return debugScope; |
| |
2203 |
| |
2204 ScopeIter copy(si, cx); |
| |
2205 RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); |
| |
2206 if (!enclosingDebug) |
| |
2207 return nullptr; |
| |
2208 |
| |
2209 /* |
| |
2210 * Create the missing scope object. For block objects, this takes care of |
| |
2211 * storing variable values after the stack frame has been popped. For call |
| |
2212 * objects, we only use the pretend call object to access callee, bindings |
| |
2213 * and to receive dynamically added properties. Together, this provides the |
| |
2214 * nice invariant that every DebugScopeObject has a ScopeObject. |
| |
2215 * |
| |
2216 * Note: to preserve scopeChain depth invariants, these lazily-reified |
| |
2217 * scopes must not be put on the frame's scope chain; instead, they are |
| |
2218 * maintained via DebugScopes hooks. |
| |
2219 */ |
| |
2220 DebugScopeObject *debugScope = nullptr; |
| |
2221 switch (si.type()) { |
| |
2222 case ScopeIter::Call: { |
| |
2223 // Generators should always reify their scopes. |
| |
2224 JS_ASSERT(!si.frame().callee()->isGenerator()); |
| |
2225 Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.frame())); |
| |
2226 if (!callobj) |
| |
2227 return nullptr; |
| |
2228 |
| |
2229 if (callobj->enclosingScope().is<DeclEnvObject>()) { |
| |
2230 JS_ASSERT(CallObjectLambdaName(callobj->callee())); |
| |
2231 DeclEnvObject &declenv = callobj->enclosingScope().as<DeclEnvObject>(); |
| |
2232 enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug); |
| |
2233 if (!enclosingDebug) |
| |
2234 return nullptr; |
| |
2235 } |
| |
2236 |
| |
2237 debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug); |
| |
2238 break; |
| |
2239 } |
| |
2240 case ScopeIter::Block: { |
| |
2241 // Generators should always reify their scopes. |
| |
2242 JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); |
| |
2243 Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock()); |
| |
2244 ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame()); |
| |
2245 if (!block) |
| |
2246 return nullptr; |
| |
2247 |
| |
2248 debugScope = DebugScopeObject::create(cx, *block, enclosingDebug); |
| |
2249 break; |
| |
2250 } |
| |
2251 case ScopeIter::With: |
| |
2252 case ScopeIter::StrictEvalScope: |
| |
2253 MOZ_ASSUME_UNREACHABLE("should already have a scope"); |
| |
2254 } |
| |
2255 if (!debugScope) |
| |
2256 return nullptr; |
| |
2257 |
| |
2258 if (!DebugScopes::addDebugScope(cx, si, *debugScope)) |
| |
2259 return nullptr; |
| |
2260 |
| |
2261 return debugScope; |
| |
2262 } |
| |
2263 |
| |
2264 static JSObject * |
| |
2265 GetDebugScope(JSContext *cx, JSObject &obj) |
| |
2266 { |
| |
2267 /* |
| |
2268 * As an engine invariant (maintained internally and asserted by Execute), |
| |
2269 * ScopeObjects and non-ScopeObjects cannot be interleaved on the scope |
| |
2270 * chain; every scope chain must start with zero or more ScopeObjects and |
| |
2271 * terminate with one or more non-ScopeObjects (viz., GlobalObject). |
| |
2272 */ |
| |
2273 if (!obj.is<ScopeObject>()) { |
| |
2274 #ifdef DEBUG |
| |
2275 JSObject *o = &obj; |
| |
2276 while ((o = o->enclosingScope())) |
| |
2277 JS_ASSERT(!o->is<ScopeObject>()); |
| |
2278 #endif |
| |
2279 return &obj; |
| |
2280 } |
| |
2281 |
| |
2282 Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>()); |
| |
2283 if (ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) { |
| |
2284 ScopeIter si(*maybeLiveScope, cx); |
| |
2285 return GetDebugScope(cx, si); |
| |
2286 } |
| |
2287 ScopeIter si(scope->enclosingScope(), cx); |
| |
2288 return GetDebugScopeForScope(cx, scope, si); |
| |
2289 } |
| |
2290 |
| |
2291 static JSObject * |
| |
2292 GetDebugScope(JSContext *cx, const ScopeIter &si) |
| |
2293 { |
| |
2294 JS_CHECK_RECURSION(cx, return nullptr); |
| |
2295 |
| |
2296 if (si.done()) |
| |
2297 return GetDebugScope(cx, si.enclosingScope()); |
| |
2298 |
| |
2299 if (!si.hasScopeObject()) |
| |
2300 return GetDebugScopeForMissing(cx, si); |
| |
2301 |
| |
2302 Rooted<ScopeObject*> scope(cx, &si.scope()); |
| |
2303 |
| |
2304 ScopeIter copy(si, cx); |
| |
2305 return GetDebugScopeForScope(cx, scope, ++copy); |
| |
2306 } |
| |
2307 |
| |
2308 JSObject * |
| |
2309 js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun) |
| |
2310 { |
| |
2311 assertSameCompartment(cx, fun); |
| |
2312 JS_ASSERT(cx->compartment()->debugMode()); |
| |
2313 if (!DebugScopes::updateLiveScopes(cx)) |
| |
2314 return nullptr; |
| |
2315 return GetDebugScope(cx, *fun->environment()); |
| |
2316 } |
| |
2317 |
| |
2318 JSObject * |
| |
2319 js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) |
| |
2320 { |
| |
2321 assertSameCompartment(cx, frame); |
| |
2322 if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) |
| |
2323 return nullptr; |
| |
2324 ScopeIter si(frame, pc, cx); |
| |
2325 return GetDebugScope(cx, si); |
| |
2326 } |
| |
2327 |
| |
2328 #ifdef DEBUG |
| |
2329 |
| |
2330 typedef HashSet<PropertyName *> PropertyNameSet; |
| |
2331 |
| |
2332 static bool |
| |
2333 RemoveReferencedNames(JSContext *cx, HandleScript script, PropertyNameSet &remainingNames) |
| |
2334 { |
| |
2335 // Remove from remainingNames --- the closure variables in some outer |
| |
2336 // script --- any free variables in this script. This analysis isn't perfect: |
| |
2337 // |
| |
2338 // - It will not account for free variables in an inner script which are |
| |
2339 // actually accessing some name in an intermediate script between the |
| |
2340 // inner and outer scripts. This can cause remainingNames to be an |
| |
2341 // underapproximation. |
| |
2342 // |
| |
2343 // - It will not account for new names introduced via eval. This can cause |
| |
2344 // remainingNames to be an overapproximation. This would be easy to fix |
| |
2345 // but is nice to have as the eval will probably not access these |
| |
2346 // these names and putting eval in an inner script is bad news if you |
| |
2347 // care about entraining variables unnecessarily. |
| |
2348 |
| |
2349 for (jsbytecode *pc = script->code(); pc != script->codeEnd(); pc += GetBytecodeLength(pc)) { |
| |
2350 PropertyName *name; |
| |
2351 |
| |
2352 switch (JSOp(*pc)) { |
| |
2353 case JSOP_NAME: |
| |
2354 case JSOP_SETNAME: |
| |
2355 name = script->getName(pc); |
| |
2356 break; |
| |
2357 |
| |
2358 case JSOP_GETALIASEDVAR: |
| |
2359 case JSOP_SETALIASEDVAR: |
| |
2360 name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); |
| |
2361 break; |
| |
2362 |
| |
2363 default: |
| |
2364 name = nullptr; |
| |
2365 break; |
| |
2366 } |
| |
2367 |
| |
2368 if (name) |
| |
2369 remainingNames.remove(name); |
| |
2370 } |
| |
2371 |
| |
2372 if (script->hasObjects()) { |
| |
2373 ObjectArray *objects = script->objects(); |
| |
2374 for (size_t i = 0; i < objects->length; i++) { |
| |
2375 JSObject *obj = objects->vector[i]; |
| |
2376 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { |
| |
2377 JSFunction *fun = &obj->as<JSFunction>(); |
| |
2378 RootedScript innerScript(cx, fun->getOrCreateScript(cx)); |
| |
2379 if (!innerScript) |
| |
2380 return false; |
| |
2381 |
| |
2382 if (!RemoveReferencedNames(cx, innerScript, remainingNames)) |
| |
2383 return false; |
| |
2384 } |
| |
2385 } |
| |
2386 } |
| |
2387 |
| |
2388 return true; |
| |
2389 } |
| |
2390 |
| |
2391 static bool |
| |
2392 AnalyzeEntrainedVariablesInScript(JSContext *cx, HandleScript script, HandleScript innerScript) |
| |
2393 { |
| |
2394 PropertyNameSet remainingNames(cx); |
| |
2395 if (!remainingNames.init()) |
| |
2396 return false; |
| |
2397 |
| |
2398 for (BindingIter bi(script); bi; bi++) { |
| |
2399 if (bi->aliased()) { |
| |
2400 PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name()); |
| |
2401 if (!p && !remainingNames.add(p, bi->name())) |
| |
2402 return false; |
| |
2403 } |
| |
2404 } |
| |
2405 |
| |
2406 if (!RemoveReferencedNames(cx, innerScript, remainingNames)) |
| |
2407 return false; |
| |
2408 |
| |
2409 if (!remainingNames.empty()) { |
| |
2410 Sprinter buf(cx); |
| |
2411 if (!buf.init()) |
| |
2412 return false; |
| |
2413 |
| |
2414 buf.printf("Script "); |
| |
2415 |
| |
2416 if (JSAtom *name = script->functionNonDelazifying()->displayAtom()) { |
| |
2417 buf.putString(name); |
| |
2418 buf.printf(" "); |
| |
2419 } |
| |
2420 |
| |
2421 buf.printf("(%s:%d) has variables entrained by ", script->filename(), script->lineno()); |
| |
2422 |
| |
2423 if (JSAtom *name = innerScript->functionNonDelazifying()->displayAtom()) { |
| |
2424 buf.putString(name); |
| |
2425 buf.printf(" "); |
| |
2426 } |
| |
2427 |
| |
2428 buf.printf("(%s:%d) ::", innerScript->filename(), innerScript->lineno()); |
| |
2429 |
| |
2430 for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) { |
| |
2431 buf.printf(" "); |
| |
2432 buf.putString(r.front()); |
| |
2433 } |
| |
2434 |
| |
2435 printf("%s\n", buf.string()); |
| |
2436 } |
| |
2437 |
| |
2438 if (innerScript->hasObjects()) { |
| |
2439 ObjectArray *objects = innerScript->objects(); |
| |
2440 for (size_t i = 0; i < objects->length; i++) { |
| |
2441 JSObject *obj = objects->vector[i]; |
| |
2442 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { |
| |
2443 JSFunction *fun = &obj->as<JSFunction>(); |
| |
2444 RootedScript innerInnerScript(cx, fun->getOrCreateScript(cx)); |
| |
2445 if (!innerInnerScript || |
| |
2446 !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) |
| |
2447 { |
| |
2448 return false; |
| |
2449 } |
| |
2450 } |
| |
2451 } |
| |
2452 } |
| |
2453 |
| |
2454 return true; |
| |
2455 } |
| |
2456 |
| |
2457 // Look for local variables in script or any other script inner to it, which are |
| |
2458 // part of the script's call object and are unnecessarily entrained by their own |
| |
2459 // inner scripts which do not refer to those variables. An example is: |
| |
2460 // |
| |
2461 // function foo() { |
| |
2462 // var a, b; |
| |
2463 // function bar() { return a; } |
| |
2464 // function baz() { return b; } |
| |
2465 // } |
| |
2466 // |
| |
2467 // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|. |
| |
2468 bool |
| |
2469 js::AnalyzeEntrainedVariables(JSContext *cx, HandleScript script) |
| |
2470 { |
| |
2471 if (!script->hasObjects()) |
| |
2472 return true; |
| |
2473 |
| |
2474 ObjectArray *objects = script->objects(); |
| |
2475 for (size_t i = 0; i < objects->length; i++) { |
| |
2476 JSObject *obj = objects->vector[i]; |
| |
2477 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { |
| |
2478 JSFunction *fun = &obj->as<JSFunction>(); |
| |
2479 RootedScript innerScript(cx, fun->getOrCreateScript(cx)); |
| |
2480 if (!innerScript) |
| |
2481 return false; |
| |
2482 |
| |
2483 if (script->functionDelazifying() && script->functionDelazifying()->isHeavyweight()) { |
| |
2484 if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript)) |
| |
2485 return false; |
| |
2486 } |
| |
2487 |
| |
2488 if (!AnalyzeEntrainedVariables(cx, innerScript)) |
| |
2489 return false; |
| |
2490 } |
| |
2491 } |
| |
2492 |
| |
2493 return true; |
| |
2494 } |
| |
2495 |
| |
2496 #endif |