Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to list methods of struct/interface #2249

Open
llhuii opened this issue Dec 4, 2020 · 11 comments
Open

Allow users to list methods of struct/interface #2249

llhuii opened this issue Dec 4, 2020 · 11 comments

Comments

@llhuii
Copy link

llhuii commented Dec 4, 2020

It would be nice for developers to list all methods of a struct/interface in addition to its fields in dlv like python dir(object).

PS: though this expr doc says that dlv shows the concrete type of a interface, I also want to list all methods of a interface.

Additional information:

  1. What version of Delve are you using (dlv version)?
Delve Debugger
Version: 1.5.0
@icholy
Copy link
Contributor

icholy commented Mar 25, 2021

In the meantime, the funcs command lists all methods on types.

(dlv) funcs sync.atomic\.\(\*Value\)
sync/atomic.(*Value).Load
sync/atomic.(*Value).Store

@icholy
Copy link
Contributor

icholy commented Mar 25, 2021

What about a doc command which shows the godoc for the values type?

@komuw
Copy link

komuw commented May 10, 2021

I'm also interested. And I have found myself wanting this feature on more than one occasion.

Specifically:

Proposal:

Add ability to list the public API of any variable/type and also the public api of any import.
This will involve adding a new command(lets call it ptype for proposal purposes although I would be happy for any other name.)

Problem statement:

Whenever you are debugging using delve you want to know a number of things;
(a) What variables are available?
(b) What are the types of these variables?
(c) What are the values of these variables?
(d) What can I do with these variables(How can I use them)?
You ideally want to answer these questions without leaving the comfort of your debugging session/terminal.

For (a) we have locals, args, funcs and vars
For (b) we have print
For (c) we have whatis and types
For (d) however, delve is a bit lacking. When you have a variable/type and you want to know how you can use it; there is no delve command to do that. You have to use whatis to get the type AND then lookup that type either in godoc or https://pkg.go.dev/ or some other place to find the public API. This process involves leaving the debug session to go and use another tool; I think we can do better.

Usage:

Let's say you have a Go file like

package main

import (
	"compress/flate"
	"net/http"
	"time"
)

func main() {
	_ = flate.BestSpeed
	tomorrow := time.Hour * 24
	req, _ := http.NewRequest("GET", "https://google.com", nil)
}

You can then use the ptype delve command while debugging the above code like this

(dlv) ptype tomorrow
(dlv) ptype req
(dlv) ptype "compress/flate"

And when you do so, the public API of the requested variables/types or package import will be printed.

(dlv) ptype tomorrow
[
NAME: time.Duration
KIND: int64
SIGNATURE: [time.Duration]
FIELDS: []
METHODS: [
	Hours func(time.Duration) float64
	Microseconds func(time.Duration) int64
	Milliseconds func(time.Duration) int64
	Minutes func(time.Duration) float64
	Nanoseconds func(time.Duration) int64
	Round func(time.Duration, time.Duration) time.Duration
	Seconds func(time.Duration) float64
	String func(time.Duration) string
	Truncate func(time.Duration, time.Duration) time.Duration
	]
]
(dlv) ptype req
[
NAME: net/http.Request
KIND: struct
SIGNATURE: [*http.Request http.Request]
FIELDS: [
	Method string
	URL *url.URL
	Proto string
	ProtoMajor int
	ProtoMinor int
	... <truncated> ...
	]
METHODS: [
	AddCookie func(*http.Request, *http.Cookie)
	BasicAuth func(*http.Request) (string, string, bool)
	Clone func(*http.Request, context.Context) *http.Request
	... <truncated> ...
	]
]
(dlv) ptype  "compress/flate"
[
NAME: compress/flate
CONSTANTS: [
	BestCompression untyped int
	BestSpeed untyped int
	... <truncated> ...
	]
VARIABLES: []
FUNCTIONS: [
	NewReader(r io.Reader) io.ReadCloser
	NewReaderDict(r io.Reader, dict []byte) io.ReadCloser
	... <truncated> ...
	]
TYPES: [
	... <truncated> ...
	Writer struct
		(*Writer) Close() error
		(*Writer) Flush() error
		(*Writer) Reset(dst io.Writer)
		(*Writer) Write(data []byte) (n int, err error)
	CorruptInputError int64
		(CorruptInputError) Error() string]
]

