1

I recently started to swap out all my debugger statements with eval('debugger') statements. The reason is that with the plain version, not all "factually/theoretically" visible variables are "practically" visible. This happens because of optimization (see earlier SO question).

With this trick, the problem is like "90% solved" - there are some drawbacks. Apart from longer source code, those are:

  • When third party libraries are involved, it is not feasible, maybe not even possible to have the debugger -> eval('debugger') transformation done there also.
  • When I would rather set a break point in the debugger itself, instead of changing the code, that cannot be done - or can it?
  • When I'm already stopped at a "normal" debugger statement (in third party code, or where I forgot one), there is no way to switch to the desired mode - certainly typing eval('debugger') on the console doesn't help. If I want the functionality, I have to change the debugger statement, and run the code again, which might be a whole lot of work
  • When I stopped at an eval('debugger') statement, but then use the debugger 'step over/into/out' functionality, I 'lost my special status'.

How can I work around this? Is there a way to tell v8 to interpret all debugger statements by eval('debugger')? Is there a trick with which you can 'go into the other mode' - as if the eval('debugger') statement would magically appear as the next statement after the debugger statement where you're stopped? Do command line options to the chrome executable help? Maybe there is a way in firefox?

I learned about the eval('debugger') trick in an answer to a recent SO question of my own

ANNOUNCEMENT

What I'm going to do next is write a little transpiler for usage within node webserver. The transpiler will insert eval('') statements all over the place (by default once at the beginning/body of every function, and more or fewer of them if so specified in the query string.) Then I can set a breakpoint where the eval statement is, do "step into" and then I got what I want. Then I'm going to answer my own question.

Unless of course, someone will beat me to it. That would be most delightful, as I do have other things to do.

mathheadinclouds
  • 3,507
  • 2
  • 27
  • 37

1 Answers1

2

V8 developer here.

Is there a way to tell v8 to interpret all debugger statements by eval('debugger')?

There is currently no way to treat debugger statements or breakpoints as eval("debugger"), but it might be feasible to add a flag that does this. You can file a "feature request" bug at crbug.com/v8/new and ask for a flag that forcibly context-allocates all variables for debugging purposes.

