Bug 554955: Give blocks and call objects unique shapes when they have parents that may be extended with new bindings. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Mon, 31 Jan 2011 12:08:13 -0800
changeset 61701 297b1312f534a4c610c4d987a7faa394bb84a898
parent 61700 18a1effafe19ab82f760864f1757e63ac2171b31
child 61702 62ba32799f6fc8863358334a9d1ae2e2aeb51957
push idunknown
push userunknown
push dateunknown
reviewersjorendorff
bugs554955
milestone2.0b11pre
Bug 554955: Give blocks and call objects unique shapes when they have parents that may be extended with new bindings. r=jorendorff The comments for js::Bindings::extensibleParents explain why this is necessary. AssertValidPropertyCacheHit should have been catching this bug, but for reasons I don't understand, it is restricted from checking this case. This patch extends it to assert when the bug is detected. I've gathered the infallible parts of the initialization for Call objects and cloned block objects into their own functions.
js/src/jsemit.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jspropertycache.cpp
js/src/jsprvtd.h
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/tests/js1_8_5/regress/jstests.list
js/src/tests/js1_8_5/regress/regress-554955-1.js
js/src/tests/js1_8_5/regress/regress-554955-2.js
js/src/tests/js1_8_5/regress/regress-554955-3.js
js/src/tests/js1_8_5/regress/regress-554955-4.js
js/src/tests/js1_8_5/regress/regress-554955-5.js
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -3661,19 +3661,40 @@ js_EmitFunctionScript(JSContext *cx, JSC
 
     if (cg->flags & TCF_FUN_UNBRAND_THIS) {
         CG_SWITCH_TO_PROLOG(cg);
         if (js_Emit1(cx, cg, JSOP_UNBRANDTHIS) < 0)
             return false;
         CG_SWITCH_TO_MAIN(cg);
     }
 
-    return js_EmitTree(cx, cg, body) &&
-           js_Emit1(cx, cg, JSOP_STOP) >= 0 &&
-           JSScript::NewScriptFromCG(cx, cg);
+    if (!js_EmitTree(cx, cg, body) ||
+        js_Emit1(cx, cg, JSOP_STOP) < 0 ||
+        !JSScript::NewScriptFromCG(cx, cg)) {
+        return false;
+    }
+
+    JSFunction *fun = cg->fun();
+    if (fun->script()->bindings.extensibleParents()) {
+        /*
+         * Since the function has extensible parents, its blocks need unique
+         * shapes. See the comments for js::Bindings::extensibleParents.
+         */
+        JSScript *script = FUN_SCRIPT(fun);
+        if (JSScript::isValidOffset(script->objectsOffset)) {
+            JSObjectArray *objects = FUN_SCRIPT(fun)->objects();
+            for (uint32 i = 0; i < objects->length; i++) {
+                JSObject *obj = objects->vector[i];
+                if (obj->isBlock())
+                    obj->setBlockOwnShape(cx);
+            }
+        }
+    }
+
+    return true;
 }
 
 /* A macro for inlining at the top of js_EmitTree (whence it came). */
 #define UPDATE_LINE_NUMBER_NOTES(cx, cg, line)                                \
     JS_BEGIN_MACRO                                                            \
         uintN line_ = (line);                                                 \
         uintN delta_ = line_ - CG_CURRENT_LINE(cg);                           \
         if (delta_ != 0) {                                                    \
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -953,18 +953,17 @@ NewCallObject(JSContext *cx, Bindings *b
     size_t slots = JSObject::CALL_RESERVED_SLOTS + argsVars;
     gc::FinalizeKind kind = gc::GetGCObjectKind(slots);
 
     JSObject *callobj = js_NewGCObject(cx, kind);
     if (!callobj)
         return NULL;
 
     /* Init immediately to avoid GC seeing a half-init'ed object. */
-    callobj->init(cx, &js_CallClass, NULL, &scopeChain, NULL, false);
-    callobj->setMap(bindings->lastShape());
+    callobj->initCall(cx, bindings, &scopeChain);
 
     /* This must come after callobj->lastProp has been set. */
     if (!callobj->ensureInstanceReservedSlots(cx, argsVars))
         return NULL;
 
 #ifdef DEBUG
     for (Shape::Range r = callobj->lastProp; !r.empty(); r.popFront()) {
         const Shape &s = r.front();
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -91,17 +91,17 @@
 #define JSFUN_PROTOTYPE     0x0800  /* function is Function.prototype for some
                                        global object */
 
 #define JSFUN_EXPR_CLOSURE  0x1000  /* expression closure: function(x) x*x */
 #define JSFUN_TRCINFO       0x2000  /* when set, u.n.trcinfo is non-null,
                                        JSFunctionSpec::call points to a
                                        JSNativeTraceInfo. */
 #define JSFUN_INTERPRETED   0x4000  /* use u.i if kind >= this value else u.n */
-#define JSFUN_FLAT_CLOSURE  0x8000  /* flag (aka "display") closure */
+#define JSFUN_FLAT_CLOSURE  0x8000  /* flat (aka "display") closure */
 #define JSFUN_NULL_CLOSURE  0xc000  /* null closure entrains no scope chain */
 #define JSFUN_KINDMASK      0xc000  /* encode interp vs. native and closure
                                        optimization level -- see above */
 
 #define FUN_OBJECT(fun)      (static_cast<JSObject *>(fun))
 #define FUN_KIND(fun)        ((fun)->flags & JSFUN_KINDMASK)
 #define FUN_SET_KIND(fun,k)  ((fun)->flags = ((fun)->flags & ~JSFUN_KINDMASK) | (k))
 #define FUN_INTERPRETED(fun) (FUN_KIND(fun) >= JSFUN_INTERPRETED)
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2085,17 +2085,17 @@ AssertValidPropertyCacheHit(JSContext *c
     if (JOF_OPMODE(*regs.pc) == JOF_NAME) {
         ok = js_FindProperty(cx, ATOM_TO_JSID(atom), &obj, &pobj, &prop);
     } else {
         obj = start;
         ok = js_LookupProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop);
     }
     if (!ok)
         return false;
-    if (cx->runtime->gcNumber != sample || entry->vshape() != pobj->shape())
+    if (cx->runtime->gcNumber != sample)
         return true;
     JS_ASSERT(prop);
     JS_ASSERT(pobj == found);
 
     const Shape *shape = (Shape *) prop;
     if (entry->vword.isSlot()) {
         JS_ASSERT(entry->vword.toSlot() == shape->slot);
         JS_ASSERT(!shape->isMethod());
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3259,19 +3259,18 @@ js_CloneBlockObject(JSContext *cx, JSObj
 
     JSObject *clone = js_NewGCObject(cx, kind);
     if (!clone)
         return NULL;
 
     JSStackFrame *priv = js_FloatingFrameIfGenerator(cx, fp);
 
     /* The caller sets parent on its own. */
-    clone->init(cx, &js_BlockClass, proto, NULL, priv, false);
-
-    clone->setMap(proto->map);
+    clone->initClonedBlock(cx, proto, priv);
+
     if (!clone->ensureInstanceReservedSlots(cx, count + 1))
         return NULL;
 
     clone->setSlot(JSSLOT_BLOCK_DEPTH, proto->getSlot(JSSLOT_BLOCK_DEPTH));
 
     JS_ASSERT(clone->isClonedBlock());
     return clone;
 }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -479,16 +479,21 @@ struct JSObject : js::gc::Cell {
         map = amap;
         objShape = map->shape;
     }
 
     void setSharedNonNativeMap() {
         setMap(const_cast<JSObjectMap *>(&JSObjectMap::sharedNonNative));
     }
 
+    /* Functions for setting up scope chain object maps and shapes. */
+    void initCall(JSContext *cx, const js::Bindings *bindings, JSObject *parent);
+    void initClonedBlock(JSContext *cx, JSObject *proto, JSStackFrame *priv);
+    void setBlockOwnShape(JSContext *cx);
+
     void deletingShapeChange(JSContext *cx, const js::Shape &shape);
     const js::Shape *methodShapeChange(JSContext *cx, const js::Shape &shape);
     bool methodShapeChange(JSContext *cx, uint32 slot);
     void protoShapeChange(JSContext *cx);
     void shadowingShapeChange(JSContext *cx, const js::Shape &shape);
     bool globalObjectOwnShapeChange(JSContext *cx);
 
     void extensibleShapeChange(JSContext *cx) {
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -53,16 +53,17 @@
 #include "jsstaticcheck.h"
 #include "jsxml.h"
 
 /* Headers included for inline implementations used by this header. */
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsnum.h"
 #include "jsscopeinlines.h"
+#include "jsscriptinlines.h"
 #include "jsstr.h"
 
 #include "jsgcinlines.h"
 #include "jsprobes.h"
 
 inline bool
 JSObject::preventExtensions(JSContext *cx, js::AutoIdVector *props)
 {
@@ -134,16 +135,69 @@ JSObject::finalize(JSContext *cx)
     if (clasp->finalize)
         clasp->finalize(cx, this);
 
     js::Probes::finalizeObject(this);
 
     finish(cx);
 }
 
+/* 
+ * Initializer for Call objects for functions and eval frames. Set class,
+ * parent, map, and shape, and allocate slots.
+ */
+inline void
+JSObject::initCall(JSContext *cx, const js::Bindings *bindings, JSObject *parent)
+{
+    init(cx, &js_CallClass, NULL, parent, NULL, false);
+    map = bindings->lastShape();
+
+    /*
+     * If |bindings| is for a function that has extensible parents, that means
+     * its Call should have its own shape; see js::Bindings::extensibleParents.
+     */
+    if (bindings->extensibleParents())
+        setOwnShape(js_GenerateShape(cx, false));
+    else
+        objShape = map->shape;
+}
+
+/*
+ * Initializer for cloned block objects. Set class, prototype, frame, map, and
+ * shape.
+ */
+inline void
+JSObject::initClonedBlock(JSContext *cx, JSObject *proto, JSStackFrame *frame)
+{
+    init(cx, &js_BlockClass, proto, NULL, frame, false);
+
+    /* Cloned blocks copy their prototype's map; it had better be shareable. */
+    JS_ASSERT(!proto->inDictionaryMode() || proto->lastProp->frozen());
+    map = proto->map;
+
+    /*
+     * If the prototype has its own shape, that means the clone should, too; see
+     * js::Bindings::extensibleParents.
+     */
+    if (proto->hasOwnShape())
+        setOwnShape(js_GenerateShape(cx, false));
+    else
+        objShape = map->shape;
+}
+
+/* 
+ * Mark a compile-time block as OWN_SHAPE, indicating that its run-time clones
+ * also need unique shapes. See js::Bindings::extensibleParents.
+ */
+inline void
+JSObject::setBlockOwnShape(JSContext *cx) {
+    JS_ASSERT(isStaticBlock());
+    setOwnShape(js_GenerateShape(cx, false));
+}
+
 /*
  * Property read barrier for deferred cloning of compiler-created function
  * objects optimized as typically non-escaping, ad-hoc methods in obj.
  */
 inline bool
 JSObject::methodReadBarrier(JSContext *cx, const js::Shape &shape, js::Value *vp)
 {
     JS_ASSERT(canHaveMethodBarrier());
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -318,16 +318,33 @@ JSFunctionBox::inAnyDynamicScope() const
     for (const JSFunctionBox *funbox = this; funbox; funbox = funbox->parent) {
         if (funbox->tcflags & (TCF_IN_WITH | TCF_FUN_CALLS_EVAL))
             return true;
     }
     return false;
 }
 
 bool
+JSFunctionBox::scopeIsExtensible() const
+{
+    /* Direct eval calls can add bindings, but not in strict mode functions. */
+    if ((tcflags & TCF_FUN_CALLS_EVAL) && !(tcflags & TCF_STRICT_MODE_CODE))
+        return true;
+
+    /* 
+     * Function statements also extend call objects. (We forbid function
+     * statements in strict mode code.)
+     */
+    if (tcflags & TCF_HAS_FUNCTION_STMT)
+        return true;
+
+    return false;
+}
+
+bool
 JSFunctionBox::shouldUnbrand(uintN methods, uintN slowMethods) const
 {
     if (slowMethods != 0) {
         for (const JSFunctionBox *funbox = this; funbox; funbox = funbox->parent) {
             if (!(funbox->tcflags & TCF_FUN_MODULE_PATTERN))
                 return true;
             if (funbox->inLoop)
                 return true;
@@ -2022,16 +2039,17 @@ MatchOrInsertSemicolon(JSContext *cx, To
 bool
 Parser::analyzeFunctions(JSTreeContext *tc)
 {
     cleanFunctionList(&tc->functionList);
     if (!tc->functionList)
         return true;
     if (!markFunArgs(tc->functionList))
         return false;
+    markExtensibleScopeDescendants(tc->functionList, false);
     setFunctionKinds(tc->functionList, &tc->flags);
     return true;
 }
 
 /*
  * Mark as funargs any functions that reach up to one or more upvars across an
  * already-known funarg. The parser will flag the o_m lambda as a funarg in:
  *
@@ -2628,16 +2646,47 @@ Parser::setFunctionKinds(JSFunctionBox *
         funbox = funbox->siblings;
         if (!funbox)
             break;
     }
 
 #undef FUN_METER
 }
 
+/*
+ * Walk the JSFunctionBox tree looking for functions whose call objects may
+ * acquire new bindings as they execute: non-strict functions that call eval,
+ * and functions that contain function statements (definitions not appearing
+ * within the top statement list, which don't take effect unless they are
+ * evaluated). Enclosed functions that could refer to bindings these extensible
+ * call objects can shadow must have their bindings' extensibleParents flags
+ * set, and such compiler-created blocks must have their OWN_SHAPE flags set;
+ * the comments for js::Bindings::extensibleParents explain why.
+ */
+void
+Parser::markExtensibleScopeDescendants(JSFunctionBox *funbox, bool hasExtensibleParent) 
+{
+    for (; funbox; funbox = funbox->siblings) {
+        /*
+         * It would be nice to use FUN_KIND(fun) here to recognize functions
+         * that will never consult their parent chains, and thus don't need
+         * their 'extensible parents' flag set. Filed as bug 619750. 
+         */
+
+        JS_ASSERT(!funbox->bindings.extensibleParents());
+        if (hasExtensibleParent)
+            funbox->bindings.setExtensibleParents();
+
+        if (funbox->kids) {
+            markExtensibleScopeDescendants(funbox->kids,
+                                           hasExtensibleParent || funbox->scopeIsExtensible());
+        }
+    }
+}
+
 const char js_argument_str[] = "argument";
 const char js_variable_str[] = "variable";
 const char js_unknown_str[]  = "unknown";
 
 const char *
 JSDefinition::kindString(Kind kind)
 {
     static const char *table[] = {
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -979,16 +979,22 @@ struct JSFunctionBox : public JSObjectBo
     bool joinable() const;
 
     /*
      * True if this function is inside the scope of a with-statement, an E4X
      * filter-expression, or a function that uses direct eval.
      */
     bool inAnyDynamicScope() const;
 
+    /* 
+     * Must this function's descendants be marked as having an extensible
+     * ancestor?
+     */
+    bool scopeIsExtensible() const;
+
     /*
      * Unbrand an object being initialized or constructed if any method cannot
      * be joined to one compiler-created null closure shared among N different
      * closure environments.
      *
      * We despecialize from caching function objects, caching slots or shapes
      * instead, because an unbranded object may still have joined methods (for
      * which shape->isMethod), since PropertyCache::fill gives precedence to
@@ -1107,16 +1113,17 @@ struct Parser : private js::AutoGCRooter
     /*
      * Analyze the tree of functions nested within a single compilation unit,
      * starting at funbox, recursively walking its kids, then following its
      * siblings, their kids, etc.
      */
     bool analyzeFunctions(JSTreeContext *tc);
     void cleanFunctionList(JSFunctionBox **funbox);
     bool markFunArgs(JSFunctionBox *funbox);
+    void markExtensibleScopeDescendants(JSFunctionBox *funbox, bool hasExtensibleParent);
     void setFunctionKinds(JSFunctionBox *funbox, uint32 *tcflags);
 
     void trace(JSTracer *trc);
 
     /*
      * Report a parse (compile) error.
      */
     inline bool reportErrorNumber(JSParseNode *pn, uintN flags, uintN errorNumber, ...);
--- a/js/src/jspropertycache.cpp
+++ b/js/src/jspropertycache.cpp
@@ -56,16 +56,17 @@ PropertyCache::fill(JSContext *cx, JSObj
     jsuword kshape, vshape;
     JSOp op;
     const JSCodeSpec *cs;
     PCVal vword;
     PropertyCacheEntry *entry;
 
     JS_ASSERT(this == &JS_PROPERTY_CACHE(cx));
     JS_ASSERT(!cx->runtime->gcRunning);
+    JS_ASSERT_IF(adding, !obj->isCall());
 
     if (js_IsPropertyCacheDisabled(cx)) {
         PCMETER(disfills++);
         return JS_NO_PROP_CACHE_FILL;
     }
 
     /*
      * Check for fill from js_SetPropertyHelper where the setter removed shape
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -166,16 +166,17 @@ template <class T,
           class AllocPolicy = ContextAllocPolicy>
 class HashSet;
 
 class PropertyCache;
 struct PropertyCacheEntry;
 
 struct Shape;
 struct EmptyShape;
+struct Bindings;
 
 } /* namespace js */
 
 } /* export "C++" */
 #endif  /* __cplusplus */
 
 /* "Friend" types used by jscntxt.h and jsdbgapi.h. */
 typedef enum JSTrapStatus {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -168,16 +168,17 @@ enum BindingKind { NONE, ARGUMENT, VARIA
  * both function and top-level scripts (the latter is needed to track names in
  * strict mode eval code, to give such code its own lexical environment).
  */
 class Bindings {
     js::Shape *lastBinding;
     uint16 nargs;
     uint16 nvars;
     uint16 nupvars;
+    bool hasExtensibleParents;
 
   public:
     inline Bindings(JSContext *cx);
 
     /*
      * Transfers ownership of bindings data from bindings into this fresh
      * Bindings instance. Once such a transfer occurs, the old bindings must
      * not be used again.
@@ -299,16 +300,53 @@ class Bindings {
     /*
      * Protect stored bindings from mutation.  Subsequent attempts to add
      * bindings will copy the existing bindings before adding to them, allowing
      * the original bindings to be safely shared.
      */
     void makeImmutable();
 
     /*
+     * Sometimes call objects and run-time block objects need unique shapes, but
+     * sometimes they don't.
+     *
+     * Property cache entries only record the shapes of the first and last
+     * objects along the search path, so if the search traverses more than those
+     * two objects, then those first and last shapes must determine the shapes
+     * of everything else along the path. The js_PurgeScopeChain stuff takes
+     * care of making this work, but that suffices only because we require that
+     * start points with the same shape have the same successor object in the
+     * search path --- if two start points have different successors, they must
+     * have different shapes.
+     *
+     * For call and run-time block objects, the "successor" is the scope chain
+     * parent. Unlike prototype objects (of which there are usually few), scope
+     * chain parents are created frequently (possibly on every call), so it's
+     * not too far from optimal to give every call and block its own shape.
+     *
+     * In many cases, however, it's not actually necessary to give call and
+     * block objects their own shapes, so we can do better. If the code will
+     * always be used with the same global object, and none of the enclosing
+     * call objects could have bindings added to them at runtime (by direct eval
+     * calls or function statements), then we can use a fixed set of shapes for
+     * those objects. You could think of the shapes in the functions' bindings
+     * and compile-time blocks as uniquely identifying the global object(s) at
+     * the end of the scope chain.
+     *
+     * (In fact, some JSScripts we do use against multiple global objects (see
+     * bug 618497), and using the fixed shapes isn't sound there.)
+     * 
+     * If the hasExtensibleParents flag is set, then Call objects created for
+     * the function this Bindings describes need unique shapes. If the flag is
+     * clear, then we can use lastBinding's shape.
+     */
+    void setExtensibleParents() { hasExtensibleParents = true; }
+    bool extensibleParents() const { return hasExtensibleParents; }
+
+    /*
      * These methods provide direct access to the shape path normally
      * encapsulated by js::Bindings. These methods may be used to make a
      * Shape::Range for iterating over the relevant shapes from youngest to
      * oldest (i.e., last or right-most to first or left-most in source order).
      *
      * Sometimes iteration order must be from oldest to youngest, however. For
      * such cases, use js::Bindings::getLocalNameArray. The RAII class
      * js::AutoLocalNameArray, defined in jscntxt.h, should be used where
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -47,17 +47,18 @@
 #include "jsregexp.h"
 #include "jsscript.h"
 #include "jsscope.h"
 
 namespace js {
 
 inline
 Bindings::Bindings(JSContext *cx)
-  : lastBinding(cx->runtime->emptyCallShape), nargs(0), nvars(0), nupvars(0)
+  : lastBinding(cx->runtime->emptyCallShape), nargs(0), nvars(0), nupvars(0),
+    hasExtensibleParents(false)
 {
 }
 
 inline void
 Bindings::transfer(JSContext *cx, Bindings *bindings)
 {
     JS_ASSERT(lastBinding == cx->runtime->emptyCallShape);
 
--- a/js/src/tests/js1_8_5/regress/jstests.list
+++ b/js/src/tests/js1_8_5/regress/jstests.list
@@ -8,16 +8,21 @@ script regress-541255-3.js
 script regress-541255-4.js
 script regress-541455.js
 script regress-546615.js
 script regress-551763-0.js
 script regress-551763-1.js
 script regress-551763-2.js
 script regress-552432.js
 script regress-553778.js
+script regress-554955-1.js
+script regress-554955-2.js
+script regress-554955-3.js
+script regress-554955-4.js
+script regress-554955-5.js
 script regress-555246-0.js
 script regress-555246-1.js
 script regress-559402-1.js
 script regress-559402-2.js
 script regress-559438.js
 script regress-560101.js
 script regress-560998-1.js
 script regress-560998-2.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-554955-1.js
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function f(s) {
+  eval(s);
+  return function(a) {
+    var d;
+    let (c = 3) {
+      d = function() { a; }; // force Block object to be cloned
+      with({}) {}; // repel JägerMonkey
+      return b; // lookup occurs in scope of Block
+    }
+  };
+}
+
+var b = 1;
+var g1 = f("");
+var g2 = f("var b = 2;");
+
+/* Call the lambda once, caching a reference to the global b. */
+g1(0);
+
+/* 
+ * If this call sees the above cache entry, then it will erroneously use the
+ * global b.
+ */
+assertEq(g2(0), 2);
+
+reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-554955-2.js
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function f(s) {
+    eval(s);
+    return function(a) {
+        with({}) {}; // repel JägerMonkey
+        eval(a);
+        return b;
+    };
+}
+
+var b = 1;
+var g1 = f("");
+var g2 = f("var b = 2;");
+
+/* Call the lambda once, caching a reference to the global b. */
+g1('');
+
+/*
+ * If this call sees the above cache entry, then it will erroneously use
+ * the global b.
+ */
+assertEq(g2(''), 2);
+
+reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-554955-3.js
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function f(s) {
+    eval(s);
+    return function(a) {
+        with({}) {}; // repel JägerMonkey
+        eval(a);
+        let (c = 3) {
+            return b;
+        };
+    };
+}
+
+var b = 1;
+var g1 = f("");
+var g2 = f("var b = 2;");
+
+/* Call the lambda once, caching a reference to the global b. */
+g1('');
+
+/* 
+ * If this call sees the above cache entry, then it will erroneously use the
+ * global b.
+ */
+assertEq(g2(''), 2);
+
+reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-554955-4.js
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function f(s) {
+    eval(s);
+    return function(a) {
+        eval(a);
+        let (c = 3) {
+            eval(s);
+            return b;
+        };
+    };
+}
+
+var b = 1;
+var g1 = f('');
+var g2 = f('');
+
+/* Call the lambda once, caching a reference to the global b. */
+g1('');
+
+/*
+ * If this call sees the above cache entry, then it will erroneously use the
+ * global b.
+ */
+assertEq(g2('var b=2'), 2);
+
+reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/regress/regress-554955-5.js
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+function f(s) {
+    if (s) {
+        function b() { }
+    }
+    return function(a) {
+        eval(a);
+        return b;
+    };
+}
+
+var b = 1;
+var g1 = f(false);
+var g2 = f(true);
+
+/* Call the lambda once, caching a reference to the global b. */
+g1('');
+
+/*
+ * If this call sees the above cache entry, then it will erroneously use the
+ * global b.
+ */
+assertEq(typeof g2(''), "function");
+
+reportCompare(true, true);