Symbol.hasInstance
Symbol.hasInstance
In this post I will discuss Symbol.hasInstance
which is the first of 11 well-known symbols provided by the ES6 specification. Well-known symbols, for those unfamiliar, are provided by JavaScript as properties on an object which let you re-configure how ES6 treats the object. We will see how they are used in a minute.
Which native functions make use of Symbol.hasInstance
?
The short answer is that the native instanceof
function holds the sole rights to Symbol.hasInstance
. Symbol.hasInstance
in turn lets you customize and modify the behavior of the instanceof
method. Let’s review the basics of instanceof
briefly…
1 2 3 4 5 6 7 8 9 10 |
// A brief review of the native instanceof function const obj1 = {}; obj1 instanceof Object; // true function A(){}; function B(){}; const obj2 = new A(); obj2 instanceof A; // true obj2 instanceof B; // false |
How is instanceof
implemented?
instanceof
is a method available to nearly every function. Objects in JavaScript are linked via a prototype chain. Therefore a method that is a metaphorical “child” of its parent does not inherit the properties of the parent but rather contains a link to the parent’s prototype. As such, instanceof
compares the left-hand of its operand to the right-hand and determines if in the entire prototype chain of the left side, does the right-side’s prototype appear. It does this by querying the property Symbol.hasInstance
placed on the prototypes of Objects.
instanceof
‘s low-level implementation is via a Symbol.hasInstance
property placed on native functions of an Object. Symbol.hasInstance
is referred to as @@hasInstance
for short in the ES6 spec. To implement instanceof
, child instanceof parent
evaluates according to the spec to parent[@@hasInstance](child)
and when reduced further essentially evaluates to parent[Symbol.hasInstance](child)
. So in short instanceof
behind the scenes is implemented through querying the property Symbol.hasInstance
which happens to be the topic of this post. instanceof
is actually the only native function to make use of Symbol.hasInstance
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// a demonstration of the low-level implementation of instanceof // basic example function MyObj(); Object[Symbol.hasInstance](MyObj); // true // full example function MyObj() {}; const obj = new MyObj(); const obj2 = new Number(); MyObj[Symbol.hasInstance](obj); // true MyObj[Symbol.hasInstance](obj2); // false obj instanceof MyObj; // true obj2 instanceof MyObj; // false |
Here is a piece describing what we just discussed from the ES6 spec:
This (i.e.
Function.prototype[@@hasInstance](V)
) is the default implementation of@@hasInstance
that most functions inherit.@@hasInstance
is called by theinstanceof
operator to determine whether a value is an instance of a specific constructor. An expression such as
v instanceof F
evaluates as
F[@@hasInstance](v)
. . . . . .
The value of thename
property of this function (i.e.@@hasInstance
) is"[Symbol.hasInstance]"
.
Note: I know there is a trend to use Object.create()
instead of new
; however, this will not work as intended with instanceof
since the left-side of instanceof
is intended to be an object and the right-side a function. The Object.create()
design pattern means that both the left and right-side will be objects which throws an error. If you plan on using instanceof
stick to function name()
and new
instantiation as in the examples in this post. Here’s an example of the error thrown with the Object.create()
design pattern.
1 2 3 4 5 |
// Object.create() design pattern is not compatible with instanceof since instanceof requires a function and an object, versus 2 objects via Object.create() MyObject = {}; const obj = Object.create( MyObject ); obj instanceof MyObject; // TypeError: Right-hand side of 'instanceof' is not callable |
What are some use cases for Symbol.hasInstance
?
Here are two examples, the first is basic and less useful while the second is a more practical application…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * A basic example of overriding Symbol.hasInstance */ function MyObject() {}; Object.defineProperty(MyObject, Symbol.hasInstance, { value(v) { return false; } }); const obj = new MyObject; obj instanceof MyObject; // false /** * A more practical example of overriding Symbol.hasInstance */ function ValidNumber() {}; Object.defineProperty(ValidNumber, Symbol.hasInstance, { value(v) { return (v instanceof Number) && (v >=1 && v <= 100); } }); let ten = new Number(10), zero = new Number(0); ten instanceof ValidNumber; // true zero instanceof ValidNumber; // false |
I can’t say those examples are extremely practical, but it’s nice to know how to overwrite Symbol.hasInstance
. If you have a more practical use-case please comment below!
Exploiting Symbol.hasInstance
for malicious purposes
Symbol.hasInstance
is a powerful property and one that can be exploited for nefarious purposes. Let’s explore how to exploit Symbol.hasInstance
to expose the scope of private functions, and also how to modify instanceof
to always return true
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// Malicious exploits of Symbol.hasInstance // Our malicious code. (function(){ // By altering Function.prototype we intercept any call to Symbol.hasInstance! Object.defineProperty(Function.prototype, Symbol.hasInstance, { value: function(instance){ // we grab the private scope to use whenever we feel like it const context = this; // we make instanceof always return true (which will likely break lots of external code) return true; }, }); })(); // An external library we'll exploit. (function(){ function Private(){} /** * bind() traditionally cloaks the target function. In our case our malicious code will intercept it! */ const Public = Private.bind(null); /** * instanceof calls Function.prototype[Symbol.hasInstance] which we redefined * to intercept the Private function's `this`. Additionally, we also manipulated * instanceof to always return true, making this code incorrectly return true */ console.log({} instanceof Public); // true, should be false! })(); |
Unfortunately for the black-hatters among us, ECMAScript figured out this exploit before it was released and made Function.prototype[Symbol.hasInstance]
non-writable and non-configurable. So says the spec:
This property (referring to Function.prototype[Symbol.hasInstance]) is non-writable and non-configurable to prevent tampering that could be used to globally expose the target function of a bound function.
This makes using Object.defineProperty()
to redefine Function.prototype
throw TypeError: Cannot redefine property: Symbol(Symbol.hasInstance)
saving the day and countless code!
That’s it for now. There’s plenty more to write regarding the use of Object.setPrototypeOf
and Object.getPrototypeOf
instead of and perhaps preferable to instanceof
. There’s also potentially more to add regarding prototypes in general. Perhaps I’ll add those as another post or tack on to this one!
Thanks for reading!