diff --git a/lib/member_builder.js b/lib/member_builder.js index d3c5412..53ba11b 100644 --- a/lib/member_builder.js +++ b/lib/member_builder.js @@ -22,7 +22,8 @@ * @package core */ -var util = require( './util' ), +var util = require( __dirname + '/util' ), + fallback = util.definePropertyFallback(), visibility = [ 'public', 'protected', 'private' ]; @@ -361,11 +362,18 @@ function overrideMethod( super_method, new_method, instCallback, cid ) retval = undefined ; - // the _super property will contain the parent method - this.__super = super_method; + // the _super property will contain the parent method (we don't + // store the previous value for performance reasons and because, + // during conventional use, it's completely unnecessary) + context.__super = super_method; retval = new_method.apply( context, arguments ); + // prevent sneaky bastards from breaking encapsulation by stealing + // method references (we set to undefined rather than deleting it + // because deletion causes performance degradation within V8) + context.__super = undefined; + // if the value returned from the method was the context that we // passed in, return the actual instance (to ensure we do not break // encapsulation) diff --git a/test/test-member_builder-method.js b/test/test-member_builder-method.js index 3156dd4..80721f9 100644 --- a/test/test-member_builder-method.js +++ b/test/test-member_builder-method.js @@ -24,12 +24,13 @@ var common = require( './common' ), assert = require( 'assert' ), - mb_common = require( __dirname + '/inc-member_builder-common' ) + mb_common = require( __dirname + '/inc-member_builder-common' ), + builder = common.require( 'member_builder' ) ; mb_common.funcVal = 'foobar'; mb_common.value = function() { return mb_common.funcVal; }; -mb_common.buildMember = common.require( 'member_builder' ).buildMethod; +mb_common.buildMember = builder.buildMethod; // do assertions common to all member builders mb_common.assertCommon(); @@ -137,6 +138,57 @@ mb_common.assertCommon(); } )(); +/** + * If the method is called when bound to a different context (e.g. for + * protected/private members), __super may not be properly bound. + * + * This test is in response to a bug found after implementing visibility + * support. The __super() method was previously defined on 'this', which may or + * may not be the context that is actually used. Likely, it's not. + */ +( function testSuperMethodWorksProperlyWhenContextDiffers() +{ + var members = builder.initMembers(), + super_called = false, + retobj = {}, + instCallback = function() + { + return retobj; + }, + + // the overriding method + newfunc = function() + { + this.__super(); + } + ; + + // super method to be overridden + members[ 'public' ].foo = function() + { + super_called = true; + }; + + // override + builder.buildMethod( members, {}, 'foo', newfunc, {}, instCallback ); + + // call the overriding method + members[ 'public' ].foo(); + + // ensure that the super method was called + assert.equal( super_called, true, + "__super() method is called even when context differs" + ); + + // finally, ensure that __super is no longer set on the returned object + // after the call to ensure that the caller cannot break encapsulation by + // stealing a method reference (sneaky, sneaky) + assert.equal( retobj.__super, undefined, + "__super() method is unset after being called" + ); +} )(); + + /** * Once a concrete implementation has been defined for a method, a subtype * cannot make it abstract.