惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
Recorded Future
Recorded Future
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
N
News | PayPal Newsroom
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Help Net Security
Help Net Security
博客园 - Franky
SecWiki News
SecWiki News
Recent Announcements
Recent Announcements
T
Troy Hunt's Blog
The Register - Security
The Register - Security
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
S
Security Affairs
博客园 - 司徒正美
S
Schneier on Security
I
InfoQ
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
腾讯CDC
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic
Cloudbric
Cloudbric
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
B
Blog
V
Vulnerabilities – Threatpost
C
Check Point Blog
Google DeepMind News
Google DeepMind News
Google Online Security Blog
Google Online Security Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
Cisco Blogs
Schneier on Security
Schneier on Security
O
OpenAI News
K
Kaspersky official blog

NickChenyx's Blog

Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish Create_Rubbish
Create_Rubbish
nickChen · 2018-04-18 · via NickChenyx's Blog

本文主要用于分析几类 Golang 空指针引起的 panic 场景,这些场景遍布在日常开发代码中。但是出现 panic 时,真的能根据空指针信息准确定位出原因,找出故障代码吗?

来看看熟悉的 panic 信息

panic: runtime error: invalid memory address or nil pointer dereference

见识一下“熟悉”的 panic

空 map 赋值

source code

package main

func main() {
    var m map[int]int m[1] = 1
}
// Output:
// panic: assignment to entry in nil map

第一个需要避免的就是对未初始化的 map 进行赋值,当然,这个并不是本篇文章需要讨论的,但是笔者认为 map 作为常用的组件,还是需要在编码时避免此场景发生。

结构体指针未初始化

可以看出 t.n 结构体会跟随者 T 的初始化而完成初始化,此时 t.n.Log0()t.n.Log() 调用都成功了。
但是 t.np 结构体指针并不会随着 T 的初始化而完成初始化,此时 t.np 实际类型是 (*N)nil ,它是有类型的,类型为 *N,但是 t.np 值为 nil。因此 t.np.Log() 能正常调用,但是 t.np.Log0() 则会 panic

source code

package main

import (
    "fmt"
)

type T struct {
    n  N
    np *N
}

type N struct {
    n int
}

func (n N) Log0() {
    fmt.Println("n log0")
}

func (n *N) Log() {
    fmt.Println("n log")
}

func main() {
    t := T{}
    t.n.Log()
    t.n.Log0()
    t.np.Log()
    t.np.Log0()
}
// Output:
// n log
// n log0
// n log
// panic: runtime error: invalid memory address or nil pointer dereference

interface{} 未初始化

相较于结构体指针,当成员变量是一个接口(interface{})时,结果又会变得不一样。

此时 t.m 是一个接口 M,当初始化创建 T 时,如果不主动初始化 M ,那么此时 M 是一个 nil。调用 t.m.Log() 会造成 panic

source code 1

package main

import (
    "fmt"
)

type T struct {
    m M
}

type M interface {
    Log()
}

type M1 struct {
}

func (m *M1) Log() {
    fmt.Println("m1 log")
}

func main() {
    t := T{
        m: &M1{},
    }
    t.m.Log()

    t = T{}
    t.m.Log()
}
// Output:
// m1 log
// panic: runtime error: invalid memory address or nil pointer dereference

source code 2

package main

type M interface {
    Log()
}

func main() {
    var m M

    m.Log()
}
// Output:
// panic: runtime error: invalid memory address or nil pointer dereference

interface{} assign nil

这个 case 在代码中常有发生,根因还是在于 Golang 的 nil 机制有个特殊的地方,可以看source code 1

source code 1

package main

import (
    "fmt"
)

func main() {
    var i interface{}
    var n *int32
    if n == nil {
        fmt.Println("n is nil")
    }
    if i == nil {
        fmt.Println("i is nil")
    }
    i = n
    if i != nil {
        fmt.Println("after assign, i is not nil")
    }
}
// Output:
// n is nil
// i is nil
// after assign, i is not nil

基于这个规则,常见的业务中出现 panic 的场景可以看如下代码。在这个场景下,panic 的原因同结构体指针未初始化的场景一致,根因是对于变量是否为 nil 的判断上出了问题。

source code 2

package main

import (
    "fmt"
)

type M interface {
    Log()
}

type M1 struct {
}

func (m M1) Log0() {
    fmt.Println("log0")
}

func (m *M1) Log() {
    fmt.Println("log")
}

func main() {
    var i interface{}
    var m M
    var s *M1

    if i == nil {
        fmt.Println("i is nil")
    }

    i = m
    if i == nil {
        fmt.Println("after assign m, i is nil")
    }

    i = s
    if i != nil {
        fmt.Println("after assign s, i is not nil")
    }
    // 业务中常用 == nil 判断,然后快速失败 return
    if i == nil {
        return
    }
    // 业务中会认为此时的 i 就是非 nil 的了,然后开始进行方法调用
    // 和结构体指针未初始化的场景一样,这里调用 *M 的方法是可以进行的
    i.(*M1).Log()
    // 但是如果调用的是 M 的方法,那就出问题了,panic 随之而来
    i.(*M1).Log0()
}
// Output:
// i is nil
// after assign m, i is nil
// after assign s, i is not nil
// log
// panic: runtime error: invalid memory address or nil pointer dereference

要改变是否为 nil 的判断,确保这种 case 不会发生。可以使用:

func IsNil(x interface{}) {
    return x == nil || (reflect.ValueOf(x).Kind() == reflect.Ptr && reflect.ValueOf(x).IsNil())
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com