|
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 |