Channel: ClearScript
Viewing all articles
Browse latest Browse all 2297

Commented Issue: 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: I understand. Thank you for clarifying this. I overlooked the possibilities of such complex cases with generics, and fully agree with your practical and reasonable solution to this problem. It also seems that I had misunderstood this statement in the FAQtorial ```"Extension methods are available if the type that implements them has been exposed."``` by thinking that you need to explicitly expose the leaf node type to script e.g. ```lib.System.Linq.Enumerable```, something like using statements in C#. I fear that trying to exclude trivial matches would be of no help, since a mere "System.Core' would flood objects with generic LINQ extensions. Even in IEnumerables where those matches would be correct, having so many methods show would make it an annoyance, so I'm thinking it would be better not to show extensions altogether for cases such as REPL. I came up with some ideas: 1. Make extensions "non-enumerable" JavaScript properties. 2. For every ```MethodName``` extension added, add a ```MethodName{c2cf47d3-916b-4a3f-be2a-6ff567425808}``` property with value true, like it's currently done to indicate CLR objects, so when looping through property P, you can exclude it if property P{...} exists. 3. Make a NoExtensionsHostTypeCollection, so that when exposed, it doesn't bind extensions, so control is given to the user to decide if they want extensions with that library (might also be faster in performance?). 4. Add a method to HostFunctions/EngineInternal somewhere else to get "own" properties of a CLR object if they are already stored somewhere in the back, or even calculate them from scratch as if that type has no extensions. Something like ```return TypeHelpers.GetScriptable...``` 5. Hard way, but no addition to ClearScript would be required: Get members via reflection, figure out the wrapped type if it's restricted. figure out script access and names recursively. I am not sure which one of these could be implemented with the least effort, any thoughts? Thank you!

Viewing all articles
Browse latest Browse all 2297