This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Don't print script output twice in HandleCommand
ClosedPublic

Authored by JDevlieghere on May 28 2021, 5:36 PM.

Details

Summary

When executing a script command in HandleCommand(s) we currently print its output twice: once directly to the debugger's output stream, and once as part of HandleCommand's result. You can see this issue in action when adding a breakpoint command:

(lldb) b main
Breakpoint 1: where = main.out`main + 13 at main.cpp:2:3, address = 0x0000000100003fad
(lldb) break command add 1 -o "script print(\"Hey!\")"
(lldb) r
Process 76041 launched: '/tmp/main.out' (x86_64)
Hey!
(lldb)  script print("Hey!")
Hey!

Process 76041 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003fad main.out`main at main.cpp:2:3

The issue is that HandleCommands uses a temporary CommandReturnObject. When running a one-liner with script, the script interpreter redirects the I/O from to the command object through ScriptInterpreterIORedirect which sets the immediate output on the temporary result and causes the result to be printed the first time. HandleCommand will then copy the result's output into its own result and print it a second time, not knowing that it has already been printed.

I'm not entirely sure why ScriptInterpreterIORedirect relies on an immediate output file, but there are several parts of LLDB that rely on it, so I'm hesitant to change that. We could check in HandleCommands if someone set immediate mode on our temporary result and not copy it into the final result when that's the case. A better solution in my opinion, which I implemented in this patch, is a "hermetic" mode for the CommandReturnObject which prevents anyone from setting and immediate stream on the temporary result by the command interpreter. This should prevent similar bugs if there are other places that try to do this.

I added a new test using the breakpoint command and fixed a Lua test that already suffered from this issue.

Diff Detail

Event Timeline

JDevlieghere created this revision.May 28 2021, 5:36 PM
JDevlieghere requested review of this revision.May 28 2021, 5:36 PM

A command that knew it was streaming a lot of output is supposed to be able to choose to have the CommandInterpreter directly stream the results while the command is executing. That's good for something that is likely to print a lot of output, since then you don't have to wait for the command to be done to start seeing results. Of course, you still have to gather the command into the Return object as well since that's part of the CommandInterpreter contract. So you have to be careful not to print it twice. Maybe this code is part of the implementation of that?

aprantl added a subscriber: aprantl.Jun 1 2021, 8:59 AM
aprantl added inline comments.
lldb/include/lldb/Interpreter/CommandReturnObject.h
167

bool m_hermetic = false;

lldb/source/Interpreter/CommandReturnObject.cpp
44

then we don't need this

157

or this

JDevlieghere marked 2 inline comments as done.Jun 1 2021, 11:25 AM
JDevlieghere added inline comments.
lldb/include/lldb/Interpreter/CommandReturnObject.h
167

This is the second time someone has suggested to use in-class member initializers. It's not what we've been doing in LLDB so far, but I am a fan of it personally, so I'm going to interpret this as what we want to do moving forward. I believe it's even a C++ core guideline. Maybe we can have a clang tidy check to enforce this.

lldb/source/Interpreter/CommandReturnObject.cpp
157

We would still need this :-)

JDevlieghere marked 3 inline comments as done.

Use in-class initializers

Use in-class initializers

I'm all for in-class initializers, but I think it is confusing to use them piecemeal, where some ivars in a class have initializers and some are initialized in the constructor's definition.

shafik added a subscriber: shafik.Jun 1 2021, 11:43 AM
shafik added inline comments.
lldb/include/lldb/Interpreter/CommandReturnObject.h
167

This is idiomatic C++ and we should be using this feature going forward. It is more readable then a crowded mem-init-list and really makes a big difference when there are multiple constructors.

JDevlieghere marked an inline comment as done.

Don't try to be cute: s/Hermetic/SuppressImmediateOutput/

jingham accepted this revision.Jun 4 2021, 3:36 PM

HandleCommands really does need to do this suppression, and this is a fine way to do it. I can't think of any other place where you would need to do this suppression after an admittedly non-exhaustive search. So LGTM.

This revision is now accepted and ready to land.Jun 4 2021, 3:36 PM
This revision was landed with ongoing or failed builds.Jun 8 2021, 1:57 PM
This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptJun 8 2021, 1:57 PM
thakis added a subscriber: thakis.Jun 25 2021, 8:42 AM
thakis added inline comments.
lldb/lldb/test/Shell/Breakpoint/breakpoint-command.test
1

Did you mean to put this file in lldb/lldb/test/Shell/Breakpoint/breakpoint-command.test or should it be in lldb/test/Shell/Breakpoint/breakpoint-command.test (just one "lldb/")?

JDevlieghere added inline comments.Jun 28 2021, 10:36 AM
lldb/lldb/test/Shell/Breakpoint/breakpoint-command.test
1

Yeah, this seems like a patch prefix mistake on my end. I'll move the file. Thanks!

JDevlieghere added inline comments.Jun 28 2021, 10:41 AM
lldb/lldb/test/Shell/Breakpoint/breakpoint-command.test
1