Precedent:

The gdb debugger has a ptype command. See; https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_109.html
Which works like;

struct complex {double real; double imag;} v;

(gdb) whatis v
type = struct complex

(gdb) ptype v
type = struct complex {
    double real;
    double imag;
}

Implementation:

The implementation would use the reflect package with no third party dependency.
If this proposal were to be accepted, I'm willing to do most of the work to get this in. I already have a package that uses reflect to print out public API of variables/types/imports; see https://github.com/komuw/kama#usage
The implementation in delve would not use that package but would instead only rely on reflect.

This proposal can be broken into two parts:
(i) Add ability to list the public API of any variable/type.
(ii) Add ability to list the public api of any import.
I feel certain that we should do (i) but I'm not certain about (ii). The two parts can be accepted/rejected independently.

I can avail any other information required to help people be able to evaluate this proposal.

Thanks,

@komuw
Copy link

komuw commented May 10, 2021

I don't know whether to make my proposal an independent issue?

@komuw
Copy link

komuw commented May 10, 2021

Also a bit related to: #55

@icholy
Copy link
Contributor

icholy commented May 10, 2021

@komuw perhaps the new functionality could be a flag passed to whatis

struct complex {double real; double imag;} v;

(gdb) whatis v
type = struct complex

(gdb) whatis -verbose v
type = struct complex {
    double real;
    double imag;
}

@komuw
Copy link

komuw commented May 10, 2021

@icholy I would also be fine with that idea.

@hick
Copy link

hick commented Dec 30, 2021

I'm also interested. And I have found myself wanting this feature on more than one occasion.

Specifically:

Proposal:

Add ability to list the public API of any variable/type and also the public api of any import. This will involve adding a new command(lets call it ptype for proposal purposes although I would be happy for any other name.)

Problem statement:

Whenever you are debugging using delve you want to know a number of things; (a) What variables are available? (b) What are the types of these variables? (c) What are the values of these variables? (d) What can I do with these variables(How can I use them)? You ideally want to answer these questions without leaving the comfort of your debugging session/terminal.

For (a) we have locals, args, funcs and vars For (b) we have print For (c) we have whatis and types For (d) however, delve is a bit lacking. When you have a variable/type and you want to know how you can use it; there is no delve command to do that. You have to use whatis to get the type AND then lookup that type either in godoc or https://pkg.go.dev/ or some other place to find the public API. This process involves leaving the debug session to go and use another tool; I think we can do better.

Usage:

Let's say you have a Go file like

package main

import (
	"compress/flate"
	"net/http"
	"time"
)

func main() {
	_ = flate.BestSpeed
	tomorrow := time.Hour * 24
	req, _ := http.NewRequest("GET", "https://google.com", nil)
}

You can then use the ptype delve command while debugging the above code like this

(dlv) ptype tomorrow
(dlv) ptype req
(dlv) ptype "compress/flate"

And when you do so, the public API of the requested variables/types or package import will be printed.

