golang fmt递归引起stack overflow异常

前言

我想大家都有这么一个场景,在类里实现一个string方法,供调用方去格式化输出。但如果你的string()里包含了类本身,会引起golang stack overflow.

该文章后续仍在不断的更新修改中, 请移步到原文地址  http://xiaorui.cc/?p=5909

问题重现:

下面是我的问题代码

// xiaorui.cc

package main

import (
    "fmt"
)

type people struct {
    Name string
}

func (p *people) std() {
    fmt.Printf("print: %v", p)
}

func (p *people) String() string {
    return fmt.Sprintf("print: %v", p)
}

func main() {
    p := &people{}
    p.std()
}

运行会报错,终端错误信息如下:

// xiaorui.cc

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x10c53c2, 0xe)
    /usr/local/go/src/runtime/panic.go:608 +0x72
runtime.newstack()
    /usr/local/go/src/runtime/stack.go:1008 +0x729
runtime.morestack()
    /usr/local/go/src/runtime/asm_amd64.s:429 +0x8f

// xiaorui.cc
goroutine 1 [running]:
fmt.Sprintf(0x10c49ac, 0x9, 0xc0240003a0, 0x1, 0x1, 0xc00949ae90, 0xc0240003d0)
    /usr/local/go/src/fmt/print.go:201 +0xdd fp=0xc024000368 sp=0xc024000360 pc=0x108adbd
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c300)
    /Users/ruifengyun/test/p.go:16 +0x70 fp=0xc0240003c0 sp=0xc024000368 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c300, 0x76, 0xc00949c301)
    /usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc024000450 sp=0xc0240003c0 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c300, 0x10ab4a0, 0xc00000e1e0, 0x76)
    /usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc0240004e8 sp=0xc024000450 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c300, 0x10c49ac, 0x9, 0xc024000660, 0x1, 0x1)
    /usr/local/go/src/fmt/print.go:1003 +0x166 fp=0xc0240005d0 sp=0xc0240004e8 pc=0x1091bc6
fmt.Sprintf(0x10c49ac, 0x9, 0xc024000660, 0x1, 0x1, 0xc00949ae40, 0xc024000690)
    /usr/local/go/src/fmt/print.go:203 +0x66 fp=0xc024000628 sp=0xc0240005d0 pc=0x108ad46
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c240)
    /Users/ruifengyun/test/p.go:16 +0x70 fp=0xc024000680 sp=0xc024000628 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c240, 0x76, 0xc00949c201)
    /usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc024000710 sp=0xc024000680 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c240, 0x10ab4a0, 0xc00000e1e0, 0x76)
    /usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc0240007a8 sp=0xc024000710 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c240, 0x10c49ac, 0x9, 0xc024000920, 0x1, 0x1)
    /usr/local/go/src/fmt/print.go:1003 +0x166 fp=0xc024000890 sp=0xc0240007a8 pc=0x1091bc6
fmt.Sprintf(0x10c49ac, 0x9, 0xc024000920, 0x1, 0x1, 0xc00949adf0, 0xc024000950)
    /usr/local/go/src/fmt/print.go:203 +0x66 fp=0xc0240008e8 sp=0xc024000890 pc=0x108ad46
main.(*people).String(0xc00000e1e0, 0x10cad08, 0xc00949c180)
    /Users/ruifengyun/test/p.go:16 +0x70 fp=0xc024000940 sp=0xc0240008e8 pc=0x1093070
fmt.(*pp).handleMethods(0xc00949c180, 0x76, 0xc00949c101)
    /usr/local/go/src/fmt/print.go:603 +0x27c fp=0xc0240009d0 sp=0xc024000940 pc=0x108da8c
fmt.(*pp).printArg(0xc00949c180, 0x10ab4a0, 0xc00000e1e0, 0x76)
    /usr/local/go/src/fmt/print.go:686 +0x203 fp=0xc024000a68 sp=0xc0240009d0 pc=0x108ded3
fmt.(*pp).doPrintf(0xc00949c180, 0x10c49ac, 0x9, 0xc024000be0, 0x1, 0x1)

什么原因?

我们直接看golang的fmt源码,文件 go/src/fmt/print.go 。我们的类有string方法,fmt的stringer接口也有个string方法,可以说我们间接的实现了stringer接口,所以递归了。

// xiaorui.cc

type Stringer interface {
    String() string
}


func (p *pp) handleMethods(verb rune) (handled bool) {
	if p.fmt.sharpV {
		if stringer, ok := p.arg.(GoStringer); ok {
			handled = true
			defer p.catchPanic(p.arg, verb, "GoString")
			// Print the result of GoString unadorned.
			p.fmt.fmtS(stringer.GoString())
			return
		}
	} else {
		switch verb {
		case 'v', 's', 'x', 'X', 'q':
			switch v := p.arg.(type) {
			case error:
				handled = true
				defer p.catchPanic(p.arg, verb, "Error")
				p.fmtString(v.Error(), verb)
				return

			case Stringer:
				handled = true
				defer p.catchPanic(p.arg, verb, "String")
                // 这里
				p.fmtString(v.String(), verb)
				return
			}
		}
	}
	return false
} 

怎么解决?

换个string这个函数名,这个跟fmt的stringer interface冲突了。 或者不要直接把类传进去,而是把属性传递进去。

总结:

这个fmt引起的stack overflow有点奇葩。这是第二次遇到stack overflow了,上次因为递归风控计算,没控制好逻辑,直接爆了。 😅


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc