Channel: ClearScript
Viewing all articles
Browse latest Browse all 2297

Commented Unassigned: Extension methods appearing as keys on irrelevant objects (V8) [101]

Hello! First of all thank you for this amazing library and your great effort in maintaining it!

I am building a runtime on top of ClearScript that supports node-like requiring of scripts and dll's. I also implemented a REPL feature to explore my objects in console and print them like node does. I do so by enumerating Object.keys(obj).

The issue I am facing is confusing - ```Object.keys()``` is returning names of extension methods even if the object is not suitable for those methods (different type), but only if the assembly containing those methods is exposed in a specific way to script. The type containing the extension methods is not required to be exposed, only the assembly suffices.

How I managed to narrow down the reproduction:

// The assembly I want to import
var telegram = require('./Telegram.Bot.dll');
// require loads the file dynamically and returns a wrapper for its System.Reflection.Assembly
// calling root() on that wrapper returns a new HostTypeCollection(assembly) in C#
var root = telegram.root();

// use the library as it's intended (note: the bug occurs even without this line)
var api = new root.Telegram.Bot.Api(......);

// The next line returns a regular CLR object module
var core = require('core');

var keys = Object.keys(core); // keys now contains ToUnixTime and some other irrelevant extensions!
// From the source code of the library: public static long ToUnixTime(this DateTime self) { ... }
// My object is not a DateTime, nor did I import root.Telegram.Bot.Helpers.Extensions
// class where it resides

// Calling sleep tells the runtime to start a REPL
//after the script file finishes running instead of terminating
// Exporting object to global namespace
global.core = core;

After running this file with my executable, if I type ```core``` in my REPL, It still prints out those keys, and evaluating ```core.ToUnixTime``` as a command returns ```[HostMethod:ToUnixTime]```, trying to invoke ```core.ToUnixTime()``` throws ```... does not contain a definition for 'ToUnixTime'```. This issue pollutes my objects and defeats the purpose of having an inspector.

This bug does not occur (at first) under these conditions:
global.telegram = require('./Telegram.Bot.dll');
// I am not exposing root() here
global.core = require('core');

After this file finishes running, typing ```root = telegram.root()``` in REPL exposes the assembly but does not introduce those buggy extension methods. More so, calling ```ext = root.Telegram.Bot.Helpers.Extensions``` does not cause any trouble. However, objects exposed afterwards: ```console = require('console')``` get polluted.

-I am using the latest version of ClearScript from NuGet (v5.4.4)
-Executing files runs them within a ```function (exports, require, module, __filename, __dirname) { code }``` wrapper.
-This means that the first case is entirely executed within a engine.Execute(...) call.
-The bug occurs in the first case even if core is exposed before the library.
-The REPL loop starts after executions are finished, so in the second case it is a different execution - an engine.Evaluate(...) more exactly.
-Runtime source code https://github.com/EdonGashi/ShipScript
-Library source code https://github.com/MrRoundRobin/telegram.bot

Comments: Greetings! What you're seeing is the expected behavior; that is, extension methods have always worked this way in ClearScript. There's always room for improvement, of course. Extension methods become available when the implementing class is exposed, either via `ScriptEngine.AddHostType` or by exposing a `HostTypeCollection` that contains the corresponding type. Once available, extension methods appear as properties on all host objects, regardless of type, and only when script code attempts to invoke an extension method is the object's suitability evaluated. The reason for this is that, generally speaking, determining whether an object is suitable for an extension method is nontrivial. An extension method can be generic, with a `this` parameter of an open constructed type (see [`System.Linq.Enumerable`](https://msdn.microsoft.com/en-us/library/system.linq.enumerable(v=vs.100).aspx) for some examples). Matching such a type against an object's specific type might require relatively complex (and expensive) recursive analysis of the metadata. Such analysis is subject to various rules encoded within the C# compiler, and our preference was not to try to duplicate it within ClearScript. Instead, we chose to defer to the compiler itself, by relying on the C# Runtime Binder (part of the C# compiler) to reject invalid extension method calls when they're made. This was a practical approach, as ClearScript was already using the C# Runtime Binder for method invocation. All that having been said, it's been a while since we've looked at this, and we can certainly see how this aspect of ClearScript's implementation could undermine REPL features such as the one you're building. Therefore we'll activate this issue and investigate potential improvements. We should be able to eliminate at least some of the false positives. Thank you!

Viewing all articles
Browse latest Browse all 2297