(dlv) ptype tomorrow
[
NAME: time.Duration
KIND: int64
SIGNATURE: [time.Duration]
FIELDS: []
METHODS: [
	Hours func(time.Duration) float64
	Microseconds func(time.Duration) int64
	Milliseconds func(time.Duration) int64
	Minutes func(time.Duration) float64
	Nanoseconds func(time.Duration) int64
	Round func(time.Duration, time.Duration) time.Duration
	Seconds func(time.Duration) float64
	String func(time.Duration) string
	Truncate func(time.Duration, time.Duration) time.Duration
	]
]
(dlv) ptype req
[
NAME: net/http.Request
KIND: struct
SIGNATURE: [*http.Request http.Request]
FIELDS: [
	Method string
	URL *url.URL
	Proto string
	ProtoMajor int
	ProtoMinor int
	... <truncated> ...
	]
METHODS: [
	AddCookie func(*http.Request, *http.Cookie)
	BasicAuth func(*http.Request) (string, string, bool)
	Clone func(*http.Request, context.Context) *http.Request
	... <truncated> ...
	]
]
(dlv) ptype  "compress/flate"
[
NAME: compress/flate
CONSTANTS: [
	BestCompression untyped int
	BestSpeed untyped int
	... <truncated> ...
	]
VARIABLES: []
FUNCTIONS: [
	NewReader(r io.Reader) io.ReadCloser
	NewReaderDict(r io.Reader, dict []byte) io.ReadCloser
	... <truncated> ...
	]
TYPES: [
	... <truncated> ...
	Writer struct
		(*Writer) Close() error
		(*Writer) Flush() error
		(*Writer) Reset(dst io.Writer)
		(*Writer) Write(data []byte) (n int, err error)
	CorruptInputError int64
		(CorruptInputError) Error() string]
]

Precedent:

The gdb debugger has a ptype command. See; https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_109.html Which works like;

struct complex {double real; double imag;} v;

(gdb) whatis v
type = struct complex

(gdb) ptype v
type = struct complex {
    double real;
    double imag;
}

Implementation:

The implementation would use the reflect package with no third party dependency. If this proposal were to be accepted, I'm willing to do most of the work to get this in. I already have a package that uses reflect to print out public API of variables/types/imports; see https://github.com/komuw/kama#usage The implementation in delve would not use that package but would instead only rely on reflect.

This proposal can be broken into two parts: (i) Add ability to list the public API of any variable/type. (ii) Add ability to list the public api of any import. I feel certain that we should do (i) but I'm not certain about (ii). The two parts can be accepted/rejected independently.

I can avail any other information required to help people be able to evaluate this proposal.

Thanks,

good idea, I found this issue for almost the same purpose, to print a complex data structure without the souce code

@hitzhangjie
Copy link
Contributor

hitzhangjie commented Feb 10, 2022

I need this feature, and I am trying to implement this. Now I have a problem to solve.

Following is my testing:

$ dlv debug main.go 
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x10cb87b for main.main() ./main.go:17
(dlv) c
> main.main() ./main.go:17 (hits goroutine(1):1 total:1) (PC: 0x10cb87b)
    12:		def     int
    13:	}
    14:	
    15:	var _ Student = Student{}
    16:	
=>  17:	func main() {
    18:		ss := Student{Name: "xxxx"}
    19:		fmt.Println(ss)
    20:	}
(dlv) types Student
*main.Student
main.Student
(dlv) ptype main.Student
struct main.Student <nil>
(dlv) ptype int
int <nil>
(dlv) 

main.Student is defined and printed by ptype, it outputs its type is a struct, but the inner details is <nil>. This is useless.

I just use BinaryInfo.findType(name) to return the godwarf.Type, then call the godwarf.Type.String() to return the description of this type, but it's <nil>.

I expect godwarf.Type.String() would return the complete type description. Please let me know if anyone knows how to solve this problem.


I think I can do a type switch to handle case by case. For example, convert godwarf.Type to godwarf.StructType, then I get the struct fields. So it works like this now.

$ dlv debug main.go 
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x10cb87b for main.main() ./main.go:17
(dlv) ptype main.Student
struct main.Student
fields
	0 Name string
	1 Age int
	2 Sex int
	3 Address string
	4 School string
	5 abc int
	6 def int

Problem is godwarf.StructType may not contain field to access its methods. Correct me if I misunderstand.

