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

Identical Expression Comparison analyzer #7066

Merged
merged 11 commits into from
Aug 21, 2020
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ nogo(
"//tools/analyzers/cryptorand:go_tool_library",
"//tools/analyzers/errcheck:go_tool_library",
"//tools/analyzers/featureconfig:go_tool_library",
"//tools/analyzers/comparesame:go_tool_library",
] + select({
# nogo checks that fail with coverage enabled.
":coverage_enabled": [],
Expand Down
7 changes: 7 additions & 0 deletions nogo_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,12 @@
"shared/rand/rand\\.go": "Abstracts CSPRNGs for common use",
"shared/aggregation/testing/bitlistutils.go": "Test-only package"
}
},
"comparesame": {
"exclude_files": {
"external/.*": "Third party code",
"rules_go_work-.*": "Third party code",
"tools/analyzers/comparesame/testdata/compare_len.go": "Analyzer testdata has to break rules"
}
}
}
28 changes: 28 additions & 0 deletions tools/analyzers/comparesame/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("@prysm//tools/go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_tool_library")

go_library(
name = "go_default_library",
srcs = ["analyzer.go"],
importpath = "github.com/prysmaticlabs/prysm/tools/analyzers/comparesame",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis:go_default_library",
"@org_golang_x_tools//go/analysis/passes/inspect:go_default_library",
"@org_golang_x_tools//go/ast/inspector:go_default_library",
],
)

go_tool_library(
name = "go_tool_library",
srcs = ["analyzer.go"],
importpath = "github.com/prysmaticlabs/prysm/tools/analyzers/comparesame",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/inspect:go_tool_library",
"@org_golang_x_tools//go/ast/inspector:go_tool_library",
],
)

# gazelle:exclude analyzer_test.go
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to ignore tests because of #7074

65 changes: 65 additions & 0 deletions tools/analyzers/comparesame/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package comparesame

import (
"bytes"
"errors"
"go/ast"
"go/printer"
"go/token"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)

// Doc explaining the tool.
const Doc = "Tool to detect comparison (==, !=, >=, <=, >, <) of identical boolean expressions."

const messageTemplate = "Boolean expression has identical expressions on both sides. The result is always %v."

// Analyzer runs static analysis.
var Analyzer = &analysis.Analyzer{
Name: "comparesame",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !ok {
return nil, errors.New("analyzer is not type *inspector.Inspector")
}

nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil),
}

inspect.Preorder(nodeFilter, func(node ast.Node) {
expr, ok := node.(*ast.BinaryExpr)
if !ok {
return
}

switch expr.Op {
case token.EQL, token.NEQ, token.GEQ, token.LEQ, token.GTR, token.LSS:
var xBuf, yBuf bytes.Buffer
if err := printer.Fprint(&xBuf, pass.Fset, expr.X); err != nil {
pass.Reportf(expr.X.Pos(), err.Error())
}
if err := printer.Fprint(&yBuf, pass.Fset, expr.Y); err != nil {
pass.Reportf(expr.Y.Pos(), err.Error())
}
if xBuf.String() == yBuf.String() {
switch expr.Op {
case token.EQL, token.NEQ, token.GEQ, token.LEQ:
pass.Reportf(expr.OpPos, messageTemplate, true)
case token.GTR, token.LSS:
pass.Reportf(expr.OpPos, messageTemplate, false)
}
}
}
})

return nil, nil
}
11 changes: 11 additions & 0 deletions tools/analyzers/comparesame/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package comparesame

import (
"testing"

"golang.org/x/tools/go/analysis/analysistest"
)

func TestAnalyzer(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analyzer)
}
8 changes: 8 additions & 0 deletions tools/analyzers/comparesame/testdata/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@prysm//tools/go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["compare_len.go"],
importpath = "github.com/prysmaticlabs/prysm/tools/analyzers/comparesame/testdata",
visibility = ["//visibility:public"],
)
37 changes: 37 additions & 0 deletions tools/analyzers/comparesame/testdata/compare_len.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package testdata

func Equal() {
x := []string{"a"}
if len(x) == len(x) { // want "Boolean expression has identical expressions on both sides. The result is always true."
}
}

func NotEqual() {
x := []string{"a"}
if len(x) != len(x) { // want "Boolean expression has identical expressions on both sides. The result is always true."
}
}

func GreaterThanOrEqual() {
x := []string{"a"}
if len(x) >= len(x) { // want "Boolean expression has identical expressions on both sides. The result is always true."
}
}

func LessThanOrEqual() {
x := []string{"a"}
if len(x) <= len(x) { // want "Boolean expression has identical expressions on both sides. The result is always true."
}
}

func GreaterThan() {
x := []string{"a"}
if len(x) > len(x) { // want "Boolean expression has identical expressions on both sides. The result is always false."
}
}

func LessThan() {
x := []string{"a"}
if len(x) < len(x) { // want "Boolean expression has identical expressions on both sides. The result is always false."
}
}