Saturday, May 18, 2024
HomeProgrammingDiscovering Bugs in Code Programmatically Utilizing Go | by Amit Davidson |...

Discovering Bugs in Code Programmatically Utilizing Go | by Amit Davidson | Oct, 2022


Write a static code analyzer to seek out bugs in code

Picture by Pixabay from Pexels

It is a weblog put up primarily based on a workshop I gave a 12 months in the past at GopherCon on static evaluation in Go. The workshop covers far more than I may cowl on this article, so you may try the workshop right here.

In line with Wikipedia, Static code evaluation is the evaluation of pc packages carried out with out executing them. You is likely to be already aware of instruments doing static evaluation:

  • A linter for detecting styling errors and fixing them.
  • Constructed-in IDE analyzers for detecting completely different points corresponding to infinite loops, unreachable code, and declaration redundancy.
  • The go analyzer staticcheck and the open supply platform SonarQube.

Why you must care? Code analyzers are a staple when working in groups with many builders. They’re used within the CI/CD course of to implement that no dangerous practices, bugs, and anti-patterns are pushed to the principle repo.

The Go language offers the go/evaluation bundle for creating static code analyzers. Earlier than speaking about the right way to write these instruments, we have to introduce a little bit of concept.

An summary syntax tree (AST) is a approach of representing the syntax of a programming language as a hierarchical tree-like construction. Let’s check out the next program:

That is the AST for this system.

[]ast.Decl comprises all of the top-level declarations within the file — imports and the principle perform. Inside ast.FuncDecl, which is the foremost perform declaration, we’ve a ast.blockStmt. It’s a record of the perform statements- the precise code of the principle perform. We will hold happening the tree, however you may simply match the tree node to the precise code. (I eliminated some nodes from the tree to make it concise).

Go offers the go/ast bundle. The AST bundle comprises the kinds used to symbolize syntax bushes in Go, as within the tree above. Let’s check out ast.AssignStmt. The next code is from the go/ast bundle:

The expression a := 5 is an project, so it might be represented in AST as an ast.AssignStmt as follows:

  • Lhs is [a]
  • TokPos [2] (the place of the “:” character)
  • Tok [:=]
  • Rhs is [5]

The record of all the kinds within the bundle is lengthy, however it’s compliant with what you’d really anticipate. You too can view the AST tree of a code utilizing this website.

On this half, we are going to write a static analyzer that detects if perform arguments are being overwritten. That is thought of a code scent, because it makes it tougher to know what arguments had been handed to a perform throughout debugging. You’ll be able to examine the total code of the analyzer right here.

First, let’s have a look at a typical construction of an analyzer. We create a listing named passes for every move. Every move is an inspection of our code. Each considered one of them lives in its personal bundle, together with its logic and assessments. Then we create a foremost file that executes the passes the module has.

│── README.md
│── cmd
│ └── analyzerName
│ └── foremost.go
│── go.mod
│── go.sum
└── passes
└── passName
│── analyzer.go
│── analyzer_test.go
└── testdata

The go/evaluation bundle defines an API for modular static evaluation instruments. To start with, all passes ought to create an occasion of evaluation.Analyzer. It describes an evaluation perform: its title, documentation, flags, relationship to different analyzers, and naturally, its logic.

We’ll begin by including an analyzer to analyzer.go. We may even add its title, and docs.

Under is the run perform contained in the analyzer.go file. It comprises the precise logic of the analyzer. It receives an argument of sort *evaluation.Cross. *evaluation.Cross offers data and operations for reporting diagnostics to the analyzer’s Run perform concerning the analyzed bundle.

Let’s have a look at strains 51–53 within the code beneath. We iterate over all of the recordsdata and run the ast.Examine perform on every file. This perform traverses the AST tree of a given file in depth-first order. The perform can also be given the customer perform which receives an AST node, and there may be the place the logic occurs.

In strains 3–18, we validate the given node matches our circumstances:

  • Traces 3–15 make sure that the node sort is a perform — common perform or a lambda perform. Additionally, in strains 13–15, we exclude features with out a physique, corresponding to meeting routines.
  • In strains 16–18, features with out parameters are excluded.

We iterate over every argument on strains 20-25 and assemble a set from them. Traces 27–47 are the place the magic occurs.

We traverse the tree ranging from the physique of the perform as the basis node. Then the perform physique is looked for statements the place variables are modified- AssignStmt (a = 5 for exmaple) and IncDecStmt (i++ for instance).

Then we examine the identifier collaborating within the assertion, and if it’s one of many arguments within the set (arguments), then it’s thought of an error, and we name the report perform. That is an auxiliary perform I wrote, and you may see its code beneath. All it does is wrap the decision to move.Report which really notifies the consumer about an error.

Lastly, we are going to add a brand new foremost file and a foremost perform that makes use of the analyzer we simply wrote. To run an analyzer it’s a must to use single/multi checker for single/a number of passes.

Now that our analyzer is prepared, we need to run it on an precise file. We will run it utilizing the next instructions:

go set up path/to/analyzer
go vet -vettool=$(which analyzername) path/to/recordsdata

The primary command installs the analyzer as a binary, and the second run it utilizing go vet. This fashion, you may simply combine it into your IDE or CI/CD atmosphere.

We will additionally check our code utilizing the analysistest bundle. Utilizing analysistest.Run, it’s potential to run an analyzer on a bundle named testdata with check recordsdata and examine that it reported all of the anticipated diagnostics. Expectations are expressed utilizing “// need …” feedback within the enter code, corresponding to the next:

Lastly, we’ve to name analysistest.Run from a check file as beneath.

analysistest.Run(t, analysistest.TestData(), Analyzer)

The primary argument is *testing.T , the second is a path to the check recordsdata, simply provided to us by analysistest.TestData() , and the third is the precise analyzer itself.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments