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-7808] LLDB can't find identifiers from breakpoints within closures that were passed into an enclosing function #4496

Open
swift-ci opened this issue May 31, 2018 · 12 comments
Labels
bug Something isn't working DebugInfo LLDB for Swift

Comments

@swift-ci
Copy link

Previous ID SR-7808
Radar rdar://problem/30948792
Original Reporter twof (JIRA User)
Type Bug
Environment

Xcode 9.4. Also observed in Xcode 9.3 and earlier

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

md5: fe2513eff690d40288f61c4f3ae2ea0d

Issue Description:

Repro code:

func foo(source: String, callback: (String) -> ()) {
    callback("hello")
}

func bar(source: String) {
    foo(source: source) { (anything) in
        print(anything) // (lldb) po source
                        // error: <EXPR>:3:1: error: use of unresolved identifier 'source'
                        // source
                        // ^~~~~~
    }
}

bar(source: "anything")


let source = "anything"
foo(source: source) { param in
    print(param) // (lldb) po source
                 // "anything"
}
@belkadan
Copy link

@swift-ci create

@swift-ci
Copy link
Author

Comment by Fred Riss (JIRA)

This works if "source" is captured by the closure. When the closure is invoked synchronously, what you ask for makes sense, but what if the closure is invoked on a different thread? The local you want to access might not exist anymore. I don't see a good way for the debugger to solve this.

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

In current TOT and swift-4.2 the behavior is a little worse. Now the debug information mentions source, but if you don't actually use the variable, the value doesn't seem to be set up properly. The value is wrong and the attempt to "po" it fails.

(lldb) run

Process 67938 launched: '/tmp/sf7808' (x86_64)

Process 67938 stopped

4

5 func bar(source: String) {

6 foo(source: source) { (anything) in

-> 7 print(anything) // (lldb) po source

              ^

8 // error: <EXPR>:3:1: error: use of unresolved identifier 'source'

9 // source

10 // ^~~~~~

Target 0: (sf7808) stopped.

(lldb) fr v source

(String) source = ""

(lldb) po source

expression produced error: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x18).

The process has been returned to the state before expression evaluation.

{{ }}

@swift-ci
Copy link
Author

Comment by Fred Riss (JIRA)

I think what happens here is that the "let source" global that comes later in the code has been displayed, but has not yet been initialized. I doubt it's the local 'source' variable

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

Yup, I forgot that "frame var source" will look more widely (including file globals) for variables than "frame var" which only looks in the current frame locals. So I thought this must be a local, but it isn't.

@belkadan
Copy link

Personally I don't object to it not finding the variable, but we should try to give a better error message.

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

Jordan, that's the error message we get from the swift compiler. What would you propose as a better error message?

@swift-ci
Copy link
Author

Comment by Alex Reilly (JIRA)

I noticed something like that in Swift 4.1 too but I opted not to reported since it seemed like a different issue and I couldn't pin it down jingham@apple.com (JIRA User)

The above code all together

func foo(source: String, callback: (String) -> ()) {
    callback("hello")
}

func bar(source: String) {
    foo(source: source) { (anything) in
        print(anything) // (lldb) po source
                        // "" 
                        // THIS IS THE BIT THAT SEEMS ODD
    }
}

bar(source: "anything")


let source = "anything"
foo(source: source) { param in
    print(param) // (lldb) po source
                 // "anything"
}

The above code seperate

func foo(source: String, callback: (String) -> ()) {
    callback("hello")
}

func bar(source: String) {
    foo(source: source) { (anything) in
        print(anything) // (lldb) po source
                        // error: <EXPR>:3:1: error: use of unresolved identifier 'source'
                        // source
                        // ^~~~~~
    }
}

bar(source: "anything")
func foo(source: String, callback: (String) -> ()) {
    callback("hello")
}

let source = "anything"
foo(source: source) { param in
    print(param) // (lldb) po source
                 // "anything"
}

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

I'm pretty sure that oddness is just because swift lazily initializes file static data. So when you do the "po source" in bar, source hasn't been initialized yet. I don't know if we could get the expression parser to force initialization to happen when it refers to a variable, and even if we could, I'm not sure that that would actually be desirable, since it would add another obscure way the debugger changes program execution.

@swift-ci
Copy link
Author

Comment by Alex Reilly (JIRA)

func foo(other: String, callback: (String) -> ()) {
 callback("hello")
}

func bar(somethingElse: String) {
 foo(other: somethingElse) { (anything) in
     print(anything) // (lldb) e print(source)
                     //
                     // (lldb) e source = "hello"
                     // (lldb) po source
                     // "hello"
 }
}

bar(somethingElse: "anything")


let source = "anything"

Maybe I'm misunderstanding how the debugger is meant to work, but if `let source = "anything"` hasn't been executed yet, and compilation fails if I put `print(source)` within `foo`'s closure, I shouldn't be able to execute code that wouldn't compile if it was on the same line as the breakpoint right?

@swift-ci
Copy link
Author

Comment by Jim Ingham (JIRA)

First off, more generally that last statement is not correct. The expression parser lets you access variables that aren't in scope (or even in the same module.) It violates protections, and it will allow you to see globals and use types that aren't visible in the scope of the current frame. It's pretty common to want the debugger to show you some variable or call some method that isn't visible in the current scope. The goal (and it's mostly true) is that any legal code in the current frame will also run successfully and with the same result in an expression. But it is not true that you can cut and paste a successful debugger expression into the current frame and have it run.

However in your case, the requirement that you shouldn't be able to execute code in an expression that wouldn't compile "in situ" doesn't actually help. For instance, this compiles:

func foo(other: String, callback: (String) -> ()) {
    callback("hello")
}

func bar(somethingElse: String) {
    foo(other: somethingElse) { (anything) in
        print(anything) // (lldb) e print(source)
        print(source)
                        //
                        // (lldb) e source = "hello"
                        // (lldb) po source
                        // "hello"
    }
}

let source = "anything"

bar(somethingElse: "anything")

So accessing source at this point is in fact legal.

Of course if you switch the order of the "let source" and the CALL to bar, then it wouldn't compile. BTW, the error in that case is:

whatever.swift:8:15: error: use of unresolved identifier 'source'
        print(source)
              ^~~~~~

To get lldb to emulate this aspect of swift correctly would require it to reason about the path taken to reach the code it is currently in, and whether that was arrived at before or after "source" was initialized. I doubt we actually can do that reliably in all cases. The effect of getting that reasoning wrong would be to prevent your access to valid data in the debugger, something that we really try to avoid.

@swift-ci
Copy link
Author

swift-ci commented Jun 1, 2018

Comment by Alex Reilly (JIRA)

Oh I see! Thanks a bunch for the explanation 🙂

I didn't know Swift acts a bit differently at the global scope, so while your snippet compiles, this won't

func main() {
    func foo(other: String, callback: (String) -> ()) {
        callback("hello")
    }

    func bar(somethingElse: String) {
        foo(other: somethingElse) { (anything) in
            print(source)
            print(anything) 
        }
    }

    let source = "anything"
    bar(somethingElse: "anything")
}

@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 DebugInfo LLDB for Swift
Projects
None yet
Development

No branches or pull requests

2 participants