Somehow some days ago I ended up performing one more of those useless programming experiments that still manage to keep me entertained. It's important to understand that the private accessors provided by languages like C# and Java are a compile time mechanism to enforce restrictions on the code that we write, but they should not be confused with a real security mechanism. They can be circumvented at runtime with Reflection, and that's not a flaw in the system, it's that they are not intended to be really "secret". Years ago I wrote about this and presented a trick where Reflection itself was used (by examining the call stack) to prevent this the use of Reflection for invoking private methods. Similar techniques can be used in Java.
As I explain in that post, you can get privacy in JavaScript by means of defining your public methods as closures that trap the private members. This indeed gives you a level of privacy way superior to the one provided by the private accessor in other languages. Anyway, few people found it necessary to mess their code with that technique. The last versions of JavaScript have remained reluctant to providing us with private accessors, but as the TypeScript compiler comes with them and almost everyone is using TypeScript... in a sense it's as if they were already a part of JavaScript...
Notice that getting by TypeScript privacy is even easier than using reflection, all you need is casting to any:
(myInstance as any).privateMethod();
For whatever the reason it came to my mind the idea of making a JavaScript method private at runtime. You have defined the method public and while the application is running you decide that it should go private. The closures trick has to be used when defining the methods in the class, so that's not an option. What we can do is replacing the existing method by a new one that checks the call stack to see if it's being called from another method in the same class and depending on it invokes the original method or throws an exception. Getting the caller of one function in JavaScript used to be quite straightforward, by means of function.caller, but it's not standard and is not available in strict mode. The other option, arguments.caller, is plain obsolete.
There's a pretty inventive solution (of course I did not come up with it, I just googled a bit). When you create an Error object, its stack property gets filled with a string with the call stack information, something like this:
Error at calledFromInsideClass (D:\Main\MyPrograms\programs_JavaScript\PrivatizeMethod\PrivatizeMethod.js:51:23) at City.privatizedMethod (D:\Main\MyPrograms\programs_JavaScript\PrivatizeMethod\PrivatizeMethod.js:60:13) at City.growCity (D:\Main\MyPrograms\programs_JavaScript\PrivatizeMethod\PrivatizeMethod.js:30:14) at Object.(D:\Main\MyPrograms\programs_JavaScript\PrivatizeMethod\PrivatizeMethod.js:71:6) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
This has quite surprised me. In C#, the stack trace of an Exception only gets populated if you throw the exception, not just by creating the Exception object. The thing is that you can examine that string to see if you're being called from inside or outside the class. There are some articles about parsing this string [1] and [2]. For my proof of concept I've just used some basic, error-prone code based on some of the code I've seen there.
So I have created a privatizeMethod function that receives a "class" and the name of a method, and replaces that method (in the class.prototype) by a new method that when invoked checks (through the calledFromInsideClass function) if it's being invoked by a method in the class. The stack at that point is: calledFromInsideClass <- myPrivatizedMethod <- caller method, so we are checking the third entry in the stack trace.
function calledFromInsideClass(className:string):boolean{ let stackTrace = (new Error()).stack; // Only tested in latest FF and Chrome console.log("stackTrace: " + stackTrace); stackTrace = (stackTrace as string).replace(/^Error\s+/, ''); // Sanitize Chrome let callerLine = stackTrace.split("\n")[2]; // 1st item is this function, 2nd item is the privatizedMethod, 3rd item is the caller return callerLine.includes(className + "."); } function privatizeMethod(classFn: any, methodName: string){ let originalMethod = classFn.prototype[methodName]; let privatizedMethod = function(){ if (calledFromInsideClass(classFn.name)){ console.log("privatized method called from inside"); return originalMethod.call(this, ...arguments); } else{ throw new Error(`privatized method ${originalMethod.name} called`); } }; classFn.prototype[methodName] = privatizedMethod; } //privatize the the City.growPopulation method privatizeMethod(City, City.prototype.growPopulation.name);
I've uploaded a full example (in typescript) here.
No comments:
Post a Comment