(Side note 1: It's the eval part that has an effect here. Instead of eval('debugger') you could write eval(""); debugger; or debugger; other_code(); eval("");, so long as you have eval somewhere in the function.)

(Side note 2: the tension here is that on the one hand, it's Good™ when the behavior of a program when it is being debugged is the same as when it is not being debugged -- otherwise there might be issues that only show up when debugging, or un-debuggable failures in production mode. On the other hand, sometimes certain deviations are explicitly necessary in order to make debugging possible. I'm not sure on which side of the line this one falls. Context-allocating all variables will reduce performance and increase memory consumption, so if/when such a mode becomes available, you will probably have to (or want to) switch back and forth during your development work; which in particular means that this couldn't simply be the default when DevTools are open.)

Is there a trick with which you can 'go into the other mode' - as if the eval('debugger') statement would magically appear as the next statement after the debugger statement where you're stopped?

No, and there can't be. It's not a "mode" or "special status", it's a decision that has to be made when first parsing/compiling the outer function (the one that contained the variable you want to see within an inner function). Once that time has passed, it's too late to change anything (non-context-allocated variables are just gone), and the only option is to reload.

[EDIT in response to comments: To clarify what I mean by "not a mode, but a decision": from the scope chain's point of view, the situation is essentially the same as:

var inner;
function outer() {
  var foo = "foo";
  var bar = "bar";
  inner = function() {
    console.log(bar);
    debugger;
  }
  // "inner();" here is moved below
}
outer();
inner();

at the point when inner() is called, foo is either still there like bar (if it's context-allocated because at compile time of outer V8 determined that inner will need it), or gone (if V8 determined that it's local to outer and nobody else will need it). If inner contains eval, then it might need all outer-scope variables. --end of EDIT]

This happens because of optimization

Well... because of an optimization, namely being smart about whether to allocate variables on the stack or in the "context object". This is completely unrelated to what is typically referred to as "optimization of a function", i.e. running them through the optimizing compiler. That's why the %NeverOptimizeFunction hack discussed in the other issue had no effect -- it's an unrelated mechanism.

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Side note 1: Not true, I just tried it. If I understand what's happening, it's that a "context object" is created, and for that to happen, it's not interesting if there is 'debugger' in the eval or the empty string. But the effect is not the same. If I write eval(''); debugger, I cannot "get into" that new "context object", and the eval('') is of no use to me. What DOES have the "same effect" is this: Just putting eval(''); in the code, no debugger statement, and then setting a breakpoint on the eval line, AND THEN (!!) clicking STEP INTO on that eval line, so the yellow VM window opens. – mathheadinclouds Dec 04 '19 at 11:31
  • In contrast, doing eval(''); debugger, I can't "get into" that "context object", it is "gone with the wind" when I reach the debugger statement, so it's of no use to me that it "was the same" - in some sense - when it still existed. – mathheadinclouds Dec 04 '19 at 11:33
  • At the same time the "blah is not defined" error is thrown, I can very well see the variable value in the "scope" panel, when I change to the suitable position in the "call stack" panel. So the value "is there", and something *can* be done with it, in principle. Just that that something isn't "just call the compiled function". Forgive me for calling that "other something" a "mode". – mathheadinclouds Dec 04 '19 at 11:43
  • Well, when I put `eval(""); debugger;` into a function, then I can see unreferenced variables from the outer scopes in its "Scope" info box (in DevTools on the right), and can access them from the console. It's true that this funky hover tool-tip thingie doesn't seem to work; dunno why. -- You can't call a function without compiling it first. -- At any rate, I've tried my best to provide explanations and suggest a path forward, if you choose to disagree and prefer to announce your own transpiler, that is of course up to you. – jmrk Dec 04 '19 at 12:49
  • I value your input, especially the link for the feature request. I shall put in such a feature request after I've written my transpiler, so that everybody & me knows better what exactly the feature is which I'm requesting. "Writing a transpiler" sounds *way* bigger than it is; in an answer to another question (https://stackoverflow.com/questions/19710494/js-rename-variables-for-refactor-using-an-ast-not-text/58750338#58750338) I posted some code renaming variables (only variables, not just any string); it uses esprima, it's like 10 lines of code, and technically, that, too is a "transpiler". – mathheadinclouds Dec 04 '19 at 13:05
  • about side note 1: What I meant by saying <<>> is illustrated in the following screenshot: https://i.stack.imgur.com/wewRS.png – mathheadinclouds Dec 04 '19 at 15:04
  • ...and I don't know what you're talking about concerning "this funky hover tool-tip thingie" – mathheadinclouds Dec 04 '19 at 15:07
  • "funky tool tip" was referring to https://i.stack.imgur.com/SiaHY.png. -- This is what I see for the example I posted: https://i.stack.imgur.com/getwr.png (I noticed after uploading that I even typo'd it, but I went back and tested: `eval()` and `eval("")` both do the trick for me.) Chrome M79 Beta. – jmrk Dec 04 '19 at 20:06
  • yes, you "see" rightHandPanel/Scope/Closure/outer/foo, but if you type it into the console, you get an error thrown nevertheless. On my Chrome, which is 78. Can't believe your Chrome does something different. https://i.stack.imgur.com/wewRS.png – mathheadinclouds Dec 05 '19 at 06:34
  • haven't tested whether or not `eval()` and `eval('')` differ – mathheadinclouds Dec 05 '19 at 06:38
  • Unbelievable. What am I missing. It looks like your Chrome and my Chrome are different. I'm having a "please pinch me so I know I'm not dreaming" moment. WTFH?? See here: https://i.stack.imgur.com/AZwQE.png There must be a difference in the code I failed to see like 20 times in a row. – mathheadinclouds Dec 05 '19 at 06:55
  • I made another one with exactly the same code as yours, and even ran it over file system instead of web server, so absolutely everything matches. Except I'm not running M79 Beta, I'm running 78 – mathheadinclouds Dec 05 '19 at 07:22
  • I just noticed that my firefox is behaving like your chrome. So really is a browser thingy. Boy, that surprises me! https://i.stack.imgur.com/yC7fB.png – mathheadinclouds Dec 05 '19 at 08:19