By elf | 1/26/2017 | General |Advanced

Breaking Private Scope in JavaScript

Breaking Private Scope in JavaScript

A common way to simulate private variables in JavaScript object "namespaces" is by something like the following:

 

var someObj = (function () {
    var privateCount = 0;
    return {
        incCount : function() { return ++privateCount; },
        decCount : function() { return --privateCount; }
    };
}

 

privateCount is within the scope of the returned object (and its contents), so this works as expected:

 

someObj.incCount();  // returns 1
someObj.decCount();  // returns 0

 

Likewise, because privateCount itself is not one of the properties, these will fail:

 

someObj.privateCount;  // returns undefined

someObj.queryCount = function() { return privateCount; };
someObj.queryCount();  // Exception: ReferenceError: count is not defined 

 

One might think that something like this would work:

 

someObj.privateCount = 3;
someObj.incCount();  // returns 1 ???

 

The newly defined property of privateCount is completely irrelevant to the previously defined functions.

 

So how can new functions be added to access the so-called "private" variables?

 

Introducing eval

 

eval is a function that evaluates its string argument as code.  This is an incredibly powerful capability, but at first glance it doesn't help...

 

eval("someObj.queryCount = function() { return privateCount; };"); 
someObj.queryCount();  // same as before

 

What happens, though, if the original definition looks something like this?

 

var otherObj = (function () {
    var privateCount = 0;
    return {
        incCount : function() { return ++privateCount; },
        decCount : function() { return --privateCount; },
        newFunc: function(n,e) { eval("this." + n + " = " + e + ";"); }
    };
})();

 

These work as before, of course:

 

otherObj.incCount();  // returns 1
otherObj.decCount();  // returns 0

 

Let's change the definition of queryCount:

 

otherObj.newFunc("queryCount", "function() { return privateCount; }");
otherObj.incCount();    // returns 1
otherObj.queryCount();  // returns 1 !!!

 

This works (even in strict mode)!  Why?  eval evaluates its arguments in the local scope of its caller.  Since the eval call itself is within scope of the so-called "private" variables, the evaluated code string can reference them.

 

 

Introducing with

 

Note that this only addresses the concept of breaking into "privately" scoped variables with new functions.  It does not allow the arbitrary addition of new "private" variables, so this will fail:

 

var errObj = (function() {
    var privateCount = 0;
    return {
        incCount : function() { return ++privateCount; },
        decCount : function() { return --privateCount;  },
        errCount : function() { return privateErr; },  // look here
        newFunc: function(n,e) { eval("this." + n + " = " + e + ";"); }
    };
})();

errObj.errCount(); // error: privateErr is not defined

 

Let's try the previous trick:

errObj.newFunc("privateErr", "0");
errObj.errCount();  // undefined: privateErr is not defined ???

 

The property added is in a scope lower than that of the errCount function, so it still fails.

 

However, we can do a full redefinition, carefully:

 

var errObj = (function () {
  var wFunc = function(s) { eval("with (this) { " + s + "};"); };
  var pCount = 0;
  return {
    incCount : function () { return ++pCount; },
    decCount : function () { return --pCount; },
    errCount : function () { return rCount; },
    errInc  : function() { return ++rCount; },
    newFunc : function (n,s) { eval("this." + n + " = " + s + ";"); },
    withFunc : wFunc
  };
})();

errObj.withFunc("var rCount = 0; this.errCount = function() { return rCount; };");
errObj.errCount(); // returns 0 !

 

This will return 0, as expected. But what of the errInc() function?

 

errObj.errInc(); // Exception: ReferenceError: rCount is not defined

 

What is going on? with adds its argument to the top of the lookup chain when evaluating its argument. So, essentially, the withFunc() is adding the top level object into the chain, which then defines a scope with the rCount variable and a new (overridden) definition of errCount(). While rCount is in scope for errCount(), and errCount() redefines the errCount() function found later through the explicit this, rCount will still not be visible to errInc(), being defined in a separate scope.

 

By playing with the power of eval in conjunction with with, we have a solution:

 

var errObj = (function () {
  var wFunc = function(s) { eval("with (this) { " + s + "};"); };
  var pCount = 0;
  return {
    incCount : function () { return ++pCount; },
    decCount : function () { return --pCount; },
    errCount : function () { return rCount; }, 
    incErr   : function () { return ++rCount; },
    newFunc : function(s) { eval(s); },
    withFunc : wFunc
  };
})();

errObj.newFunc("this.withFunc(\"rCount = 0;\");");
errObj.errCount();  // returns 0
errObj.incErr();    // returns 1 !!
errObj.errCount();  // returns 1 !!

 

 

Let's trace what's going on. newFunc(), in the scope of the return object, is evaluating withFunc("rCount = 0"). So the this object is pointing to the returned object of errObj, which is the top scope in which this new variable (rCount) is defined. The new scope is block-visible to the returned errObj. That it is a new scope is important; it is NOT visible in or to the environment of pCount.  Essentially, we've just tacked on a new private environment to the errObj, distinct from its normal "private" enclosing environment.

 

We can demonstrate that it is indeed still private with a simple call:

 

errObj.rCount;  // undefined

 

And we can demonstrate that new functions can be added to it in the same way as before:

errObj.newFunc("this.decErr = function() { return --rCount; };");
errObj.decErr();  // returns 0

 

As far as I am aware, it is not possible to add new private variables to an existing private scope.  Please note in the comments below if there is a solution of which I am not aware or if the explanation was unclear.

 

 

Conclusion - Safety and Uses

 

NEVER use this in production code.  This is precisely the concept of arbitrary code execution with the hook already open and free for use.  Why mention it, then?  This can give some amazing debugging, testing and augmentation functionality. This lets QA figure out solutions to the problems they find. This lets coders figure out their own bugs and be able to test a fix on the live (development) environment without constant trivial commits. This lets optimisation geeks be able to hook into the running system and time and test alternatives, again, without pushing commits. It's incredibly powerful. Don't misuse it, and again, NEVER use it in production code - don't let anyone else misuse it either.

 

For more detailed information on eval and with, please look at the MDN documentation or the ECMAscript standard.

 

Please feel free to add other solutions and uses below in the comments. Thanks!

 

By elf | 1/26/2017 | General

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now