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