2

In Javascript, local variables do not live on any object that I'm aware of. That is,

function foo() {
    const x = 2;

    self.x; // undefined
    this.x; // undefined
    window.x; // undefined
    x; // 2, obviously
    eval('x'); // 2
}

The last option eval('x') shows that it is possible to refer to these variables by name. I'm looking to extend this and access a variable using a name pattern:

function foo() {
    // Code not under my direct control
    function foobar_abc() {}
    function other_functions() {}

    // Code under my control
    const matchingFunction = // find the function with name matching 'foobar_*'
}

If this lived on an object, I would use something like myObject[Object.keys(myObject).find((key) => key.startsWith('foobar_'))]. If it were in the global scope, myObject would be window and everything works.

The fact that eval is able to access the variable by name implies that the value is available somewhere. Is there any way to find this variable? Or must I resort to techniques which re-write the (potentially very complex) code which is not under my direct control?

I'm targeting modern browsers, and in this use case I don't mind using eval or similarly hacky solutions. Arbitrary code is already being executed, because the execution of user-provided code is the purpose.

Dave
  • 44,275
  • 12
  • 65
  • 105
  • If it was a regular browser you would probably want `window[X]`. What about `this.hasOwnProperty(X)`? The `this` object gives you the current scope. Basically you should be able to do `for (let name in this) { ... }` – Dekel Sep 13 '17 at 21:52
  • This appears to be a duplicate of https://stackoverflow.com/questions/2051678/getting-all-variables-in-scope – Duncan Thacker Sep 13 '17 at 21:53
  • @Dekel since this is a locally-scoped variable, it is on neither `window` nor `this`. It may exist on an object somewhere, but as yet I haven't heard of it! – Dave Sep 13 '17 at 21:54
  • @DuncanThacker yes that seems to be a reasonable duplicate. A pity that it's apparently impossible without resorting to parsing the provided code. – Dave Sep 13 '17 at 21:57
  • 1
    If you don't need an entirely reliable technique you could use a RegExp to pull out candidate function names from the code and then a bit of eval/try/catch magic to test whether each one is genuinely an available function. – skirtle Sep 13 '17 at 21:59
  • @skirtle that actually might be a workable option here (not often I'd say that about regex + eval + loop-on-error!). Thanks for the idea; I'll have a check to see how reliable it is. – Dave Sep 13 '17 at 22:04
  • No, the data structure in which variables are stored in is not accessible to user code. What is your [actual problem](https://meta.stackexchange.com/q/66377)? – Bergi Sep 13 '17 at 23:41

3 Answers3

1

Another option is to use code parsing to deduce the function names using a javascript AST (abstract syntax tree) library. The "esprima" package will probably be good place to look:

https://www.npmjs.com/package/esprima

So you can do

import esprima from 'esprima'

const userCodeStructure = esprima.parseScript( userProvidedJavascriptString );

const allTopLevelFunctionDeclarations = userCodeStructure.body.filter( declaration => declaration.type === "FunctionDeclaration" );

const allTopLevelFunctionNames = allTopLevelFunctionDeclarations.map( d => d.id );

I haven't tried this myself by the documentation suggests it should work (or something like it):

http://esprima.readthedocs.io/en/latest/

Duncan Thacker
  • 5,073
  • 1
  • 10
  • 20
0

One possible approach that might help you here is to evaluate at global scope rather than in a function, and that might put the functions on the window object instead of in local scope.

Easiest way to do this is probably to write a new tag into the document and inject the user-provided code.

Duncan Thacker
  • 5,073
  • 1
  • 10
  • 20
0

Relying on variable names is the wrong approach.

eval is evil. It may not be available under CSP. Considering that the code is supposed to run in browser, the biggest problem is that variables don't have expected names in minified code. They are a, b, c...

In order to maintain their names, they should be object properties - and so they will be available on the object.

Or must I resort to techniques which re-write the (potentially very complex) code

This is refactoring and that's what should be done to avoid bad code that smells and creates major problems.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I think you misunderstand the source of the code not under my control; it is provided **by the user at runtime**. It is not code which is available for me to manually fix; it would have to be fixed algorithmically before executing it. For the same reason, it will never be minified. – Dave Sep 13 '17 at 22:16
  • 1
    @Dave This may be XY problem then. The answers are as good as the question is. Consider providing more details on where this code originates from, what it is and what exactly you do with it. *Code not under my direct control* and *Code under my control* aren't sufficient explanations. As it was said, this won't work with minified code. – Estus Flask Sep 13 '17 at 22:21
  • I think my explanation so far is detailed enough without being overwhelming. Or at least, the linked duplicate and up-voted answer cover what I wanted to know. If you really want to know the full details: I am working on an engine for running "King-of-the-Hill" games online, and I am implementing old challenges from the PPCG site. This one in particular: https://codegolf.stackexchange.com/questions/67560/king-of-the-hill-spacewar requires functions named according to a pattern. Since multiple answers made small mistakes in their naming, autodetection of the names is desirable. – Dave Sep 13 '17 at 22:33
  • Hopefully that explains why `eval` doesn't bother me in the slightest in this use case (though the engine does attempt to use `importScripts(blob)` if supported by the browser for performance reasons), and why the code really will never be minified and is not refactor-able! – Dave Sep 13 '17 at 22:35
  • @Dave Thanks for the explanation. Yes, context matters. Techniques like this ones would be totally inappropriate in normal app but it they work for code golf, they work. The options are limited by the fact if you can get script source on the fly and reevaluate it if needed. Yes, the approach with JS parser (Esprima, etc) is likely the most solid one. – Estus Flask Sep 13 '17 at 22:48