diff --git a/lib/class_builder.js b/lib/class_builder.js index f8858c0..b65128f 100644 --- a/lib/class_builder.js +++ b/lib/class_builder.js @@ -52,6 +52,15 @@ var util = require( __dirname + '/util' ), */ extending = false, + /** + * A flag to let the system know that we are currently attempting to access + * a static property from within a method. This means that the caller should + * be given access to additional levels of visibility. + * + * @type {boolean} + */ + sprop_internal = false, + /** * Hash of reserved members * @@ -619,6 +628,19 @@ function initStaticVisibilityObj( ctor, static_members ) // override __self on the instance's visibility object, giving internal // methods access to the restricted static methods ctor.___$$svis$$ = sobji; + + // Override the class-level accessor method to allow the system to know we + // are within a method. An internal flag is necessary, rather than using an + // argument or binding, because those two options are exploitable. An + // internal flag cannot be modified by conventional means. + sobji.$ = function() + { + sprop_internal = true; + var val = ctor.$.apply( ctor, arguments ); + sprop_internal = false; + + return val; + }; } @@ -666,17 +688,26 @@ function attachStatic( ctor, members, base, inheriting ) // we use hasOwnProperty to ensure that undefined values will not // cause us to continue checking the parent, thereby potentially // failing to set perfectly legal values - var has = Object.prototype.hasOwnProperty.call( - props[ 'public' ], prop - ), + var has = Object.prototype.hasOwnProperty, + found = false, // Determine if we were invoked in the context of a class. If // so, use that. Otherwise, use ourself. context = ( this.___$$sprops$$ ) ? this : ctor ; + // Attempt to locate the property. First, we check public. If not + // available and we are internal (within a method), we can move on + // to check other levels of visibility. `found` will contain the + // visibility level the property was found in, or false. + found = has.call( props[ 'public' ], prop ) && 'public'; + if ( !found && sprop_internal ) + { + found = has.call( props[ 'protected' ], prop ) && 'protected'; + } + // if we don't own the property, let the parent(s) handle it - if ( !has ) + if ( found === false ) { return base.$.apply( context, arguments ); } @@ -686,13 +717,13 @@ function attachStatic( ctor, members, base, inheriting ) // arguments.length to ensure that setting to undefined works) if ( arguments.length > 1 ) { - props[ 'public' ][ prop ] = val; + props[ found ][ prop ] = val; return context; } else { // return the value - return props[ 'public' ][ prop ]; + return props[ found ][ prop ]; } } ); } diff --git a/test/test-class_builder-static.js b/test/test-class_builder-static.js index 542f5e0..4bb27bc 100644 --- a/test/test-class_builder-static.js +++ b/test/test-class_builder-static.js @@ -464,6 +464,9 @@ var common = require( './common' ), var val = 'foo', Foo = builder.build( { + 'protected static prop': val, + + // the same rules should apply to methods 'protected static baz': function() { @@ -481,6 +484,16 @@ var common = require( './common' ), { return this.__self.baz(); }, + + 'public static staticGetProp': function() + { + return this.$('prop'); + }, + + 'public instGetProp': function() + { + return this.__self.$('prop'); + }, } ); assert.equal( Foo.baz, undefined, @@ -494,6 +507,14 @@ var common = require( './common' ), assert.equal( Foo().instBaz(), val, "Protected methods are accessible to instance methods" ); + + assert.equal( Foo.staticGetProp(), val, + "Protected static properties are accessible to static methods" + ); + + assert.equal( Foo().instGetProp(), val, + "Protected static properties are accessible to instance methods" + ); } )(); @@ -507,6 +528,8 @@ var common = require( './common' ), val2 = 'bazbaz', Foo = builder.build( { + 'protected static prop': val, + 'protected static foo': function() { return val; @@ -529,6 +552,11 @@ var common = require( './common' ), { return this.foo2(); }, + + 'public static getProp': function() + { + return this.$('prop'); + }, } ), SubSubFoo = builder.build( SubFoo, {} ) @@ -546,6 +574,14 @@ var common = require( './common' ), assert.equal( SubSubFoo.bar(), val, "Sub-subtypes inherit parents' protected static methods" ); + + assert.equal( SubFoo.getProp(), val, + "Subtypes inherit parents' protected static properties" + ); + + assert.equal( SubSubFoo.getProp(), val, + "Sub-subtypes inherit parents' protected static properties" + ); } )();