jinzhu / copier

Copier for golang, copy value from struct to struct and more
MIT License
5.58k stars 489 forks source link

Stack overflow in certain situation #107

Open ycydsxy opened 3 years ago

ycydsxy commented 3 years ago

Reproducible Example

please run in https://play.golang.org


package main

import ( "fmt" "github.com/jinzhu/copier" "test/a" "test/b" )

func main() { aNode := a.TreeNode{ ID: "a1", Children: []a.TreeNode{ { ID: "a11", }, }, } bNode := b.TreeNode{} _ = copier.Copy(&bNode, &aNode) fmt.Println(aNode, bNode)

} -- go.mod -- module test -- a/node.go -- package a

type TreeNode struct { ID string Children []TreeNode } -- b/node.go -- package b

type TreeNode struct { ID string Children []TreeNode }

## Description
The code above will result in a fatal **stack overflow** problem. It seems like that we cannot use recursive(such as the `TreeNode`) struct with the same name(even if they are in different packages). If we rename `a.TreeNode` to `a.TreeNode1`, the `copier` will work normally.

```golang
package main

import (
    "fmt"
    "github.com/jinzhu/copier"
    "test/a"
    "test/b"
)

func main() {
    aNode := a.TreeNode1{
        ID: "a1",
        Children: []a.TreeNode1{
            {
                ID: "a11",
            },
        },
    }
    bNode := b.TreeNode{}
    _ = copier.Copy(&bNode, &aNode)
    fmt.Println(aNode, bNode)

}
-- go.mod --
module test
-- a/node.go --
package a

type TreeNode1 struct {
    ID       string
    Children []TreeNode1
}
-- b/node.go --
package b

type TreeNode struct {
    ID       string
    Children []TreeNode
}
TobiasYin commented 3 years ago

I think that's a bug for golang. When I use 1.16.6 compiler, stack overflow happened. But I use 1.17rc1 compiler, It works fine.

TobiasYin commented 3 years ago

go 1.16.6 reflect/type.go:1590 does not check package path.

func haveIdenticalType(T, V Type, cmpTags bool) bool {
    if cmpTags {
        return T == V
    }

    if T.Name() != V.Name() || T.Kind() != V.Kind() {
        return false
    }

    return haveIdenticalUnderlyingType(T.common(), V.common(), false)
}

go 1.17 reflect/type.go:1635 fixed the bug.

func haveIdenticalType(T, V Type, cmpTags bool) bool {
    if cmpTags {
        return T == V
    }

    if T.Name() != V.Name() || T.Kind() != V.Kind() || T.PkgPath() != V.PkgPath() {
        return false
    }

    return haveIdenticalUnderlyingType(T.common(), V.common(), false)
}