Repository: VKCOM/noverify
Pull request #704 (2020-08-24 10:50:05)

linter: improved checking for duplicate cases
Added support for constants.


Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 501dc9f..85e87a0 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -411,6 +411,7 @@ func (b *blockLinter) checkGlobalStmt(s *ir.GlobalStmt) {
 func (b *blockLinter) checkSwitch(s *ir.SwitchStmt) {
 	nodeSet := &b.walker.r.nodeSet
 	nodeSet.Reset()
+	wasAdded := false
 	for i, c := range s.CaseList.Cases {
 		c, ok := c.(*ir.CaseStmt)
 		if !ok {
@@ -419,8 +420,59 @@ func (b *blockLinter) checkSwitch(s *ir.SwitchStmt) {
 		if !b.walker.sideEffectFree(c.Cond) {
 			continue
 		}
-		if !nodeSet.Add(c.Cond) {
-			b.report(c.Cond, LevelWarning, "dupCond", "duplicated switch case #%d", i+1)
+
+		var v meta.ConstValue
+		var isConstKey bool
+		if k, ok := c.Cond.(*ir.ConstFetchExpr); ok {
+			v = constfold.Eval(b.walker.r.ctx.st, k)
+			if !v.IsValid() {
+				continue
+			}
+			value := v.Value
+
+			switch v.Type {
+			case meta.Float:
+				val, ok := value.(float64)
+				if !ok {
+					continue
+				}
+				wasAdded = nodeSet.Add(&ir.Dnumber{Value: fmt.Sprint(val)})
+			case meta.Integer:
+				val, ok := value.(int64)
+				if !ok {
+					continue
+				}
+				wasAdded = nodeSet.Add(&ir.Lnumber{Value: fmt.Sprint(val)})
+			case meta.String:
+				val, ok := value.(string)
+				if !ok {
+					continue
+				}
+				wasAdded = nodeSet.Add(&ir.String{Value: fmt.Sprint(val)})
+			case meta.Bool:
+				val, ok := value.(bool)
+				if !ok {
+					continue
+				}
+				wasAdded = nodeSet.Add(&ir.Name{Value: fmt.Sprint(val)})
+			default:
+				continue
+			}
+			isConstKey = true
+		}
+
+		isDupKey := isConstKey && !wasAdded
+		if !isDupKey {
+			isDupKey = !nodeSet.Add(c.Cond)
+		}
+
+		if isDupKey {
+			msg := fmt.Sprintf("duplicated switch case #%d", i+1)
+			if isConstKey {
+				dupKey := getConstValue(v)
+				msg += " (value " + dupKey + ")"
+			}
+			b.report(c.Cond, LevelWarning, "dupCond", "%s", msg)
 		}
 	}
 }
diff --git a/src/linter/utils.go b/src/linter/utils.go
index 61216e0..86b8e7c 100644
--- a/src/linter/utils.go
+++ b/src/linter/utils.go
@@ -166,6 +166,14 @@ func classHasProp(className, propName string) bool {
 	return ok
 }
 
+func getConstValue(c meta.ConstValue) string {
+	if c.Type == meta.Undefined {
+		return ""
+	}
+
+	return fmt.Sprintf("%v", c.Value)
+}
+
 func binaryOpString(n ir.Node) string {
 	switch n.(type) {
 	case *ir.BitwiseAndExpr:


Test patch:
diff --git a/src/tests/checkers/codedup_test.go b/src/tests/checkers/codedup_test.go
index 55a775d..8d2b1eb 100644
--- a/src/tests/checkers/codedup_test.go
+++ b/src/tests/checkers/codedup_test.go
@@ -319,13 +319,22 @@ function f($cond) {
   case impurefunc(1): break;
   case impurefunc(1): break;
   }
+
+  switch ($cond) {
+  case 3: break; // Bad: duplicated value 3 (C3 = 3)
+  case 1: break;
+  case C1: break; // Bad: duplicated value 1 (C1 = 1)
+  case C3: break;
+  }
 }
 `)
 	test.Expect = []string{
 		`duplicated switch case #2`,
 		`duplicated switch case #4`,
-		`duplicated switch case #5`,
+		`duplicated switch case #5 (value 2)`,
 		`duplicated switch case #2`,
+		`duplicated switch case #3 (value 1)`,
+		`duplicated switch case #4 (value 3)`,
 	}
 	test.RunAndMatch()
 }