190

With this code:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
  };
  bar();
}
baz();

I get this unexpected result:

enter image description here

When I change the code:

function baz() {
  var x = "foo";

  function bar() {
    x;
    debugger;
  };
  bar();
}

I get the expected result:

enter image description here

Also, if there is any call to eval within the inner function, I can access my variable as I want to do (doesn't matter what I pass to eval).

Meanwhile, Firefox dev tools give the expected behavior in both circumstances.

What's up with Chrome that the debugger behaves less conveniently than Firefox? I have observed this behavior for some time, up to and including Version 41.0.2272.43 beta (64-bit).

Is it that Chrome's javascript engine "flattens" the functions when it can?

Interestingly if I add a second variable that is referenced in the inner function, the x variable is still undefined.

I understand that there are often quirks with scope and variable definition when using an interactive debugger, but it seems to me that based on the language specification there ought to be a "best" solution to these quirks. So I am very curious if this is due to Chrome optimizing further than Firefox. And also whether or not these optimizations can easily be disabled during development (maybe they ought to be disabled when dev tools are open?).

Also, I can reproduce this with breakpoints as well as the debugger statement.

Gabe Kopley
  • 16,281
  • 5
  • 47
  • 60
  • 2
    maybe it's getting un-used variables out of your way for you... – dandavis Feb 07 '15 at 23:09
  • markle976 seems to be saying the `debugger;` line isn't actually called from inside `bar`. So look at the stack trace when it pauses in debugger: Is the `bar` function mentioned in the stacktrace? If I'm right, then the stacktrace should say it's paused at line 5, at line 7, at line 9. – David Knipe Feb 10 '15 at 00:16
  • I don't think it has anything to do with V8 flattening functions. I think this is just a quirk; I don't know if I'd even call it a bug. I think David's answer below makes the most sense. – markle976 Feb 10 '15 at 02:44
  • see also [garbage collection with node.js](http://stackoverflow.com/q/5326300/1048572), [How are closures and scopes represented at run time in JavaScript](http://stackoverflow.com/q/5368048/1048572) and [About closure, LexicalEnvironment and GC](http://stackoverflow.com/q/8665781/1048572) – Bergi Jul 20 '16 at 03:32
  • 2
    I have the same issue, I hate it. But when I need to have access closure entries in the console, I go to where you can see the scope, find the _Closure_ entry and open it. Then right-click on the element you need and click on _Store as Global Variable_. A new global variable `temp1` is attached to the console and you can use it to access the scope entry. – Pablo Jun 19 '17 at 14:59
  • This seems eminently sensible to me; the variable is not used by the function `bar`, so in the execution context of `bar` it's not visible to the debugger since the runtime is under no obligation to keep/make unnecessary things visible in that context. – Lawrence Dol Jul 10 '17 at 23:07
  • I don't think you're (OP) quite right about eval making context available. My comment to @OwnageIsMagic's answer explains: context is available inside the `eval`ed code, but not in the inner function itself. – Sigfried Jul 27 '17 at 11:21
  • This sort of issue is incredibly harder to diagnose when using React, and [variables that are in plain sight USED in your components](https://imgur.com/a/MBqwVZl), appear undefined. Super frustraing. – Dan Dascalescu May 28 '20 at 03:20

7 Answers7

168

I've found a v8 issue report which is precisely about what you're asking.

Now, To summarize what is said in that issue report... v8 can store the variables that are local to a function on the stack or in a "context" object which lives on the heap. It will allocate local variables on the stack so long as the function does not contain any inner function that refers to them. It is an optimization. If any inner function refers to a local variable, this variable will be put in a context object (i.e. on the heap instead of on the stack). The case of eval is special: if it is called at all by an inner function, all local variables are put in the context object.

The reason for the context object is that in general you could return an inner function from the outer one and then the stack that existed while the outer function ran won't be available anymore. So anything the inner function accesses has to survive the outer function and live on the heap rather than on the stack.

The debugger cannot inspect those variables that are on the stack. Regarding the problem encountered in debugging, one Project Member says:

The only solution I could think of is that whenever devtools is on, we would deopt all code and recompile with forced context allocation. That would dramatically regress performance with devtools enabled though.

Here's an example of the "if any inner function refers to the variable, put it in a context object". If you run this you'll be able to access x at the debugger statement even though x is only used in the foo function, which is never called!

function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
Louis
  • 146,715
  • 28
  • 274
  • 320
  • 16
    Have you figured out a way to deopt code? I like to use the debugger as a REPL and code there then transfer the code to my own files. But it's often not feasible as variables that should be there aren't accessible. A simple eval won't do it. I hear an infinite for loop might. – Ray Foss Mar 02 '16 at 17:29
  • I've not actually run into this problem while debugging so I've not searched for ways to deopt code. – Louis Mar 03 '16 at 11:33
  • 8
    The last comment from the issue says: *Putting V8 into a mode where everything is forcibly context allocated is possible, but I'm not sure how/when to trigger that through Devtools UI* For the sake of debugging, I would sometimes want to do so. How can I force such mode? – Suma Jan 30 '17 at 10:40
  • @Louis: For future reference, this question is the duplicate. – user208769 Feb 17 '17 at 09:11
  • 2
    @user208769 When closing as duplicate, we favor the question that is the most useful to future readers. There are multiple factors that help determine which question is most useful: your question got exactly 0 answers while this one got multiple upvoted answers. So this question is the most useful of the two. Dates become a determining factor only if the usefulness is mostly equal. – Louis Feb 17 '17 at 11:20
  • 2
    This answer answers the actual question (Why?), but the implied question -- How do I get access to unused context variables for debugging without adding extra references to them in my code? -- is better answered by @OwnageIsMagic below. – Sigfried Jul 27 '17 at 11:25
36

Like @Louis said it caused by v8 optimizations. You can traverse Call stack to frame where this variable is visible:

call1 call2

Or replace debugger with

eval('debugger');

eval will deopt current chunk

OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31
  • 1
    Almost great! It pauses in a VM module (yellow) with contents `debugger`, and context is indeed available. If you step up the stack one level to the code you're actually trying to debug, you're back to not having access to context. So it's just slightly clunky, not being able to look at the code you're debugging while accessing hidden closure variables. I'll upvote, though, since it saves me from having to add code that's not obviously for debugging, and it gives me access to entire context without deoptimizing the whole app. – Sigfried Jul 27 '17 at 11:12
  • Oh...it's even clunkier than having to use the yellow `eval`ed source window to get access to context: you can't step through code (unless you put `eval('debugger')` between all the lines you want to step through.) – Sigfried Jul 27 '17 at 12:46
  • It seems that there are situations where certain variables are invisible even after traversing to the appropriate stack frame; I have something like `controllers.forEach(c => c.update())` and hit a breakpoint somewhere deep inside `c.update()`. If I then select the frame where `controllers.forEach()` is called, `controllers` is undefined (but everything else in that frame is visible). I couldn't reproduce with a minimal version, I suppose there may be some threshold of complexity that needs to be passed or something. – PeterT Dec 03 '18 at 21:15
  • @PeterT if it is you are in wrong place Or `somewhere deep inside c.update()` your code goes async and you see async stack frame – OwnageIsMagic Dec 24 '18 at 19:57
6

I've also noticed this in nodejs. I believe (and I admit this is only a guess) that when the code is compiled, if x does not appear inside bar, it doesn't make x available inside the scope of bar. This probably makes it slightly more efficient; the problem is someone forgot (or didn't care) that even if there's no x in bar, you might decide to run the debugger and hence still need to access x from inside bar.

David Knipe
  • 3,417
  • 1
  • 19
  • 19
  • 3
    Thanks. Basically I want to be able explain this to javascript beginners better than "The debugger lies." – Gabe Kopley Feb 09 '15 at 22:22
  • 1
    @GabeKopley: Technically the debugger isn't lying. If a variable isn't referenced then it isn't technically enclosed. Thus there is no need for the interpreter to create the closure. – slebetman Apr 21 '17 at 15:55
  • 7
    That's not the point. When using debugger, I've frequently been in a situation where I wanted to know the value of a variable in an outer scope, but couldn't because of this. And on a more philosophical note, I'd say the debugger is lying. Whether the variable exists in the inner scope shouldn't depend on whether it's actually used or whether there's an unrelated `eval` command. If the variable is declared, it should be accessible. – David Knipe Apr 22 '17 at 11:30
  • Nobody promised that you could spontaneously promote a variable to be accessible in a closure. Give an inch, people expect a mile. – doug65536 Jul 16 '22 at 00:09
  • No one is "promoting" anything. I haven't consulted the javascript spec because life is too short, but I assume it says that closure variables work. And I also assume it doesn't say that it's OK to not implement an unused variable. That's just an implementation detail, which is not what the spec is supposed to be about. – David Knipe Jul 16 '22 at 14:29
2

Wow, really interesting!

As others have mentioned, this seems to be related to scope, but more specifically, related to debugger scope. When injected script is evaluated in the developer tools, it seems to determine a ScopeChain, which results in some quirkiness (since it's bound to the inspector/debugger scope). A variation of what you posted is this:

(EDIT - actually, you mention this in your original question, yikes, my bad!)

function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();

For the ambitious and/or curious, scope (heh) out the source to see what's going on:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

Jack
  • 9,151
  • 2
  • 32
  • 44
1

I suspect this has to do with variable and function hoisting. JavaScript brings all variable and function declarations to the top of the function they are defined in. More info here: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

I bet that Chrome is calling the break point with the variable unavailable to the scope because there is nothing else in the function. This seems to work:

function baz() {
  var x = "foo";

  function bar() {
    console.log(x); 
    debugger;
  };
  bar();
}

As does this:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
    console.log(x);     
  };
  bar();
}

Hope this, and / or the link above helps. These are my favorite kind of SO questions, BTW :)

markle976
  • 957
  • 1
  • 6
  • 10
  • 1
    Thanks! :) I am wondering what FF does differently. From my perspective as a developer, the FF experience is objectively better... – Gabe Kopley Feb 09 '15 at 18:34
  • 2
    "calling the break point at lex time" I doubt it. That's not what breakpoints are for. And I don't see why the absence of other things in the function should matter. Having said that, if it's anything like nodejs then breakpoints might be very buggy. – David Knipe Feb 09 '15 at 22:11
1

I seem to have access to _this. Where this is undefined in chrome inspector for me, _this seems to reference the appropriate context (and is probably what's used to as > local > this in the stack trace inspector?).

Brad G
  • 2,528
  • 1
  • 22
  • 23
0

I know this is a bit old, but my issue was minification using babel - i.e. --presets minify

When my js code was built and minified my local variables were undefined; when not minified I was able to see values of variables in the console.

Rob
  • 173
  • 1
  • 5
  • 15