proc,service: pretty print time.Time variables (#2865)

Fixes #999
This commit is contained in:
Alessandro Arzilli 2022-03-25 21:59:57 +01:00 committed by GitHub
parent 9a9c1a9f33
commit 2b97231e30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 9 deletions

@ -5,6 +5,7 @@ import (
"go/constant"
"math"
"runtime"
"time"
"unsafe"
)
@ -359,6 +360,10 @@ func main() {
w5 := &W5{nil}
w5.W5 = w5
tim1 := time.Unix(233431200, 0)
loc, _ := time.LoadLocation("Mexico/BajaSur")
tim2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2022-06-07 02:03:04", loc)
var amb1 = 1
runtime.Breakpoint()
for amb1 := 0; amb1 < 10; amb1++ {
@ -369,5 +374,5 @@ func main() {
longslice := make([]int, 100, 100)
runtime.Breakpoint()
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl)
fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2)
}

@ -13,6 +13,7 @@ import (
"sort"
"strconv"
"strings"
"time"
"unsafe"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
@ -1304,6 +1305,9 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) {
v.Children[i].loadValueInternal(recurseLevel+1, cfg)
}
}
if t.Name == "time.Time" {
v.formatTime()
}
case reflect.Interface:
v.loadInterface(recurseLevel, true, cfg)
@ -2454,3 +2458,63 @@ type constantValuesByValue []constantValue
func (v constantValuesByValue) Len() int { return len(v) }
func (v constantValuesByValue) Less(i int, j int) bool { return v[i].value < v[j].value }
func (v constantValuesByValue) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
const (
timeTimeWallHasMonotonicBit uint64 = (1 << 63) // hasMonotonic bit of time.Time.wall
//lint:ignore ST1011 addSeconds is the name of the relevant function
maxAddSeconds time.Duration = (time.Duration(^uint64(0)>>1) / time.Second) * time.Second // maximum number of seconds that can be added with (time.Time).Add, measured in nanoseconds
wallNsecShift = 30 // size of the nanoseconds field of time.Time.wall
unixTimestampOfWallEpoch = -2682288000 // number of seconds between the unix epoch and the epoch for time.Time.wall (1 jan 1885)
)
// formatTime writes formatted value of a time.Time to v.Value.
// See $GOROOT/src/time/time.go for a description of time.Time internals.
func (v *Variable) formatTime() {
wallv := v.fieldVariable("wall")
extv := v.fieldVariable("ext")
if wallv == nil || extv == nil || wallv.Unreadable != nil || extv.Unreadable != nil || wallv.Value == nil || extv.Value == nil {
return
}
var loc *time.Location
locv := v.fieldVariable("loc")
if locv != nil && locv.Unreadable == nil {
namev := locv.loadFieldNamed("name")
if namev != nil && namev.Unreadable == nil {
name := constant.StringVal(namev.Value)
loc, _ = time.LoadLocation(name)
}
}
wall, _ := constant.Uint64Val(wallv.Value)
ext, _ := constant.Int64Val(extv.Value)
hasMonotonic := (wall & timeTimeWallHasMonotonicBit) != 0
if hasMonotonic {
// the 33-bit field of wall holds a 33-bit unsigned wall
// seconds since Jan 1 year 1885, and ext holds a signed 64-bit monotonic
// clock reading, nanoseconds since process start
sec := int64(wall << 1 >> (wallNsecShift + 1)) // seconds since 1 Jan 1885
t := time.Unix(sec+unixTimestampOfWallEpoch, 0).UTC()
if loc != nil {
t = t.In(loc)
}
v.Value = constant.MakeString(fmt.Sprintf("%s, %+d", t.Format(time.RFC3339), ext))
} else {
// the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext
var t time.Time
for ext > int64(maxAddSeconds/time.Second) {
t = t.Add(maxAddSeconds)
ext -= int64(maxAddSeconds / time.Second)
}
t = t.Add(time.Duration(ext) * time.Second)
if loc != nil {
t = t.In(loc)
}
v.Value = constant.MakeString(t.Format(time.RFC3339))
}
}

@ -261,7 +261,7 @@ func VariableValueAsString(v *proc.Variable) string {
return convertFloatValue(v, 32)
case reflect.Float64:
return convertFloatValue(v, 64)
case reflect.String, reflect.Func:
case reflect.String, reflect.Func, reflect.Struct:
return constant.StringVal(v.Value)
default:
if cd := v.ConstDescr(); cd != "" {

@ -89,6 +89,10 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
}
}
case reflect.Struct:
if v.Value != "" {
fmt.Fprintf(buf, "%s(%s)", v.Type, v.Value)
includeType = false
}
v.writeStructTo(buf, newlines, includeType, indent, fmtstr)
case reflect.Interface:
if v.Addr == 0 {

@ -2291,7 +2291,7 @@ func TestVariablesMetadata(t *testing.T) {
disconnect: false,
}, {
execute: func() {
checkStop(t, client, 1, "main.main", 372)
checkStop(t, client, 1, "main.main", -1)
client.VariablesRequest(localsScope)
locals := client.ExpectVariablesResponse(t)
@ -5966,12 +5966,7 @@ func TestSetVariable(t *testing.T) {
execute: func() {
tester := &helperForSetVariable{t, client}
startLineno := 364 // after runtime.Breakpoint
if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
startLineno = -1
}
checkStop(t, client, 1, "main.main", startLineno)
checkStop(t, client, 1, "main.main", -1)
locals := tester.variables(localsScope)
// channel

@ -850,6 +850,10 @@ func TestEvalExpression(t *testing.T) {
{"unknownthing(2)", false, "", "", "", errors.New("could not evaluate function or type unknownthing: could not find symbol value for unknownthing")},
{"(*unknownthing)(2)", false, "", "", "", errors.New("could not evaluate function or type (*unknownthing): could not find symbol value for unknownthing")},
{"(*strings.Split)(2)", false, "", "", "", errors.New("could not evaluate function or type (*strings.Split): could not find symbol value for strings")},
// pretty printing special types
{"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil},
{"tim2", false, `time.Time(2022-06-07T02:03:04-06:00)…`, `time.Time(2022-06-07T02:03:04-06:00)…`, "time.Time", nil},
}
ver, _ := goversion.Parse(runtime.Version())