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