Search code examples
javascriptmicrosoft-edgedestructuringlabeled-statements

What happens in `let:let{let:[x=1]}=[alert(1)]`?


I’m watching a talk on JSON hijacking and not 2 minutes in, there is already JavaScript that is unfamiliar to me.

let:let{let:[x=1]}=[alert(1)]

It seems to work on Edge and just alerts 1, but I’ve never come across that let:let syntax. I’m curious, how am I supposed to read this?


Solution

  • The video actually says that it uses destructuring assignment and labels.

    This code doesn’t appear to work in browser other than Edge; so to make it work in other browsers, it needs to look like this:

    let:{let{let:[x=1]}=[alert(1)]}
    

    Why? Let’s look at Firefox’s console:

    SyntaxError: lexical declarations can't appear in single-statement context

    The “single-statement context” the error is referring to, is the part after let: at the beginning — let{let:[x=1]}=[alert(1)]. In this case, the let before it is a label. No other keywords appear to work as a label:

    var: while(false); // => SyntaxError: missing variable name
    for: while(false); // => SyntaxError: missing ( after for
    

    However, a few of them work:

    yield: while(false);
    async: while(false);
    await: while(false);
    

    In strict mode however, let and yield would fail as well with SyntaxError: [keyword] is a reserved identifier.

    Now, the remaining part of the code uses destructuring:

    let {
        let: [x = 1]
      } = [
        alert(1)
      ];
    

    The let inside the { } just signifies an object property, which is totally fine. The following is valid JS:

    let object = {
        let: 2,
        var: 1,
        const: "hello",
        while: true,
        throw: Error
      };
    

    alert(1) gets executed, so you see the alert. It evaluates to undefined, so you have:

    let {let: [x = 1]} = [undefined];
    

    Now, this is trying to get the let property of [undefined], which itself is undefined. Further, this line is attempting to take the value of that property, and destructure it further into an array (so the value has to be an iterable object) with the variable name x for its first element, with the default value 1. Since [undefined].let is undefined, it can’t be destructured, so the code throws the error:

    TypeError: [...].let is undefined


    Working destructuring could look like one of these lines:

    let {let: [x = 1]} = {let: [alert(1)]}; // x is now 1 (default value, since first element in right-hand side is undefined)
    
    let {let: [x = 1]} = {let: [2]}; // x is now 2 (defined due to right-hand side)
    

    Both don’t throw an error, and the first one assigns 1 to x, because the first element in the array on the right-hand side was undefined.


    Part of the confusion may stem from nested destructuring, like these two snippets:

    let {a: {b: {c}}} = {a: {b: {c: 3}}}
    
    let {a: {b: {c = 1}}} = {a: {b: {c: 3}}}
    

    Here, no variables a or b are created, only c, which is the identifier not followed by a : on the left-hand side. Property names that are followed by a : basically instruct the assignment to “find this property in the right-hand side value”.