ps: 运行环境是Linux和MacOS, 编译文件是MacOS ARM64下的
问题
打开一个不存在的文件, 调用f.Name()
, 没有发生panic
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("there_is_nothing")
fmt.Println(f)
name := f.Name()
if err != nil {
fmt.Println("err:", err.Error())
return
}
fmt.Println(name)
}
这里我加上一句
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("there_is_nothing")
fmt.Println(f)
name := f.Name()
fmt.Println(123) // 加上这一句
if err != nil {
fmt.Println("err:", err.Error())
return
}
fmt.Println(name)
}
运行后
❯ go run main.go
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102978dd8]
goroutine 1 [running]:
os.(*File).Name(...)
/opt/homebrew/Cellar/go/1.24.3/libexec/src/os/file.go:62
main.main()
/tmp/funny/main.go:12 +0x78
exit status 2
看到这个东西的第一反应是大概率编译器优化,发现既然 name := f.Name()
的值只在 err == nil
分支被用到, 于是把相关的加载和调用指令移到条件判断之后
go build -gcflags="all=-S" -o main main.go
go tool objdump -s main.main main
导出看看
go tool objdump -s main.main main
TEXT main.main(SB) /tmp/funny/main.go
main.go:8 0x10009cd60 f9400b90 MOVD 16(R28), R16
main.go:8 0x10009cd64 d10043f1 SUB $16, RSP, R17
main.go:8 0x10009cd68 eb10023f CMP R16, R17
main.go:8 0x10009cd6c 540009a9 BLS 77(PC)
main.go:8 0x10009cd70 f8170ffe MOVD.W R30, -144(RSP)
main.go:8 0x10009cd74 f81f83fd MOVD R29, -8(RSP)
main.go:8 0x10009cd78 d10023fd SUB $8, RSP, R29
main.go:9 0x10009cd7c d503201f NOOP
file.go:370 0x10009cd80 d0000000 ADRP 8192(PC), R0
file.go:370 0x10009cd84 91313800 ADD $3150, R0, R0
file.go:370 0x10009cd88 b27c03e1 ORR $16, ZR, R1
file.go:370 0x10009cd8c aa1f03e2 MOVD ZR, R2
file.go:370 0x10009cd90 aa1f03e3 MOVD ZR, R3
file.go:370 0x10009cd94 97ffda8b CALL os.OpenFile(SB)
file.go:370 0x10009cd98 f90023e0 MOVD R0, 64(RSP)
file.go:370 0x10009cd9c f9001be1 MOVD R1, 48(RSP)
file.go:370 0x10009cda0 f9001fe2 MOVD R2, 56(RSP)
main.go:10 0x10009cda4 f0000244 ADRP 307200(PC), R4
main.go:10 0x10009cda8 91358084 ADD $3424, R4, R4
main.go:10 0x10009cdac f9003fe4 MOVD R4, 120(RSP)
main.go:10 0x10009cdb0 f90043e0 MOVD R0, 128(RSP)
print.go:314 0x10009cdb4 b00006db ADRP 888832(PC), R27
print.go:314 0x10009cdb8 f9471761 MOVD 3624(R27), R1
print.go:314 0x10009cdbc 90000280 ADRP 327680(PC), R0
print.go:314 0x10009cdc0 910c6000 ADD $792, R0, R0
print.go:314 0x10009cdc4 9101e3e2 ADD $120, RSP, R2
print.go:314 0x10009cdc8 b24003e3 ORR $1, ZR, R3
print.go:314 0x10009cdcc aa0303e4 MOVD R3, R4
print.go:314 0x10009cdd0 97ffecec CALL fmt.Fprintln(SB)
main.go:12 0x10009cdd4 d503201f NOOP
main.go:15 0x10009cdd8 f9401be0 MOVD 48(RSP), R0
main.go:15 0x10009cddc b4000380 CBZ R0, 28(PC)
main.go:16 0x10009cde0 f9400c01 MOVD 24(R0), R1
main.go:16 0x10009cde4 f9401fe0 MOVD 56(RSP), R0
main.go:16 0x10009cde8 d63f0020 CALL (R1)
main.go:16 0x10009cdec a905ffff STP (ZR, ZR), 88(RSP)
main.go:16 0x10009cdf0 a906ffff STP (ZR, ZR), 104(RSP)
main.go:16 0x10009cdf4 b00001a2 ADRP 217088(PC), R2
main.go:16 0x10009cdf8 91348042 ADD $3360, R2, R2
main.go:16 0x10009cdfc f9002fe2 MOVD R2, 88(RSP)
main.go:16 0x10009ce00 f0000262 ADRP 323584(PC), R2
main.go:16 0x10009ce04 9135a042 ADD $3432, R2, R2
main.go:16 0x10009ce08 f90033e2 MOVD R2, 96(RSP)
main.go:16 0x10009ce0c 97ff222d CALL runtime.convTstring(SB)
main.go:16 0x10009ce10 b00001a1 ADRP 217088(PC), R1
main.go:16 0x10009ce14 91348021 ADD $3360, R1, R1
main.go:16 0x10009ce18 f90037e1 MOVD R1, 104(RSP)
main.go:16 0x10009ce1c f9003be0 MOVD R0, 112(RSP)
print.go:314 0x10009ce20 b00006db ADRP 888832(PC), R27
print.go:314 0x10009ce24 f9471761 MOVD 3624(R27), R1
print.go:314 0x10009ce28 90000280 ADRP 327680(PC), R0
print.go:314 0x10009ce2c 910c6000 ADD $792, R0, R0
print.go:314 0x10009ce30 910163e2 ADD $88, RSP, R2
print.go:314 0x10009ce34 b27f03e3 ORR $2, ZR, R3
print.go:314 0x10009ce38 aa0303e4 MOVD R3, R4
print.go:314 0x10009ce3c 97ffecd1 CALL fmt.Fprintln(SB)
main.go:17 0x10009ce40 f85f83fd MOVD -8(RSP), R29
main.go:17 0x10009ce44 f84907fe MOVD.P 144(RSP), R30
main.go:17 0x10009ce48 d65f03c0 RET
file.go:62 0x10009ce4c f94023e2 MOVD 64(RSP), R2
file.go:62 0x10009ce50 f9400042 MOVD (R2), R2
file.go:62 0x10009ce54 f9401c40 MOVD 56(R2), R0
file.go:62 0x10009ce58 f9402041 MOVD 64(R2), R1
main.go:20 0x10009ce5c a904ffff STP (ZR, ZR), 72(RSP)
main.go:20 0x10009ce60 97ff2218 CALL runtime.convTstring(SB)
main.go:20 0x10009ce64 b00001a2 ADRP 217088(PC), R2
main.go:20 0x10009ce68 91348042 ADD $3360, R2, R2
main.go:20 0x10009ce6c f90027e2 MOVD R2, 72(RSP)
main.go:20 0x10009ce70 f9002be0 MOVD R0, 80(RSP)
print.go:314 0x10009ce74 b00006db ADRP 888832(PC), R27
print.go:314 0x10009ce78 f9471761 MOVD 3624(R27), R1
print.go:314 0x10009ce7c 90000280 ADRP 327680(PC), R0
print.go:314 0x10009ce80 910c6000 ADD $792, R0, R0
print.go:314 0x10009ce84 910123e2 ADD $72, RSP, R2
print.go:314 0x10009ce88 b24003e3 ORR $1, ZR, R3
print.go:314 0x10009ce8c aa0303e4 MOVD R3, R4
print.go:314 0x10009ce90 97ffecbc CALL fmt.Fprintln(SB)
main.go:21 0x10009ce94 f85f83fd MOVD -8(RSP), R29
main.go:21 0x10009ce98 f84907fe MOVD.P 144(RSP), R30
main.go:21 0x10009ce9c d65f03c0 RET
main.go:8 0x10009cea0 aa1e03e3 MOVD R30, R3
main.go:8 0x10009cea4 97ff39d7 CALL runtime.morestack_noctxt.abi0(SB)
main.go:8 0x10009cea8 17ffffae JMP main.main(SB)
main.go:8 0x10009ceac 00000000 ?
关键部分
0x10009cdd8 MOVD 48(RSP), R0 ; R0 = err
0x10009cddc CBZ R0, 28(PC) ; err==nil -> 跳到 0x10009cdf8
──────── err != nil 路径 ────────
0x10009cde0 MOVD 24(R0), R1
0x10009cde4 MOVD 56(RSP), R0 ; fmt.Fprintln 参数
0x10009cde8 CALL (R1) ; err.Error()
... ; 打印 & RET
───────────────────────────────
0x10009cdf4 STP (ZR,ZR), 88(RSP)
0x10009cdf8 ADD $3360, R2, R2 ; 构造 &file.name 常量
0x10009ce0c CALL runtime.convTstring; *File.name → string
0x10009ce20 … ; 接着准备 fmt.Fprintln
0x10009ce3c CALL fmt.Fprintln(SB) ; 打印 name
而如果加上了一行print的话
file.go:62 0x10009cdd4 f9402be4 MOVD 80(RSP), R4
file.go:62 0x10009cdd8 f9400084 MOVD (R4), R4
main.go:12 0x10009cddc d503201f NOOP
file.go:62 0x10009cde0 f9401c85 MOVD 56(R4), R5
file.go:62 0x10009cde4 f90027e5 MOVD R5, 72(RSP)
file.go:62 0x10009cde8 f9402084 MOVD 64(R4), R4
file.go:62 0x10009cdec f9001fe4 MOVD R4, 56(RSP)
main.go:13 0x10009cdf0 b00001a4 ADRP 217088(PC), R4
main.go:13 0x10009cdf4 913d8084 ADD $3936, R4, R4
main.go:13 0x10009cdf8 f90047e4 MOVD R4, 136(RSP)
main.go:13 0x10009cdfc f0000124 ADRP 159744(PC), R4
main.go:13 0x10009ce00 913d0084 ADD $3904, R4, R4
main.go:13 0x10009ce04 f9004be4 MOVD R4, 144(RSP)
print.go:314 0x10009ce08 b00006db ADRP 888832(PC), R27
print.go:314 0x10009ce0c f9471761 MOVD 3624(R27), R1
print.go:314 0x10009ce10 90000280 ADRP 327680(PC), R0
print.go:314 0x10009ce14 910c6000 ADD $792, R0, R0
print.go:314 0x10009ce18 910223e2 ADD $136, RSP, R2
print.go:314 0x10009ce1c b24003e3 ORR $1, ZR, R3
print.go:314 0x10009ce20 aa0303e4 MOVD R3, R4
print.go:314 0x10009ce24 97ffecd7 CALL fmt.Fprintln(SB)
f.Name()
在CBZ之前就取了, 自然就panic了
结语
这种本应暴露的空指针错误却被优化掉,个人认为不应该依赖这种行为,也不应把它视为"好特性" (
后续找到有个类似的issue