I have 2 solutions which maybe not so good.
method1: We can traverse all the functions to find main.(*Student)* to list.
method2: The generated .[z]debug_info, main.(*Student).String() is DIE DW_TAG_subprogram, this DIE's first sibling is its receiver type, which points to *main.Student with tag DW_TAG_pointer_type.
I think when parsing dwarf, if we use some datastructure to store the mappings between the receiver type and its methods. That will accelerate the query speed compared to traversing all BinaryInfo.Functions.

todo:

  • how to list the methods.
  • Printing it recursively should be considered, like print <variable>, we also consider the recursive levels.
  • other godwarf.*Type should be supported.

Next, I want to see how print works, maybe there's some code that can be reused.

This modification is here: https://github.com/hitzhangjie/delve/tree/feat/ptype.


using method1 mentioned above, now I can list the method's name (without parameter list).

(dlv) ptype main.Student
struct main.Student
fields
	0 Name string
	1 Age int
	2 Sex int
	3 Address string
	4 School string
	5 abc int
	6 def int
methods
	String
	Sing
	Learn
 <nil>

After writing some code about DWARF, now it can list the parameter list (receiverType var is dropped) and return list:

$ dlv debug main.go
Type 'help' for list of commands.
(dlv) ptype main.Student
struct main.Student
fields
	0 Name string
	1 Age int
	2 Sex int
	3 Address string
	4 School string
	5 abc int
	6 def int
methods
	func Learn(a int,b int) (~r2 error)
 <nil>

@komuw
Copy link

komuw commented Feb 10, 2022

I had given this a shot one year ago and got it somehow working as a proof of concept with bad code.
The code can be found here: https://github.com/komuw/delve/tree/issues/2249-A
And instructions on how to run the demo are; https://github.com/komuw/delve/blob/issues/2249-A/example/main.go#L11-L36
basically;

A. Get the code and drop inside a docker container.
git clone git@github.com:komuw/delve.git
cd delve
git checkout issues/2249-A
docker-compose run app

B. build delve and debug the example application
rm -rf example/example /go/bin/dlv && \
go mod tidy && \
make install && \
go build -x -gcflags="all=-N -l" -ldflags='all=-linkshared' -o example/example example/main.go && \
/go/bin/dlv exec example/example

C. set breakpoint and execute various `whatis` commands
break example/main.go:86
(dlv) continue
(dlv) whatis -v f
(dlv) whatis -v hReq

when you do so, you get output like:

(dlv) whatis -v f
	 ===printVar:===
struct main.Fire {
  fields:
    HH main.Health
    Age int64
    Name string
    Dist main.Distance
    Day time.Time
  methods:
    Hello func(i int) string
    MethodTwo func() int64
}
	 ===printVar:===
main.Fire
whatis -v hReq
	 ===printVar:===
struct *net/http.Request {
  fields:
    Method string
    URL *net/url.URL
    Proto string
    ....
    TLS *crypto/tls.ConnectionState
    Cancel <-chan struct {}
    Response *net/http.Response
  methods:
    BasicAuth func() (username string, password string, ok bool)
    SetBasicAuth func(username string, password string)
    ...
    Clone func(ctx context.Context) *net/http.Request
    PostFormValue func(key string) string
    Write func(w io.Writer) error
    ParseMultipartForm func(maxMemory int64) error
    Cookies func() []*net/http.Cookie
}
	 ===printVar:===
*net/http.Request
(dlv) whatis -v MyFn
	 ===printVar:===
func(int, string, *uint64) void {
  methods:
    MethodOnFunc func() int64
}
	 ===printVar:===
main.fn

I don't have time to take it further than I did, so I'm just sharing whatever I have.

@hitzhangjie
Copy link
Contributor

@komuw Thanks for your sharing, I'm willing to learn your code later...and contribute this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants
@hick @aarzilli @llhuii @icholy @hitzhangjie @komuw and others