Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-10544] After change in lldb, false still evaluates to true in if statement #4570

Open
swift-ci opened this issue Apr 24, 2019 · 12 comments
Labels
bug Something isn't working LLDB for Swift

Comments

@swift-ci
Copy link

Previous ID SR-10544
Radar None
Original Reporter ripbenfranklin (JIRA User)
Type Bug
Environment

Xcode 10.2

macOS Mojave 10.14.4

Additional Detail from JIRA
Votes 0
Component/s LLDB for Swift
Labels Bug
Assignee None
Priority Medium

md5: 6c1fdbd8b5de1a60fdc0867d368abe38

Issue Description:

After changing a boolean to false in lldb, it's still evaluating to true. Here's a simplified version.

=> is a breakpoint

```
func getCount(actionWasSuccessful successful: Bool) -> Int {
=> var count = 0

// (lldb) po successful (returns true)
// (lldb) exp successful = false
// (lldb) po successful (returns false)

if successful {
=> count += 1 // breakpoint stops here
} else {
=> count = 0 // breakpoint should stop here
}
return count
}

let count = getCount(successful: true)
print(count) // returns 1
```

@belkadan
Copy link

I'm surprised LLDB lets you do that assignment at all. successful isn't mutable.

cc @dcci

@dcci
Copy link
Mannequin

dcci mannequin commented Apr 25, 2019

This is really interesting, let me see if I can take a look.

@dcci
Copy link
Mannequin

dcci mannequin commented Apr 25, 2019

Slightly modified the example so that it compiles:

 func getCount(actionWasSuccessful successful: Bool) -> Int {
var count = 0
// (lldb) po successful (returns true)
// (lldb) exp successful = false
// (lldb) po successful (returns false)
if successful
{ count += 1 // breakpoint stops here
}
else {
 count = 0 // breakpoint should stop here
}
return count
}
let count = getCount(actionWasSuccessful: true)
print(count) // returns 1

@dcci
Copy link
Mannequin

dcci mannequin commented Apr 25, 2019

I think somehow lldb synthetizes the injected `successful` as `var` instead of `let`.

I agree this should be not allowed and we should have an error like:

a.swift:3:1: error: cannot assign to value: 'successful' is a 'let' constant
successful = true
^~~~~~~~~~

@dcci
Copy link
Mannequin

dcci mannequin commented Apr 25, 2019

This seems a more general problem with the way the expression parser handles reassignment.

let b = 23
print(b)

Here breaking on the `print` line and running `expr b = 23423423423` succeeds.
This clearly violates the rules of the language and creates problem down the road as the one the user reported.

jingham@apple.com (JIRA User) do you know if this is intended or just something that crept in?
we shouldn't allow non-mutable bindings to be modified, as we can't control all the side effects.

@dcci
Copy link
Mannequin

dcci mannequin commented Apr 25, 2019

This is <rdar://problem/50210692>

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

To be clear, I think there are two issues here. One is dealing with modifying let bindings. The other is the inability to change var variable values for reals. I think they are orthogonal issues, and the latter is (to most users) the more important one. So, for instance, change the example to:

func getCount(inSuccessful: Bool) -> Int {
  var count = 0
  var successful = inSuccessful
  print(successful)
  
    // (lldb) po successful (returns true)
    // (lldb) exp successful = false
    // (lldb) po successful (returns false)


    if successful {
      count += 1 // breakpoint stops here
    } else {
      count = 0 // breakpoint should stop here
    }
    return count
}


var input = true
let count = getCount(inSuccessful: input)
print(count) // returns 1

Then in lldb do:

(lldb) br s -p print
Breakpoint 1: 2 locations.
(lldb) run
Process 61167 launched: '/tmp/success' (x86_64)
Process 61167 stopped
* thread #&#8203;1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #&#8203;0: 0x0000000100000e51 success`success.getCount(inSuccessful: Swift.Bool) -> Swift.Int at success.swift:4
   1    func getCount(inSuccessful: Bool) -> Int {
   2      var count = 0
   3      var successful = inSuccessful
-> 4      print(successful)
                ^
   5      
   6        // (lldb) po successful (returns true)
   7        // (lldb) exp successful = false
Target 0: (success) stopped.
(lldb) fr v successful
(Bool) successful = true
(lldb) expr successful = false
(lldb) fr v successful
(Bool) successful = false

So yay, we thought we had changed successful - which is a var so that's legit. But:

(lldb) n
true
Process 61167 stopped
* thread #&#8203;1, queue = 'com.apple.main-thread', stop reason = step over
    frame #&#8203;0: 0x0000000100000ec0 success`success.getCount(inSuccessful: Swift.Bool) -> Swift.Int at success.swift:10
   7        // (lldb) exp successful = false
   8        // (lldb) po successful (returns false)
   9    
-> 10       if successful {
               ^
   11         count += 1 // breakpoint stops here
   12       } else {
   13         count = 0 // breakpoint should stop here
Target 0: (success) stopped.
(lldb) n
Process 61167 stopped
* thread #&#8203;1, queue = 'com.apple.main-thread', stop reason = step over
    frame #&#8203;0: 0x0000000100000ec8 success`success.getCount(inSuccessful: Swift.Bool) -> Swift.Int at success.swift:11
   8        // (lldb) po successful (returns false)
   9    
   10       if successful {
-> 11         count += 1 // breakpoint stops here
                    ^
   12       } else {
   13         count = 0 // breakpoint should stop here
   14       }
Target 0: (success) stopped.

So the actual code paid no attention to the changed value.

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

As for the let vrs. var question, I don't see anything in the debug information for the local variables that would tell me the kind of the variable. For instance, in the example above, if I add:

  let let_success = inSuccessful

and then look at the debug info, I see:

0x000000c9:         DW_TAG_variable
                      DW_AT_location    (DW_OP_fbreg -24)
                      DW_AT_name        ("successful")
                      DW_AT_decl_file   ("/tmp/success.swift")
                      DW_AT_decl_line   (4)
                      DW_AT_type        (0x000000f6 "$sSbD")


0x000000d7:         DW_TAG_variable
                      DW_AT_location    (0x00000000
                         [0x0000000100000e35,  0x0000000100000ee5): DW_OP_breg6 RBP-29)
                      DW_AT_name        ("let_success")
                      DW_AT_decl_file   ("/tmp/success.swift")
                      DW_AT_decl_line   (3)
                      DW_AT_type        (0x000000f6 "$sSbD")

successful is a var, let_success is a let, but I can't see anything in the debug info that distinguishes them. Also, the parameter is:

0x000000a0:       DW_TAG_formal_parameter
                    DW_AT_location      (DW_OP_fbreg -8)
                    DW_AT_name  ("inSuccessful")
                    DW_AT_decl_file     ("/tmp/success.swift")
                    DW_AT_decl_line     (1)
                    DW_AT_type  (0x000000f6 "$sSbD")

which also looks exactly the same as the other "var" and "let" variables.

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

To make things more confusing, sometimes the compiler injects let values as real constants - in which case the DWARF reflects that (it has a DW_AT_const_value attribute) and we properly don't allow you to change it. Sometimes the values are stored on the stack - as above - and then we have no way to know we shouldn't change it.

It looks like we need to add some other attribute for "let" variables that says they are formally invariable even though they are actually values the debugger could change.

@belkadan
Copy link

Presumably C has this concept with const?

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

In C the const is always on the type not the variable - expressed by having the variable's DW_AT_type point to a DW_TAG_const_type rather than DW_TAG_type. It's not even an attribute that you COULD attach to the DW_TAG_variable. Swift types don't point to DWARF TAG's, and in Swift the type is not different between "let" and "var" variables. So the C way of doing things isn't going to work here.

DWARF has the notion of "a named constant" that it describes with DW_TAG_constant. But I don't think we can use that because it is really a distinct entity from Variables and Parameters. So it wouldn't allow us to describe a parameter as a let value, you can't be both a DW_TAG_constant and a DW_TAG_formal_parameter.

Also I don't think that let variables are what DWARF is trying to describe with DW_TAG_constant, these are "true named constants". Among other things, they are supposed to have values and names, but not locations. And from the examples above it looks like you don't always describe let variables as constant values, but instead give memory locations. That makes sense, since let variables can be arbitrarily complex and you wouldn't want to describe the value in DWARF. But it makes the tag not really right for this usage.

@belkadan
Copy link

Thanks for the explanation!

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working LLDB for Swift
Projects
None yet
Development

No branches or pull requests

2 participants