Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Find argument passed to a function

I am needing to find the argument passed to a function from the function.

Let us suppose I have a function called foo:

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];
  bar(a - b / c);
  bar(c * a + b);
}

function bar(arg) { alert(arg) }

As it is now, of course, bar will always alert NaN.

Inside of the function bar, I want to obtain the argument in the form it was originally passed. Furthermore, I want to be able to access the values of a, b, and c from the bar function. In other words, I would like something of this nature:

bar(a - b / c);    

function bar() {
 //some magic code here
 alert(originalArg); //will alert "a - b / c"
 alert(vars.a + " " + vars.b + " " + vars.c); //will alert "3 hello 0,4"
} 

You may not think this is possible, but I know you can do weird things with Javascript. For example, you can display the code of the calling function like this:

function bar() {
  alert(bar.caller);
}

I am willing to bet a few dollars that with some sort of finagling you can get a the original form of the argument and the values of the variables in the argument.

I can easily see how you could do it if you were allowed to call bar in a form like this:

bar(function(){return a - b / c}, {a: a, b: b, c: c});

But that is too convoluted and therefore unacceptable. The way bar is called may not be changed.

like image 735
Peter Olson Avatar asked Jul 16 '11 05:07

Peter Olson


People also ask

How can you get the type of arguments passed to a function JavaScript?

You can refer to a function's arguments inside that function by using its arguments object. It has entries for each argument the function was called with, with the first entry's index at 0 . You can use arguments.length to count how many arguments the function was called with.

How do I check if a parameter was passed in to a function in JavaScript?

To check if a parameter is provided to a function, use the strict inequality (! ==) operator to compare the parameter to undefined , e.g. if (param !== undefined) . If the comparison returns true , then the parameter was provided to the function.

Which argument is passed to a function?

Arguments are passed by value; that is, when a function is called, the parameter receives a copy of the argument's value, not its address. This rule applies to all scalar values, structures, and unions passed as arguments. Modifying a parameter does not modify the corresponding argument passed by the function call.

Where are the argument passed to a function stored?

Parameter values to functions are stored on the stack as well, pushed immediately before the return address. Everything what lives on the stack (local variables, parameters etc.) can live in registers as well.


2 Answers

First of all — weird question. It would help a lot to know the context of why you are asking, because obviously Javascript doesn't have exactly what you are asking. (For example, is it something specifically to do with math expressions? Does the expression have to be executed prior to calling the function, as in your example?)

Javascript does not have:

  • named parameters
  • first-class/dynamic expressions

Javascript does have:

  • dynamic objects
  • first-class functions
  • closures
  • eval

Dynamic objects

Since this was your own stated option at the end, that you declared unacceptable, it sounds like it won't help you much. In any case, since you want the expression and the values, just passing an object of the values alone isn't going to help you.

First-class functions

As you stated, you do have access to Function.caller, although that just outputs a String representation of the whole function. It is also non-standard.

You also have arguments, which is an Array of the arguments that were passed in, not named. In the case of your existing code, obviously, it will be the executed result of the expression. One convenient use of arguments is not specifying any parameters in the function signature and then iterating through the array as an open-ended list.

Closures

This is also a part of your unacceptable solution at the end, but could possibly the closest to a working solution for you, though you'd have to be flexible on the way bar is called or where it is defined.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  var bar = function(arg) {
    alert(arg); // NaN
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4"
  }

  bar(a - b / c);
  bar(c * a + b);
}

eval

Using eval and closures, you might get close to what you want.

The primary thing to remember is that in your code, a - b / c is an expression. That is not the argument, the result of the expression is the argument. There is nothing besides a toString on the function itself that will alert "a - b / c".

With eval (Disclaimer: this is very hacky and not recommended! You've been warned.), you could actually pass a String as an argument, and using a closure which is bound to the space, alert what you want.

function foo() {
  var a = 3;
  var b = "hello";
  var c = [0,4];

  function hackyUglyEvalAndOutput(callback, expression) {
    alert(expression); //will alert "a - b / c" (duh)
    alert(a + " " + b + " " + c); //will alert "3 hello 0,4" (because of the closure)
    callback(eval(expression)); // "NaN"
  }
  hackyUglyEvalAndOutput(bar, "a - b / c");
  hackyUglyEvalAndOutput(bar, "c * a + b");
}

function bar(arg) { alert(arg); }

You could similarly parse caller to find calls to the function and see what was passed into the argument list (I did get it to do this) but you will have no way of distinguishing which one made the call if there is more than one. From your example, that would be crucial to accomplish what you want to do.

Again — expressions are just that and nothing more; they are not arguments.

P.S. The second call to bar will actually output NaNhello :)

like image 150
Nicole Avatar answered Nov 15 '22 20:11

Nicole


Ok, I did it! (sort of) What you see below is the most naive, buggy, hideous, and hacky code I have ever written. This is the first time I have ever really used regex, and I'm not very good at making parser code.

Here we go:

    function foo () {
        var a = 3;
        var b = "hello";
        var c = [0, 4];
        bar(a - b / c); 
        bar(c * a + b);
    };

    var callTracker = {};
    function bar() {
        var caller = bar.caller.toString();
        callTracker[caller] !== undefined ? callTracker[caller]++ : callTracker[caller] = 0;
        function findCall(str) {
            return str.search(/bar\((\S*\s*)*\);/);
        }

        //Step 1: Get the orginal form of the argument
        var callers = [caller];
        var index = 0;
        var len;
        while (true) {
            len = callers.length - 1;
            index = findCall(callers[len]);
            if (index === -1) break;
            callers.push(callers[len].substring(index + 1));
        }
        var callIndex = callTracker[caller] % len;
        var thisCall = callers[callIndex];
        var argument = thisCall.substring(findCall(thisCall));
        argument = argument.slice(4, argument.search(/\);/));
        alert(argument);

        //Step 2: Get the values of the variables
        caller = caller.slice(caller.search(/\{/) + 1, -1);
        var lines = caller.split(";");
        for (var i = 0; i < lines.length; i++) {
            lines[i] += ";";
            if (findCall(lines[i]) === -1) {
                eval(lines[i]);
            }
        }
        var vars = argument.match(/\w/g);
        for (var i in vars) {
            if (vars.hasOwnProperty(i)) {
                alert(eval(vars[i]));
            }
        }
    }

    foo();

I can forsee quite a few situations where this will break, but I'm too lighthearted to test them out. Just a couple examples: if the code contains a string literal with a semicolon in the middle, it will break, or if somebody relies on automatic semicolon insertion, it will break. If there is a conditional return statement between the two calls to bar, it will break, due to my extremely naive parsing.

But it does work for this example. If used in a real project, I think it would actually work most of the time, but there could end up being some really weird bugs.

If you want to play with it yourself, here it is on JsFiddle.

The final takeaway, I think, is never underestimate the power of a program that can read (and even change) itself.

like image 22
Peter Olson Avatar answered Nov 15 '22 20:11

Peter Olson