Repository: VKCOM/noverify
Pull request #671 (2020-08-13 14:32:02)

solver,meta: refactor const-related code
Move const defs and functions to a separate files.

Get rid of -1 multiplier hack to handle unary minus.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/src/linter/root.go b/src/linter/root.go
index fe46a42..7c52914 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -1646,7 +1646,7 @@ func (d *RootWalker) enterConstList(lst *ir.ConstListStmt) bool {
 	for _, sNode := range lst.Consts {
 		s := sNode.(*ir.ConstantStmt)
 
-		value, _ := solver.GetConstantValue(s)
+		value, _ := solver.GetConstantValue(s.Expr)
 
 		id := s.ConstantName
 		nm := d.ctx.st.Namespace + `\` + id.Value
diff --git a/src/meta/const_value.go b/src/meta/const_value.go
new file mode 100644
index 0000000..6a61c81
--- /dev/null
+++ b/src/meta/const_value.go
@@ -0,0 +1,101 @@
+package meta
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type ConstantValueType uint8
+
+const (
+	Undefined ConstantValueType = iota
+	Integer
+	Float
+	String
+)
+
+type ConstantValue struct {
+	Type  ConstantValueType
+	Value interface{}
+}
+
+func (c ConstantValue) GobEncode() ([]byte, error) {
+	switch c.Type {
+	case Float:
+		val, ok := c.Value.(float64)
+		if !ok {
+			return nil, fmt.Errorf("corrupted float")
+		}
+		str := fmt.Sprintf("%c%f", c.Type, val)
+		return []byte(str), nil
+	case Integer:
+		val, ok := c.Value.(int64)
+		if !ok {
+			return nil, fmt.Errorf("corrupted integer")
+		}
+		str := fmt.Sprintf("%c%d", c.Type, val)
+		return []byte(str), nil
+	case String:
+		val, ok := c.Value.(string)
+		if !ok {
+			return nil, fmt.Errorf("corrupted string")
+		}
+		str := fmt.Sprintf("%c%s", c.Type, val)
+		return []byte(str), nil
+	}
+
+	return nil, fmt.Errorf("unhandeled type")
+}
+
+func (c *ConstantValue) GobDecode(buf []byte) error {
+	if len(buf) < 1 {
+		return fmt.Errorf("data corrupted")
+	}
+
+	tp := ConstantValueType(buf[0])
+	buf = buf[1:]
+	val := string(buf)
+
+	switch tp {
+	case Float:
+		value, err := strconv.ParseFloat(val, 64)
+		if err != nil {
+			return fmt.Errorf("invalid float")
+		}
+		c.Value = value
+	case Integer:
+		value, err := strconv.ParseInt(val, 10, 64)
+		if err != nil {
+			return fmt.Errorf("invalid integer")
+		}
+		c.Value = value
+	case String:
+		c.Value = val
+	}
+
+	c.Type = tp
+
+	return nil
+}
+
+func (c ConstantValue) String() string {
+	if c.Type == Undefined {
+		return "Undefined type"
+	}
+	return fmt.Sprintf("%d: %v", c.Type, c.Value)
+}
+
+func (c ConstantValue) IsEqual(v ConstantValue) bool {
+	if v.Type == Undefined || c.Type == Undefined {
+		return false
+	}
+
+	return c.Value == v.Value
+}
+
+type ConstantInfo struct {
+	Pos         ElementPosition
+	Typ         TypesMap
+	AccessLevel AccessLevel
+	Value       ConstantValue
+}
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index b4acf47..f6058ce 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -1,13 +1,10 @@
 package meta
 
 import (
-	"fmt"
-	"strconv"
 	"strings"
 	"sync"
 
 	"github.com/VKCOM/noverify/src/ir"
-	"github.com/VKCOM/noverify/src/ir/irutil"
 )
 
 var (
@@ -369,118 +366,6 @@ type PropertyInfo struct {
 	AccessLevel AccessLevel
 }
 
-type ConstantValueType uint8
-
-const (
-	Undefined ConstantValueType = iota
-	Integer
-	Float
-	String
-)
-
-type ConstantValue struct {
-	Type  ConstantValueType
-	Value interface{}
-}
-
-func (c ConstantValue) GobEncode() ([]byte, error) {
-	switch c.Type {
-	case Float:
-		val, ok := c.Value.(float64)
-		if !ok {
-			return nil, fmt.Errorf("corrupted float")
-		}
-		str := fmt.Sprintf("%c%f", c.Type, val)
-		return []byte(str), nil
-	case Integer:
-		val, ok := c.Value.(int64)
-		if !ok {
-			return nil, fmt.Errorf("corrupted integer")
-		}
-		str := fmt.Sprintf("%c%d", c.Type, val)
-		return []byte(str), nil
-	case String:
-		val, ok := c.Value.(string)
-		if !ok {
-			return nil, fmt.Errorf("corrupted string")
-		}
-		str := fmt.Sprintf("%c%s", c.Type, val)
-		return []byte(str), nil
-	}
-
-	return nil, fmt.Errorf("unhandeled type")
-}
-
-func (c *ConstantValue) GobDecode(buf []byte) error {
-	if len(buf) < 1 {
-		return fmt.Errorf("data corrupted")
-	}
-
-	tp := ConstantValueType(buf[0])
-	buf = buf[1:]
-	val := string(buf)
-
-	switch tp {
-	case Float:
-		value, err := strconv.ParseFloat(val, 64)
-		if err != nil {
-			return fmt.Errorf("invalid float")
-		}
-		c.Value = value
-	case Integer:
-		value, err := strconv.ParseInt(val, 10, 64)
-		if err != nil {
-			return fmt.Errorf("invalid integer")
-		}
-		c.Value = value
-	case String:
-		c.Value = val
-	}
-
-	c.Type = tp
-
-	return nil
-}
-
-func (c ConstantValue) String() string {
-	if c.Type == Undefined {
-		return "Undefined type"
-	}
-	return fmt.Sprintf("%d: %v", c.Type, c.Value)
-}
-
-func (c ConstantValue) IsEqual(v ConstantValue) bool {
-	if v.Type == Undefined || c.Type == Undefined {
-		return false
-	}
-
-	return c.Value == v.Value
-}
-
-func NewConstantValueFromString(value string) ConstantValue {
-	v := irutil.Unquote(value)
-	return ConstantValue{Value: v, Type: String}
-}
-
-func NewConstantValueFromFloat(value float64) ConstantValue {
-	return ConstantValue{Value: value, Type: Float}
-}
-
-func NewConstantValueFromInt(value int64) ConstantValue {
-	return ConstantValue{Value: value, Type: Integer}
-}
-
-func NewUndefinedConstantValue() ConstantValue {
-	return ConstantValue{Type: Undefined}
-}
-
-type ConstantInfo struct {
-	Pos         ElementPosition
-	Typ         TypesMap
-	AccessLevel AccessLevel
-	Value       ConstantValue
-}
-
 type ClassFlags uint8
 
 const (
diff --git a/src/solver/const_value.go b/src/solver/const_value.go
new file mode 100644
index 0000000..f9ae2b7
--- /dev/null
+++ b/src/solver/const_value.go
@@ -0,0 +1,43 @@
+package solver
+
+import (
+	"strconv"
+
+	"github.com/VKCOM/noverify/src/ir"
+	"github.com/VKCOM/noverify/src/ir/irutil"
+	"github.com/VKCOM/noverify/src/meta"
+)
+
+var undefinedValue = meta.ConstantValue{Type: meta.Undefined}
+
+func GetConstantValue(c ir.Node) (meta.ConstantValue, bool) {
+	switch c := c.(type) {
+	case *ir.Lnumber:
+		value, err := strconv.ParseInt(c.Value, 10, 64)
+		return meta.ConstantValue{Type: meta.Integer, Value: value}, err == nil
+
+	case *ir.Dnumber:
+		value, err := strconv.ParseFloat(c.Value, 64)
+		return meta.ConstantValue{Type: meta.Float, Value: value}, err == nil
+
+	case *ir.String:
+		v := irutil.Unquote(c.Value)
+		return meta.ConstantValue{Value: v, Type: meta.String}, true
+
+	case *ir.UnaryMinusExpr:
+		v, ok := GetConstantValue(c.Expr)
+		if !ok {
+			return undefinedValue, false
+		}
+		switch value := v.Value.(type) {
+		case int64:
+			return meta.ConstantValue{Type: meta.Integer, Value: -value}, true
+		case float64:
+			return meta.ConstantValue{Type: meta.Float, Value: -value}, true
+		}
+		return v, true
+
+	default:
+		return undefinedValue, false
+	}
+}
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index 3c6de37..ab2f6f5 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -2,7 +2,6 @@ package solver
 
 import (
 	"log"
-	"strconv"
 	"strings"
 
 	"github.com/VKCOM/noverify/src/ir"
@@ -379,33 +378,3 @@ func ExprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	}
 	return res
 }
-
-func GetConstantValue(c *ir.ConstantStmt) (meta.ConstantValue, bool) {
-	switch c := c.Expr.(type) {
-	case *ir.Lnumber:
-		return getConstantValue(c, 1)
-	case *ir.Dnumber:
-		return getConstantValue(c, 1)
-	case *ir.String:
-		return getConstantValue(c, 1)
-	case *ir.UnaryMinusExpr:
-		return getConstantValue(c.Expr, -1)
-	default:
-		return meta.NewUndefinedConstantValue(), false
-	}
-}
-
-func getConstantValue(n ir.Node, modifier int64) (meta.ConstantValue, bool) {
-	switch c := n.(type) {
-	case *ir.Lnumber:
-		value, err := strconv.ParseInt(c.Value, 10, 64)
-		return meta.NewConstantValueFromInt(value * modifier), err == nil
-	case *ir.Dnumber:
-		value, err := strconv.ParseFloat(c.Value, 64)
-		return meta.NewConstantValueFromFloat(value * float64(modifier)), err == nil
-	case *ir.String:
-		return meta.NewConstantValueFromString(c.Value), true
-	default:
-		return meta.NewUndefinedConstantValue(), false
-	}
-}


Test patch:
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index 9111945..9703b22 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -28,6 +28,8 @@ func TestCache(t *testing.T) {
 
 	code := `<?php
 const GLOBAL_CONST = 1;
+const FLOAT_CONST = 153.5;
+const NEGATIVE_INT_CONST = -43;
 
 $globalIntVar = 10;
 $globalStringVar = 'string';
@@ -111,7 +113,7 @@ main();
 		//
 		// If cache encoding changes, there is a very high chance that
 		// encoded data lengh will change as well.
-		wantLen := 4064
+		wantLen := 4296
 		haveLen := buf.Len()
 		if haveLen != wantLen {
 			t.Errorf("cache len mismatch:\nhave: %d\nwant: %d", haveLen, wantLen)
@@ -120,7 +122,7 @@ main();
 		// 2. Check cache "strings" hash.
 		//
 		// It catches new fields in cached types, field renames and encoding of additional named attributes.
-		wantStrings := "d2a5e66052a3e5e124dcd761d355da18c5863a07b93d12eb79e82c7fd8d07fbaae8b253a61e2b94e58c40fbdeb24bcc56d23aa0543c75510ff74336c3dc47bfe"
+		wantStrings := "10dbc28ec87d69246d9b65c17638491e5463ae05a589b11753df79ecbe6ce1f61810a4243a90cc53254c9cea280b22f9fd5b34ee468a299e258bdc2b878982b0"
 		haveStrings := collectCacheStrings(buf.String())
 		if haveStrings != wantStrings {
 			t.Errorf("cache strings mismatch:\nhave: %q\nwant: %q", haveStrings, wantStrings)