How to discover all package types at runtime?

后端 未结 5 1493
醉酒成梦
醉酒成梦 2020-12-14 02:55

As far as I\'m aware (see here, and here) there is no type discovery mechanism in the reflect package, which expects that you already have an instance of the type or value y

相关标签:
5条回答
  • 2020-12-14 03:30

    No there is not.

    If you want to 'know' your types you'll have to register them.

    0 讨论(0)
  • 2020-12-14 03:39

    (see bottom for 2019 update)

    Warning: untested and hacky. Can break whenever a new version of Go is released.

    It is possible to get all types the runtime knows of by hacking around Go's runtime a little. Include a small assembly file in your own package, containing:

    TEXT yourpackage·typelinks(SB), NOSPLIT, $0-0
        JMP reflect·typelinks(SB)
    

    In yourpackage, declare the function prototype (without body):

    func typelinks() []*typeDefDummy
    

    Alongside a type definition:

    type typeDefDummy struct {
        _      uintptr           // padding
        _      uint64            // padding
        _      [3]uintptr        // padding
        StrPtr *string           
    }
    

    Then just call typelinks, iterate over the slice and read each StrPtr for the name. Seek those starting with yourpackage. Note that if there are two packages called yourpackage in different paths, this method won't work unambiguously.

    can I somehow hook into the reflect package to instantiate new instances of those names?

    Yeah, assuming d is a value of type *typeDefDummy (note the asterisk, very important):

    t := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&d)))
    

    Now t is a reflect.Type value which you can use to instantiate reflect.Values.


    Edit: I tested and executed this code successfully and have uploaded it as a gist.

    Adjust package names and include paths as necessary.

    Update 2019

    A lot has changed since I originally posted this answer. Here's a short description of how the same can be done with Go 1.11 in 2019.

    $GOPATH/src/tl/tl.go

    package tl
    
    import (
        "unsafe"
    )
    
    func Typelinks() (sections []unsafe.Pointer, offset [][]int32) {
        return typelinks()
    }
    
    func typelinks() (sections []unsafe.Pointer, offset [][]int32)
    
    func Add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
        return add(p, x, whySafe)
    }
    
    func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer
    

    $GOPATH/src/tl/tl.s

    TEXT tl·typelinks(SB), $0-0
        JMP reflect·typelinks(SB)
    
    TEXT tl·add(SB), $0-0
        JMP reflect·add(SB)
    

    main.go

    package main
    
    import (
        "fmt"
        "reflect"
        "tl"
        "unsafe"
    )
    
    func main() {
        sections, offsets := tl.Typelinks()
        for i, base := range sections {
            for _, offset := range offsets[i] {
                typeAddr := tl.Add(base, uintptr(offset), "")
                typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
                fmt.Println(typ)
            }
        }
    }
    

    Happy hacking!

    0 讨论(0)
  • 2020-12-14 03:39

    Unfortunately, I don't think this is possible. Packages are not "actionable" in Go, you can't "call a function" on it. You can't call a function on a type either, but you can call reflect.TypeOf on an instance of the type and get reflect.Type which is a runtime abstraction of a type. There just isn't such mechanism for packages, there isn't a reflect.Package.

    With that said, you could file an issue about the absence of (and practicality of adding) reflect.PackageOf etc.

    0 讨论(0)
  • 2020-12-14 03:44

    Thanks @thwd and @icio, follow your direction it still worked on 1.13.6 today.

    Follow your way the tl.s will be:

    TEXT ·typelinks(SB), $0-0
        JMP reflect·typelinks(SB)
    

    yes, no package name and no "add" function in it.

    then follow @icio's way change "add" function to:

    func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
        return unsafe.Pointer(uintptr(p) + x)
    }
    

    then all worked now. :)

    0 讨论(0)
  • 2020-12-14 03:46

    In Go 1.5, you can use the new package types and importer to inspect binary and source packages. For example:

    package main
    
    import (
        "fmt"
        "go/importer"
    )
    
    func main() {
        pkg, err := importer.Default().Import("time")
        if err != nil {
            fmt.Printf("error: %s\n", err.Error())
            return
        }
        for _, declName := range pkg.Scope().Names() {
            fmt.Println(declName)
        }
    }
    

    You can use the package go/build to extract all the packages installed. Or you can configure the Lookup importer to inspect binaries outside the environment.

    Before 1.5, the only no-hacky way is to use the package ast to compile the source code.

    0 讨论(0)
提交回复
热议问题