This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Disable variable watchpoints when going out of scope
ClosedPublic

Authored by mib on May 24 2023, 12:51 PM.

Details

Summary

If we use a variable watchpoint with a condition using a scope variable,
if we go out-of-scope, the watpoint remains active which can the
expression evaluator to fail to parse the watchpoint condition (because
of the missing varible bindings).

This was discovered after watchpoint_callback.test started failing on
the green dragon bot.

This patch should address that issue by setting an internal breakpoint
on the return addresss of the current frame when creating a variable
watchpoint. The breakpoint has a callback that will disable the watchpoint
if the the breakpoint execution context matches the watchpoint execution
context.

This is only enabled for local variables.

This patch also re-enables the failing test following e1086384e584.

rdar://109574319

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>

Diff Detail

Event Timeline

mib created this revision.May 24 2023, 12:51 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 24 2023, 12:51 PM
mib requested review of this revision.May 24 2023, 12:51 PM
bulbazord added inline comments.May 24 2023, 3:36 PM
lldb/source/Breakpoint/Watchpoint.cpp
86

Should you also verify that the frame_sp you got isn't nullptr? If it'll never be null, consider passing a StackFrame & instead.

103–104

Is it even possible to have an ExecutionContext created from a StackFrame that doesn't have a valid Target? I wonder if we should assert that we got one here if not.

129–133

nit: You can merge this into one:

if (!baton || !context)
  return false;
140–141

Any reason to not use LLDB_LOG here? You'll get __FILE__ and __FUNCTION__ included.

152

This should be return false;

165

This should be return true; no? You could invert the condition to be consistent with the patterns above of "return false if some condition wasn't met".

mib marked 3 inline comments as done.May 24 2023, 3:53 PM
mib added inline comments.
lldb/source/Breakpoint/Watchpoint.cpp
103–104

There is no ExecutionContext::IsValid method, so if you created an ExecutionContext with a bogus frame, you might get a nullptr.

165

We want to always return false since this is a breakpoint callback, we never want to stop.

mib marked 5 inline comments as done.May 24 2023, 3:57 PM
mib added inline comments.
lldb/source/Breakpoint/Watchpoint.cpp
140–141

I just wanted to stay consistent with the rest of the file. May we be should standardize the logging format.

Just so I understand the limitations:
This works for

int main() {
  int val = 0;
  // Break here
  val++;
  val++;
  return 0;
}

but not for

int main() {
  {
    int val = 0;
    // Break here
    val++;
    val++;
  }
  {
    int other = 42;
   printf("assuming that other and val aliases");
  }
  return 0;
}

?

To be clear, I think this is still useful!

lldb/include/lldb/Breakpoint/Watchpoint.h
93

Doxygen comment?

109

s/which/to/

lldb/source/Breakpoint/Watchpoint.cpp
86

I subjectively feel like I've received a crash log for every single unchecked dereference in the LLDB code so far ;-)

lldb/source/Commands/CommandObjectWatchpoint.cpp
956

Convert to early exit?

mib updated this revision to Diff 525367.May 24 2023, 4:46 PM
mib marked 4 inline comments as done.

Address @bulbazord & @aprantl comments.

mib added a comment.May 24 2023, 5:02 PM

Just so I understand the limitations:
This works for

int main() {
  int val = 0;
  // Break here
  val++;
  val++;
  return 0;
}

but not for

int main() {
  {
    int val = 0;
    // Break here
    val++;
    val++;
  }
  {
    int other = 42;
    printf("assuming that other and val aliases");
  }
  return 0;
}

?

To be clear, I think this is still useful!

@aprantl Do you know if we can detect the end of the scope ? I'm not sure it's possible currently ... But even if we could do it, that wouldn't cover the following case:

int main() {
  int i = 0;
  {
back_in_scope:
    int val = 0;
    i++;
    // Break here
    val++;
    val++;
  }
  {
    if (i == 1)
      goto back_in_scope;
    printf("assuming that other and val aliases");
  }
  return 0;
}

If we disable the val variable watchpoint at the end of the scope, if we get back into it (because of a goto or whatever), the watchpoint won't trigger because it will disabled (by leaving the scope).

I think in order to solve these kind of problem we should do proper static analysis to be able to answer those questions.

bulbazord accepted this revision.May 24 2023, 6:57 PM

I have no objections but you should probably wait for others to take another look.

This revision is now accepted and ready to land.May 24 2023, 6:57 PM

@aprantl Do you know if we can detect the end of the scope ? I'm not sure it's possible currently ... But even if we could do it, that wouldn't cover the following case:

When setting a variable watchpoint, you would have to store the scope the variable is in, and then ignore all hits that are (1) on the same thread but (2) where the scope is not any of the lexical scopes in any of the stack frames. Because you still want to find modifications in child frames or on other threads.

mib added a comment.May 25 2023, 3:06 PM

@aprantl Do you know if we can detect the end of the scope ? I'm not sure it's possible currently ... But even if we could do it, that wouldn't cover the following case:

When setting a variable watchpoint, you would have to store the scope the variable is in, and then ignore all hits that are (1) on the same thread but (2) where the scope is not any of the lexical scopes in any of the stack frames. Because you still want to find modifications in child frames or on other threads.

As discussed offline, watchpoints still have many shortcomings that can't be addressed all at once, so I'll land this patch and document the various issues discussed here so hopefully we can find a solution for those.

This revision was automatically updated to reflect the committed changes.
JDevlieghere added inline comments.May 25 2023, 4:38 PM
lldb/source/Breakpoint/Watchpoint.cpp
127

I think this should return an llvm::Error and log it at the call site. For every early return we know what went wrong. Same for SetupVariableWatchpointDisabler.

140

Doesn't FUNCTION already include Watchpoint::? Also I think there's a flag to include the function name in the log, so this is redundant.

lldb/source/Commands/CommandObjectWatchpoint.cpp
959

You wouldn't need the (uint64_t) cast if you used formatv. This also should use a C++ style cast (https://discourse.llvm.org/t/rfc-add-preferred-casting-style-to-coding-standards/70793/4).

960–961

Status::AsCString isn't trivial so probably worth calling this one and storing it in a temporary variable.

mib marked an inline comment as done.May 25 2023, 4:51 PM
mib added inline comments.
lldb/source/Breakpoint/Watchpoint.cpp
127

This is a WatchpointCallback so it have to match a specific function signature (and return a bool). I could change SetupVariableWatchpointDisabler to return an llvm::Error in a follow-up

140

No, it only prints the method name, LLVM_PRETTY_FUNCTION prints everything but I think that macro is not defined with other compiler.

mib marked an inline comment as done.May 25 2023, 4:59 PM
mib added inline comments.
lldb/source/Commands/CommandObjectWatchpoint.cpp
959

Good point, but this code was already here before my patch, I just moved around to have early returns.

JDevlieghere added inline comments.May 25 2023, 5:10 PM
lldb/source/Breakpoint/Watchpoint.cpp
127

That's unfortunate but makes sense. Thanks for clarifying.

140

LLVM_PRETTY_FUNCTION is an llvm define, it expands to the appropriate macro based on the host compiler. You can totally use it but you must include Compiler.h.

antmo added a subscriber: antmo.May 26 2023, 6:12 AM

Hi @mib,
I think this broke lldb-arm-ubuntu bot (LocalVariableWatchpointDisabler.test failure): https://lab.llvm.org/buildbot/#/builders/17/builds/38136
Also visible on lldb-aarch64-windows here (TestVarPath.py issue is something else): https://lab.llvm.org/buildbot/#/builders/219/builds/3048
Could you please take a look ?

Hi @mib,
I think this broke lldb-arm-ubuntu bot (LocalVariableWatchpointDisabler.test failure): https://lab.llvm.org/buildbot/#/builders/17/builds/38136
Also visible on lldb-aarch64-windows here (TestVarPath.py issue is something else): https://lab.llvm.org/buildbot/#/builders/219/builds/3048
Could you please take a look ?

Thanks for pointing out the bot failures, I hadn't noticed them yet. I've reverted Ismail's change until he can look into it.