From d89d115ef9efb3acdb3a1052ac058b79f36a4940 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Fri, 20 Jan 2017 23:22:36 +0100 Subject: [PATCH] proc/variables: support NaN/Inf float values (#706) Unfortunately go/constant does not support NaN and Inf float values so we need to store this information alongside. Fixes #705 --- _fixtures/testvariables2.go | 7 ++++++- proc/eval.go | 10 ++++++++++ proc/variables.go | 21 ++++++++++++++++++++- service/api/conversions.go | 19 +++++++++++++++---- service/test/variables_test.go | 5 +++++ 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index b0a60e99..9ef5108c 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -3,6 +3,7 @@ package main import ( "fmt" "go/constant" + "math" "runtime" "unsafe" ) @@ -207,6 +208,10 @@ func main() { ni16 := int16(-5) ni32 := int32(-5) + pinf := math.Inf(+1) + ninf := math.Inf(-1) + nan := math.NaN() + var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { @@ -214,5 +219,5 @@ func main() { } runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, 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) + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, 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, pinf, ninf, nan) } diff --git a/proc/eval.go b/proc/eval.go index 01213fa2..be4ed2ec 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -3,6 +3,7 @@ package proc import ( "bytes" "encoding/binary" + "errors" "fmt" "go/ast" "go/constant" @@ -15,6 +16,8 @@ import ( "golang.org/x/debug/dwarf" ) +var OperationOnSpecialFloatError = errors.New("operations on non-finite floats not implemented") + // EvalExpression returns the value of the given expression. func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) { t, err := parser.ParseExpr(expr) @@ -688,6 +691,9 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { if xv.Unreadable != nil { return nil, xv.Unreadable } + if xv.FloatSpecial != 0 { + return nil, OperationOnSpecialFloatError + } if xv.Value == nil { return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) } @@ -794,6 +800,10 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { return nil, yv.Unreadable } + if xv.FloatSpecial != 0 || yv.FloatSpecial != 0 { + return nil, OperationOnSpecialFloatError + } + typ, err := negotiateType(node.Op, xv, yv) if err != nil { return nil, err diff --git a/proc/variables.go b/proc/variables.go index 0734422f..874c84f9 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -8,6 +8,7 @@ import ( "go/constant" "go/parser" "go/token" + "math" "reflect" "strings" "unsafe" @@ -29,6 +30,15 @@ const ( hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated ) +type FloatSpecial uint8 + +const ( + FloatIsNormal FloatSpecial = iota + FloatIsNaN + FloatIsPosInf + FloatIsNegInf +) + // Variable represents a variable. It contains the address, name, // type and other information parsed from both the Dwarf information // and the memory of the debugged process. @@ -43,7 +53,8 @@ type Variable struct { mem memoryReadWriter dbp *Process - Value constant.Value + Value constant.Value + FloatSpecial FloatSpecial Len int64 Cap int64 @@ -808,6 +819,14 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { var val float64 val, v.Unreadable = v.readFloatRaw(v.RealType.(*dwarf.FloatType).ByteSize) v.Value = constant.MakeFloat64(val) + switch { + case math.IsInf(val, +1): + v.FloatSpecial = FloatIsPosInf + case math.IsInf(val, -1): + v.FloatSpecial = FloatIsNegInf + case math.IsNaN(val): + v.FloatSpecial = FloatIsNaN + } case reflect.Func: v.readFunctionPtr() default: diff --git a/service/api/conversions.go b/service/api/conversions.go index 61a15f7e..680f90a1 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -98,6 +98,19 @@ func prettyTypeName(typ dwarf.Type) string { return r } +func convertFloatValue(v *proc.Variable, sz int) string { + switch v.FloatSpecial { + case proc.FloatIsPosInf: + return "+Inf" + case proc.FloatIsNegInf: + return "-Inf" + case proc.FloatIsNaN: + return "NaN" + } + f, _ := constant.Float64Val(v.Value) + return strconv.FormatFloat(f, 'f', -1, sz) +} + // ConvertVar converts from proc.Variable to api.Variable. func ConvertVar(v *proc.Variable) *Variable { r := Variable{ @@ -119,11 +132,9 @@ func ConvertVar(v *proc.Variable) *Variable { if v.Value != nil { switch v.Kind { case reflect.Float32: - f, _ := constant.Float64Val(v.Value) - r.Value = strconv.FormatFloat(f, 'f', -1, 32) + r.Value = convertFloatValue(v, 32) case reflect.Float64: - f, _ := constant.Float64Val(v.Value) - r.Value = strconv.FormatFloat(f, 'f', -1, 64) + r.Value = convertFloatValue(v, 64) case reflect.String, reflect.Func: r.Value = constant.StringVal(v.Value) default: diff --git a/service/test/variables_test.go b/service/test/variables_test.go index d6df2fa1..e4b5da57 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -448,6 +448,11 @@ func TestEvalExpression(t *testing.T) { {"str1[0:12]", false, "", "", "string", fmt.Errorf("index out of bounds")}, {"str1[5:3]", false, "", "", "string", fmt.Errorf("index out of bounds")}, + // NaN and Inf floats + {"pinf", false, "+Inf", "+Inf", "float64", nil}, + {"ninf", false, "-Inf", "-Inf", "float64", nil}, + {"nan", false, "NaN", "NaN", "float64", nil}, + // pointers {"*p2", false, "5", "5", "int", nil}, {"p2", true, "*5", "(*int)(0x…", "*int", nil}, -- GitLab