Possible bug with unknown args to function?

Posted on
  • I was testing espruino on the Linux command line (while waiting for my boards to be shipped) and ran across this error. Is this a bug, or a feature?

    $ espruino
    Interactive mode.
    Size of JsVar is now 32 bytes
    Size of JsVarRef is now 4 bytes
    Added SIGINT hook
    Added SIGHUP hook
    Added SIGTERM hook
    
     ____                 _ 
    |  __|___ ___ ___ _ _|_|___ ___ 
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v04.8 (c) 2019 G.Williams
    
    Espruino is Open Source. Our work is supported
    only by sales of official boards and donations:
    http://espruino.com/Donate
    
    >function foo(x,y) {
    :for (var i = 0; i < arguments.length; i++) {
    :console.log(arguments[i]);
    :}
    :}
    =function (x,y) { ... }
    >foo(1,2)
    1
    2
    =undefined
    >foo(100)
    100
    Uncaught ReferenceError: "1" is not defined
     at line 2 col 24
    console.log(arguments[i]);
                           ^
    in function "foo" called from line 1 col 8
    foo(100)
           ^
    >quit()
    =undefined
    >
    

    The source was cloned from github and compiled on Ubuntu 16.04.6 LTS. I got the same error on an OS X build as well. It was built with the following commands:

    export BOARD=LINUX
    source ./scripts/provision.sh $BOARD
     make clean && RELEASE=1 BOARD=$BOARD make
    

    I saw this post Functions with unknown amount of args. but I didn't get the same results.

  • Fri 2019.08.16

    'Is this a bug, or a feature?'

    Aren't all bugs really 'Undocumented Features'?



    Hello Windy City @jrh from an Espruino neighbor 100 miles N.W. of you. Waukesha, WI home of Les Paul inventor of the solid body electric guitar. There are at least two of us here in the Midwest. Welcome to the world of Espruino!



    Curious about your post, I tested the same on an EspruinoWiFi, and verify the same output.

    >foo( 100 )
    100
    Uncaught ReferenceError: "1" is not defined
    



    But looking under the hood reveals the culprit. I copied your snippet verbatim here for ease of others to copy and follow along as well.

    function foo(x,y) {
      for ( var i=0; i < arguments.length; i++ ) {
        console.log( arguments[i] );
      }
    }
    
    foo( 2, 3 );
    
    foo( 100 );
    



    Q: What is the container arguments?

    When we add this line inside the function and run, we get:

    console.log( typeof( arguments ) );
    
    >foo( 2, 3 )
    object
    

    So we have a container that is an object that we know from experience, massages the values or arguments that are passed to the function parameters.


    Now we go one step further and wonder well how many values does this arguments container actually receive?

    console.log( arguments.length );
    
    foo( 2, 3 );
    2
    



    So now we have our test snippet:

    function foo(x,y) {
      console.log( typeof( arguments ) );
      console.log( arguments.length );
    
      for ( var i=0; i < arguments.length; i++ ) {
        console.log( arguments[i] );
      }
    }
    

    Now lets proceed to the actual perceived error:

    Uncaught ReferenceError: "1" is not defined
    

    This is informing us that as we are dealing with an object that holds an array of values that when referenced with an 'i' indexer arguments[i] we may reveal what value is in each array location. The above error indicates that element 1 doesn't exist.

    Let's now take a peek at the function call:

    foo(100);
    



    And for the BIG SURPRISE!, I actually thought I had the obvious answer, until I did a bit more testing. Had to wrap the function contents in a try/catch to allow processing to continue and give us a bit more detail.

    function foo(x,y) {
      console.log( typeof( arguments ) );
      console.log( arguments.length );
    
      try {
    
      for ( var i=0; i < arguments.length; i++ ) {
        console.log( arguments[i] );
      }
    
      } catch(e) {
        console.log( "ERROR: " + e.message );
      }
    }
    

    Running our test produces the same error.

    >foo(100)
    object
    2
    100
    ERROR: "1" is not defined
    

    and then on to two additional lines for the Coup de Gras:

    function foo(x,y) {
      console.log( typeof( arguments ) );
      console.log( arguments.length );
    
      try {
      
      console.log( arguments[0] );
      console.log( arguments[1] );
    
    //  for ( var i=0; i < arguments.length; i++ ) {
    //    console.log( arguments[i] );
    //  }
    
      } catch(e) {
        console.log( "ERROR: " + e.message );
      }
    }
    

    provides some additional information, but we still need a bit more, however

    >foo(100)
    object
    2
    100
    ERROR: "1" is not defined
    



    Final test is to modify what is passed as the parameter list. We'll change the function definition to have just a single 'x' parameter foo( x )

    function foo(x) {
      console.log( typeof( arguments ) );
      console.log( arguments.length );
    
      try {
      
      console.log( arguments[0] );
    //  console.log( arguments[1] );
    
      for ( var i=0; i < arguments.length; i++ ) {
        console.log( arguments[i] );
      }
    
      } catch(e) {
        console.log( "ERROR: " + e.message );
      }
    }
    

    the result is as expected in our deign requirement expecting one argument be passed

    >foo(100)
    object
    1
    100
    100
    


    If you have been following along, I believe you now have the answer to the question is this a bug?



    Further clarity may be gained from the docs:

    https://www.espruino.com/Reference#l__gl­obal_arguments

  • Yes it's a bug. arguments.length is set to the number of arguments if the number of provided arguments is higher (and/or equal) to the number specified arguments in the function definition. But it is not set to the number of arguments if the number of provided arguments is smaller.

    As workaround define the function without arguments, arguments.lengthwill be always correct.

    function foo(x,y) {
      console.log("arguments.length=" + arguments.length);
      //console.log("x=" + x);
      //console.log("y=" + y);
      //for ( var i=0; i < arguments.length; i++ ) {
      //  console.log("typeof(arguments["+ i + "])=" + typeof(arguments[i]));
      //  console.log("arguments["+ i + "]=" + arguments[i]);
      //}
    }
    function bar() {
        console.log("arguments.length=" + arguments.length);
    }
    foo();
    foo(1);
    foo(1,2);
    foo(1,2,3);
    bar();
    bar(1);
    bar(1,2);
    bar(1,2,3);
    
  • I see the workaround works in node as well. Thanks for the tip!

    $ node
    > function foo() {
    ... for (var i = 0; i < arguments.length; i++) {
    ..... console.log(arguments[i]);
    ..... }
    ... }
    undefined
    > foo(1,2)
    1
    2
    undefined
    > foo(100)
    100
    undefined
    > foo(1,2,3,4,5)
    1
    2
    3
    4
    5
    undefined
    >
    
  • Sat 2019.08.17

    'Yes it's a bug'

    What basis is being used to make that determination?

    The behavior of arguments is exactly as designed. Was the Note beneath the arguments object actually read?

    https://www.espruino.com/Reference#l__gl­obal_arguments

    Anybody check the spec?

    http://www.ecma-international.org/public­ations/standards/Ecma-262.htm



    See section 17 ECMA spec

    http://www.ecma-international.org/ecma-2­62/10.0/index.html#sec-method-definition­s

    Also check out sections 14, 6 and 9 on the argument and exotic objects along with function definitions

    Specifically this passage:
    "if a built-in function or constructor is given fewer arguments than the function is specified to require, the function or constructor shall behave exactly as if it had been given sufficient additional arguments, each such argument being the undefined value."



    There seems to be a mis-understanding of the parameter list and what an argument is. Here is an easy read:

    https://codeburst.io/parameters-argument­s-in-javascript-eb1d8bd0ef04



    We the coder define the parameter list that a function container will process. In this manner, the function may act on pre-determined arguments (the value of a parameter) knowing the exact parameter count.

    By design, and specifically addressed in the ECMA specification, when fewer arguments (the values) are passed to a function through it's parameter list, those arguments take on a value of undefined.

    While there may be rare instances when more arguments are passed, it can be seen that without the defined parameter list, the function won't have sufficient information on what to do with the additional un-linked values.



    Back to the original function from #1 as defined with two parameters:

    function foo(x,y) {
      for ( var i=0; i < arguments.length; i++ ) {
        console.log( arguments[i] );
      }
    }
    

    Here we have exactly two parameters defined in function foo(x,y). When we pass two arguments, then the indexer of the arguments array container will correctly report what is passed.

    When more arguments are passed, we can see that the count matches as it should.

    foo(100);
    
    // is not passing two arguments as the function definition requires
    
    foo( x, y );
    
    // in this case 
    //  x is assigned the value 100 
    //  y is undefined
    
    // the arguments object only contains one valid argument
    

    But, when using fewer arguments, per the specification, the omitted parameter arguments contain the value undefined. As the function requires valid values and as spelled out in the note from #2 link above, the arguments array will report what is defined in the function parameter list.

    The reason the error

    Uncaught ReferenceError: "1" is not defined
     at line 2 col 24
    console.log( arguments[i] );
    

    appears is from incorrect Javascript as I pointed out in detail within the #2 explanation. Two obvious flaws here. Attempting to pass a different number (1) of arguments to a function that is defined (2) by the coder, then expecting an array (1 valid) to produce valid output, when the rules to passing (only 1) the correct number of parameters contradicts the exact definition (2) the coder intended.



    That non-array element request is similar to this snippet

    var ary = new Array( 7, 8, 9 );
    
    console.log( ary.length );
    
    // attempt to print array values that were never defined
    for( var i=0; i<5; i++ ) {
      console.log( ary[ i ] );
    }
    
    
    The output as expected
    3
    
    
    7
    8
    9
    undefined
    undefined
    


    As passing one argument to a two parameter list will result in an undefined value, the arguments array is unable to produce a valid response for the second value as there is only one valid argument that the call to function foo() passes. Therefore the array indexer only contains one reference and that is to element[ 0 ]. Attempting to print out to the console an un-referenced element[ 1 ] therefore will produce the error that is introduced from a bad coding practice.

  • Whatever. For one, foo is not an build-in function so the reference to this part of the specs is not substantial. The linked codeburst.io article states "arguments.length indicates the exact number of arguments which does not depend on the number of parameters in the function definition.".

    Maybe your understanding of the spec is correct. But all other JavaScript implementations have a different understanding. Being the only to follow the spec is a bug - from the user point of view. Because it's incompatible with the rest of the world.

  • Building function's context (frame / scope) and its variables / scoped variables, every entry in the argument list of the function is a variable known to the that frame.

    I use the technique to pass less arguments than declared in the list of function arguments for multiple reasons.

    The simple one is optional arguments and check their presence in the function.

    The more elaborate one is to 'declare' local variable(s) without having to spend the space for the extra var v = ...; statement. In the realm of micro controllers with very limited (memory and computing) resources, this is very efficient... and it works also in the Browser JS. Don't know if it works in nodeJS, but assume it does...

    Comparing Espruino's JS w/ browser (and node) JS, this is a difference. I though do not see a reason why this cannot be fixed... I'm sure 'Espruino' knows when it runs out of passed arguments and set arguments and it's length accordingly... but it may need to store the declared variables somewhere else for its own housekeeping / frame management / usage 'count' / garbage collect...

    Arguments arguments is anyway something weird... it can be accessed like an array - from the syntax point of view (like [] is a 'variable getter') - but it does not 'understand' .forEach() or for (var e in arguments) { ... };. Chrome/chromium defines it as a 'Symbol.iterator'... which makes sense...

  • 'Whatever. For one, foo is not an build-in function so the reference to this part'

    Yes, exactly. So why would a coder define a function with a specific number of parameters expecting an exact return response, then expect Javascript or any language that supports methods and functions, to magically to produce the same desired result when that same coder is only passing one valid argument?

  • ...it is all in the eye of the beholder / implementer and the specs, and what we know is that specs are not always as comprehensive (complete) as we think, and some things are not really intentional but become - grandfathered/grandmothered - a useful feature, either internally/private for the implementer or externally/public.

  • Sat 2019.08.17

    'The linked codeburst.io article states'

    That is an article, of one author's opinion describing generalized Javascript and is not a design specification.

    The link to the article was to reveal by definition the difference between a parameter list and what an argument is. It isn't there to imply that what that author wrote is how Espruinio Javascript works.

    There is an enormous amount of effort that @Gordon and others put in to staying as accurate as the ECMA spec defines.

    https://www.espruino.com/Features

    As @allObjects points out in #9, under some circumstances, it isn't possible to be 100% compliant.


    'all other JavaScript implementations have a different understanding'

    Yes, agreed. And that is why we have specifications for each of the different implementations.
    So that we all are on the same page for each different implementation.

    This page isn't a specification as such, but does accurately describe each object and function as designed and utilized by Espruino.

    https://www.espruino.com/Reference


    While it might be that the behavior of the arguments object is not what is desired, or as implemented in another environment, it does in fact perform exactly as the Espruino web documentation informs us. See the Note heading which additionally describes the behavior:

    https://www.espruino.com/Reference#l__gl­obal_arguments

    This is why reviewing each method in the reference section is necessary during the design phase in order to avoid erroneous coding pitfalls as pointed out in this thread.


    I still get the impression that it is not understood, that it is an impracticality to expect a Javacript statement to output a valid value from an array that has an indexer that was assigned a value of 'undefined'. e.g.

    console.log( arguments[ 1 ] );
    
    Uncaught ReferenceError: "1" is not defined
    

    will output an error when it is assigned 'undefined' or was never initialized in the first place,
    as in the case when only arguments[ 0 ] is correctly initialized.

  • Sun 2019.08.18

    Proposed Solution

    If the insistence on sending fewer than the defined number of function parameters is still desired, a proposed solution would be to range check the arguments as any prudent developer should do.

    We'll perform that task with the typeof operator.

    https://developer.mozilla.org/en-US/docs­/Web/JavaScript/Reference/Operators/type­of


    function foo( x, y ) {
      var numParams = arguments.length;
      if( typeof( y ) !== "number" ) { numParams = 1; }
      if( typeof( x ) !== "number" ) { numParams = 0; }
    
      for( var i=0; i<numParams; i++ ) {
        console.log( arguments[ i ] );
      }
    }
    

    The above solution, unlike the #3 work around allows for the design requirement of two parameters x and y in function foo( x, y ) and the ability to use the arguments object to iterate over the values passed, even when less than defined are supplied by the caller, not producing an error as what was desired in post #1.

    >foo(4,5)
    4
    5
    
    
    >foo(4)
    4
    
    
    >foo(4,5,6,7)
    4
    5
    6
    7
    
    
    // 'undefined' is a normal return response when a function doesn't specify a return value. note the supplied leading equals sign, that is not part of the function and supplied by the Espruino response as the function doesn't definitively specify a return value
    >foo()
    =undefined
    

    For L17 comment see: https://www.espruino.com/Troubleshooting­#espruino-keeps-responding-undefined-to-­my-commands

    The function definition containing two parameters is to the original poster's specification, along with the iteration over the arguments list without error.

  • Parsing a variable of type "undefined" creates an error:

    function foo(x, y) {
      for (var i=0; i<arguments.length; i++) {
         console.log("i="+i);
         try { console.log("val="+arguments[i]); } catch (e) { console.log(e);}
      }
    }
    foo(undefined, 1, undefined, 2)
    
    i=0
    ReferenceError: ReferenceError: "0" is not defined
    i=1
    val=1
    i=2
    ReferenceError: ReferenceError: "2" is not defined
    i=3
    val=2
    =undefined
    

    So there are two bugs: arguments.length is not updated (documented https://www.espruino.com/Reference#l__gl­obal_arguments). arguments fails for undefined arguments (not documented).

  • @maze1980

    ...all other JavaScript implementations have a different understanding.

    and

    Being the only to follow the spec is a bug - from the user point of view. Because it's incompatible with the rest of the world.

    ...quite some statements.

    For the first - may be reality reads ...have each their own different understanding because for the second: all - and that's a lot of, not just Browser JS, node JS, Espruino, and not mentioning the (incomplete) rest of the world - all of them live and have to keep living proudly their 'up-bringing'... like HMTL which came to be without specs - just by doing - and and trying to get it really (cleanly) spec'd afterwards with XHMTL 1.0 / HTML 4.01 failed miserably... in regard to it's (mattering) intention... blog.codinghorror.com: html validation - does it matter?.

    This is quite an old (2009/03 - past 10 year anniversary) article... and the discussion ran quite hot then... and: today?

    Today, some aspects of the context of html and related set of technologies - that's why it is now called HTML 5 - are obviously still running hot.

    But let's not forget the context: for a lot/ some of the CS community the bad in JS (still) outweighs the good, despite the grown popularity / spread and knowledge - avaScript: The Good Parts- by Douglas Crockford: Most programming languages contain good and bad parts, but JavaScript has more than its share of the bad, having been developed and released in a hurry before it could be refined...... but the difference to many other hurried things it was refine-able (not like Java, which had to change some of its core to become useable... and it was not rushed from a time-wise, but architecturally... it had forgotten - or missed out - on aspects learned from the late 50' thru the early 80'... even though it claims to have chosen the best of the best - at that time ...a ramble about my love-h... or h...-love for Java, and that's a lot of daily bread).

    I did not like the browser war nor do I like this js war. The real issues are:

    Is the (application / business) problem understood? - If yes, let's create a solution for it with the tools and skills we have in the time frame where the solution still has a viable business proposition.

    If we have doubt in the suitability of the tools or any of the available tools and the skills we have, let'
    s make an impact analysis and refine areas... and we may end up with a hybrid approach - a good solid approach that solves the issue in the context where life happens.

    Luckily, runtime environments - stacks - have refined themselves too over time - thanks to effort getting (IEEE / IETF shared) specs done and adhered to and last but not least thanks to competition on the market... Talking though to people from different camps, preferences are still alive... and may never go away, even after all things follow the specs... or more so all have adopted - accepted / implemented - each other's (real?) flaws (and goodies) - and completely different, new but shared solutions, so that code can be moved around... that's what I get the impression from today's HTML5 world and their most prevailing application environment(s).

    With IoT and Espruino is it a bit different... solutions barely have the intention to be portable between stacks, because they run not on generic or universal platforms. The solutions are very targeted and specific... and therefore cater to a lot of specifics.

    In the task at hand - arguments.length - I think the length should be fixed in Espruino if all possible... even though I never felt into this trap.

    When it comes to the other aspect - ReferenceError - it can be refined too: initially JS did not complain about undefined references, it just assumed that it is intentional. Therefore it has been introduced in Espruino so that such issues of undefined can be detected much more easily... also by maker's of whom the majority do not have a CS degree and life long professional CS deformation... How ReferenceError has been implemented may even be hampered by the fact of how JS defines undefined and most of the time understands undefined, but is refine-able as well.

    Concluding...

    JS also knows about null - which I use as a defined undefined vs. the undefined undefined. That sounds weird, but is my solution for the issue @maze1980 may have and tries to find a solution for. If I code something, it always - when I'm at fully capacity - intentional and has a reason / purpose to serve... and defined - otherwise it would be a waste of my time and I would not code it - and so I make a distinction between undefined undefined: undefined and defined undefined: null. This prevents me from running into this issue and provides me with the advantage of detecting when I missed something - was obviously not at full capacity... ;-)

  • Mon 2019.08.19

    Thank you @allObjects for the link to the thirty or so Javascript engines. An eye openner. The Douglas Crockford book brings back early memories.

    reply to @maze1980 #12 'So there are two bugs' and 'arguments fails for undefined arguments (not documented)'

    which of course is not true

    Refer to link in #5 'ECMA specification'

    Beneath heading 'The Reference Specification Type'

    "A base value component of undefined indicates that the Reference could not be resolved to a binding"

    Beneath heading 'Well-Known Intrinsic Objects'

    "If Type(V) is not Reference, throw a ReferenceError exception"

    Beneath heading 'ReferenceError'

    "Indicate that an invalid reference value has been detected"



    Using code snippet from #12 L1 clearly defines a function requiring two valid initialized primitive types. It is the designers responsibility to validate that the expected data, matches the function definition. It is also the responsibility of the caller to pass valid data matching the function prototype. Specification recommendations.

    L7 violates passing initialized arguments to a known function that defines that requirement. Although a bad practice, it is allowable and valid Javascript. It is an 'unresolved binding issue', however.

    The trap is now set.



    No where in lines L2 through L6 is there a validation check on the arguments passed to the function. Javascript allows this as the syntax is valid, although also a bad practice. As four primitive types are passed at L7, they initialize the arguments object, with elements [0] and [2] set to the 'undefined' primitive type. Another bad practice.

    Also verified by the Mozilla group MDN web docs.

    "failure to declare variables will very likely lead to unexpected results. Thus it is recommended to always declare variables, regardless of whether they are in a function or global scope"
    https://developer.mozilla.org/en-US/docs­/Web/JavaScript/Reference/Statements/var­

    Note that even with element indicies, object arguments is it's own object type and not derived from object Array

    Review the last snippet in #5 demonstrating undefined array elements for the following:

    L4 springs the trap.   


    A request within valid Javascript statement console.log() to access an undefined element, the first in the loop being element [0]. The statement attempts to extract a Reference to a base value that is never initialized. A rule violation. This event is detected within Javascript producing a 'detected invalid reference'. In response to and per specification, Javascript creates the 'ReferenceError' object, populates it and throws the exception which is clearly seen in L10 and L14 revealing the suspect elements.

    The output is as expected and exactly to ECMA specification. No bugs.



    Thank you @maze1980 for presenting your snippet in #12 that demonstrates careless coding deficiencies that can lead to undesired results.



    One other possible solution is to implement the redefinition example @allObjects provided in last pp. in #13, . . . and;

    Again my recommendation is to use the argument validation code as suggested in the fully functional working example within #11 to avoid attempting to access objects that are not properly initialized.

  • I'm just working my way through the forum and I don't have time to read all of this thread, but @maze1980's post looks spot on to me... http://forum.espruino.com/conversations/­337375/#14864281

    • arguments.length can never be less than the actual number of arguments - unfortunately this is a side-effect of the way Espruino works, and we could either make everything slower/use more memory or we could put up with it. It's something I know about and is documented, and is somewhere Espruino doesn't follow the spec.

    • accessing an element of arguments that is undefined raises an error - this is a really weird bug since if you do z=arguments and then use that variable then you're fine. I've just raised an issue for that here: https://github.com/espruino/Espruino/iss­ues/1691

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Possible bug with unknown args to function?

Posted by Avatar for jrh @jrh

Actions