ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
33.69k stars 2.47k forks source link

Debug breakpoint is hit when it shouldn't be #19443

Open lawrence-laz opened 5 months ago

lawrence-laz commented 5 months ago

Zig Version

0.12.0-dev.2809+f3bd17772 0.12.0-dev.3439+31a7f22b8

Steps to Reproduce and Observed Behavior

Create a simple repro by running:

mkdir repro && cd repro
zig init
cat <<EOT > src/main.zig
const std = @import("std");

pub fn main() !void {
    var i: usize = 0;
    while (i < 100) : (i += 1) {
        if (i == 90) {
            std.debug.print("It's 90!", .{});
        } else if (i == 50) {
            std.debug.print("It's 50!", .{});
        }
    }
}
EOT
zig build
cd zig-out/bin/
lldb repro

Then in lldb console:

(lldb) b main.zig:9
Breakpoint 1: where = repro`main.main + 188 at main.zig:9:28, address = 0x0000000100000824
(lldb) run
Process 4864 launched: '~/zig/repro/zig-out/bin/repro' (arm64)
Process 4864 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000824 repro`main.main at main.zig:9:28
   6            if (i == 90) {
   7                std.debug.print("It's 90!", .{});
   8            } else if (i == 50) {
-> 9                std.debug.print("It's 50!", .{});
   10           }
   11       }
   12   }
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 0
(lldb) continue
Process 4864 resuming
Process 4864 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000824 repro`main.main at main.zig:9:28
   6            if (i == 90) {
   7                std.debug.print("It's 90!", .{});
   8            } else if (i == 50) {
-> 9                std.debug.print("It's 50!", .{});
   10           }
   11       }
   12   }
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 1

Notice how the breakpoint gets hit every cycle, even though the i == 50 should happen only once. Reading the i variable it prints 0, 1, etc.

Expected Behavior

Breakpoint should hit only once when i == 50.

mlugg commented 5 months ago

I think this is an manifestation of the slightly weird issue that when a block is skipped, debuggers will indicate us first jumping to the block's last line, and then we appear to enter the following code.

lawrence-laz commented 5 months ago

You might be onto something.

If the code is changed to:

const std = @import("std");

pub fn main() !void {
    var i: usize = 0;
    while (i < 100) : (i += 1) {
        if (i == 90) {
            std.debug.print("It's 90!", .{});
        } else if (i == 50) {
            const foo: usize = i + 10;
            var bar: usize = foo + 20;
            std.debug.print("It's inner {d}!", .{bar});
            bar += 1;
            std.debug.print("It's inner plus one {d}!", .{bar});
            std.debug.print("It's 50!", .{});
        }
    }
}

And breakpoint is in the middle of the block, then it behaves as expected:

(lldb) b main.zig:11
Breakpoint 2: where = repro`main.main + 320 at main.zig:11:28, address = 0x00000001000008a8
(lldb) run
Process 5813 launched: '~/zig/repro/zig-out/bin/repro' (arm64)
Process 5813 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00000001000008a8 repro`main.main at main.zig:11:28
   8            } else if (i == 50) {
   9                const foo: usize = i + 10;
   10               var bar: usize = foo + 20;
-> 11               std.debug.print("It's inner {d}!", .{bar});
   12               bar += 1;
   13               std.debug.print("It's inner plus one {d}!", .{bar});
   14               std.debug.print("It's 50!", .{});
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 50

Would this mean zig cannot do anything and it's the issue of a debugger? Maybe zig could output some noop debug info at the end of the block, so it wouldn't overlap with the last line in source code?

nektro commented 5 months ago

does this reproduce on 0.12.0-dev.3439+31a7f22b8 ?

lawrence-laz commented 5 months ago

Tried reproducing same with c++:

mkdir repro-cpp
cd repro-cpp
cat <<EOF > main.cpp
#include <iostream>

int main()
{
    int i = 0;
    while (i < 100)
    {
        if (i == 90) {
            std::cout << "It's 90!\n";
        }
        else if (i == 50) {
            std::cout << "It's 50!\n";
        }
        i += 1;
    }
}
EOF
g++ -g main.cpp
lldb a.out

and then in debugger

(lldb) b main.cpp:12
Breakpoint 1: where = a.out`main + 124 at main.cpp:12:23, address = 0x0000000100003128
(lldb) run
Process 6746 launched: '~/zig/repro/repro-cpp/a.out' (arm64)
Process 6746 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003128 a.out`main at main.cpp:12:23
   9                std::cout << "It's 90!\n";
   10           }
   11           else if (i == 50) {
-> 12               std::cout << "It's 50!\n";
   13           }
   14           i += 1;
   15       }
Target 0: (a.out) stopped.
(lldb) var i
(int) i = 50

seems to work fine, so it's probably not a debugger issue?

lawrence-laz commented 5 months ago

does this reproduce on 0.12.0-dev.3439+31a7f22b8 ?

@nektro tested on 0.12.0-dev.3439+31a7f22b8, same issue

mlugg commented 5 months ago

Would this mean Zig cannot do anything and it's the issue of a debugger?

No, this is still a Zig bug. I might try and take a look later today.