├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── analyzer ├── GoAnalyzer.go ├── Makefile ├── globalvars │ └── gv.go ├── linter │ ├── ccomplexity │ │ ├── bblock │ │ │ ├── basicblock.go │ │ │ ├── basicblock_test.go │ │ │ └── testcode │ │ │ │ ├── _emptyfunction.go │ │ │ │ ├── _fallthrough.go │ │ │ │ ├── _gcd.go │ │ │ │ ├── _ifelse.go │ │ │ │ ├── _ifelseif.go │ │ │ │ ├── _ifelsereturn.go │ │ │ │ ├── _looper.go │ │ │ │ ├── _nestedifelse.go │ │ │ │ ├── _nestedswitch.go │ │ │ │ ├── _nestedswitches.go │ │ │ │ ├── _returnswitcher.go │ │ │ │ ├── _select.go │ │ │ │ ├── _simple.go │ │ │ │ ├── _simplelooperswitch.go │ │ │ │ ├── _simpleswitch.go │ │ │ │ ├── _switch.go │ │ │ │ └── _typeswitch.go │ │ ├── cfgraph │ │ │ ├── controlflowgraph.go │ │ │ ├── controlflowgraph_test.go │ │ │ └── testcode │ │ │ │ ├── _gcd.go │ │ │ │ ├── _ifelse.go │ │ │ │ ├── _looper.go │ │ │ │ ├── _simple.go │ │ │ │ └── _switcher.go │ │ ├── cyclocomplexity.go │ │ ├── cyclocomplexity_test.go │ │ ├── graph │ │ │ ├── graph.go │ │ │ ├── graph_test.go │ │ │ └── stack │ │ │ │ ├── stacker.go │ │ │ │ └── stacker_test.go │ │ └── testcode │ │ │ ├── _gcd.go │ │ │ ├── _helloworld.go │ │ │ ├── _ifelse.go │ │ │ ├── _swap.go │ │ │ └── _switcher.go │ ├── linter.go │ ├── linter_test.go │ └── testcode │ │ ├── bufferwriting │ │ └── main.go │ │ ├── cyclomaticomplexity │ │ └── main.go │ │ ├── earlyreturn │ │ └── main.go │ │ ├── emptyelsebody │ │ └── main.go │ │ ├── emptyforbody │ │ └── main.go │ │ ├── emptyifbody │ │ └── main.go │ │ ├── errorignored │ │ └── main.go │ │ ├── errorinreturn │ │ └── main.go │ │ ├── fmtprinting │ │ └── main.go │ │ ├── goto │ │ └── main.go │ │ ├── labeledbranch │ │ └── main.go │ │ ├── newmap │ │ └── main.go │ │ ├── staticconditions │ │ └── main.go │ │ ├── stringmethod │ │ └── main.go │ │ └── threadlooping │ │ └── main.go └── make.bat └── sonar-go-plugin ├── pmd-ruleset.xml ├── pom.xml └── src └── main ├── java └── org │ └── sonarsource │ └── plugins │ └── go │ ├── GoAnalyzer │ ├── ExecuteGoAnalyzer.java │ ├── GoFileViolation.java │ ├── JarExtractor.java │ ├── ProcessExec.java │ └── Violation.java │ ├── GoPlugin.java │ ├── languages │ ├── GoLanguage.java │ └── GoQualityProfile.java │ └── rules │ ├── GoIssue.java │ ├── GoIssuesLoaderSensor.java │ └── GoRulesDefinition.java └── resources ├── analyzer └── GoAnalyzerMac └── ruleset └── go-rules.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/go,latex,vim,intellij,emacs 2 | 3 | ### Go ### 4 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 5 | *.o 6 | *.a 7 | *.so 8 | 9 | # Folders 10 | _obj 11 | _test 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | 23 | _testmain.go 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | ### Java ### 30 | *.class 31 | 32 | # Mobile Tools for Java (J2ME) 33 | .mtj.tmp/ 34 | 35 | # Package Files # 36 | *.jar 37 | *.war 38 | *.ear 39 | 40 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 41 | hs_err_pid* 42 | 43 | ### Maven ### 44 | target/ 45 | pom.xml.tag 46 | pom.xml.releaseBackup 47 | pom.xml.versionsBackup 48 | pom.xml.next 49 | release.properties 50 | dependency-reduced-pom.xml 51 | buildNumber.properties 52 | .mvn/timing.properties 53 | 54 | ### LaTeX ### 55 | *.acn 56 | *.acr 57 | *.alg 58 | *.aux 59 | *.bbl 60 | *.bcf 61 | *.blg 62 | *.dvi 63 | *.fdb_latexmk 64 | *.fls 65 | *.glg 66 | *.glo 67 | *.gls 68 | *.idx 69 | *.ilg 70 | *.ind 71 | *.ist 72 | *.lof 73 | *.log 74 | *.lot 75 | *.maf 76 | *.mtc 77 | *.mtc0 78 | *.nav 79 | *.nlo 80 | *.out 81 | *.pdfsync 82 | *.ps 83 | *.run.xml 84 | *.snm 85 | *.synctex.gz 86 | *.toc 87 | *.vrb 88 | *.xdy 89 | *.tdo 90 | 91 | ### Graphviz ### 92 | *.gv 93 | 94 | ### Vim ### 95 | [._]*.s[a-w][a-z] 96 | [._]s[a-w][a-z] 97 | *.un~ 98 | Session.vim 99 | .netrwhist 100 | *~ 101 | 102 | 103 | ### Intellij ### 104 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 105 | 106 | *.iml 107 | 108 | ## Directory-based project format: 109 | .idea/ 110 | # if you remove the above rule, at least ignore the following: 111 | 112 | # User-specific stuff: 113 | # .idea/workspace.xml 114 | # .idea/tasks.xml 115 | # .idea/dictionaries 116 | 117 | # Sensitive or high-churn files: 118 | # .idea/dataSources.ids 119 | # .idea/dataSources.xml 120 | # .idea/sqlDataSources.xml 121 | # .idea/dynamic.xml 122 | # .idea/uiDesigner.xml 123 | 124 | # Gradle: 125 | # .idea/gradle.xml 126 | # .idea/libraries 127 | 128 | # Mongo Explorer plugin: 129 | # .idea/mongoSettings.xml 130 | 131 | ## File-based project format: 132 | *.ipr 133 | *.iws 134 | 135 | ## Plugin-specific files: 136 | 137 | # IntelliJ 138 | /out/ 139 | 140 | # mpeltonen/sbt-idea plugin 141 | .idea_modules/ 142 | 143 | # JIRA plugin 144 | atlassian-ide-plugin.xml 145 | 146 | # Crashlytics plugin (for Android Studio and IntelliJ) 147 | com_crashlytics_export_strings.xml 148 | crashlytics.properties 149 | crashlytics-build.properties 150 | 151 | 152 | ### Emacs ### 153 | # -*- mode: gitignore; -*- 154 | *~ 155 | \#*\# 156 | /.emacs.desktop 157 | /.emacs.desktop.lock 158 | *.elc 159 | auto-save-list 160 | tramp 161 | .\#* 162 | 163 | # Org-mode 164 | .org-id-locations 165 | *_archive 166 | 167 | # flymake-mode 168 | *_flymake.* 169 | 170 | # eshell files 171 | /eshell/history 172 | /eshell/lastdir 173 | 174 | # elpa packages 175 | /elpa/ 176 | 177 | # reftex files 178 | *.rel 179 | 180 | # AUCTeX auto folder 181 | /auto/ 182 | 183 | # cask packages 184 | .cask/ 185 | 186 | ### MacOSX ### 187 | .DS_Store 188 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - 1.6 6 | - 1.7 7 | - tip 8 | 9 | script: 10 | - go test -v ./... 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoAnalysis 2 | 3 | [![Build Status](https://travis-ci.org/chrisbbe/GoAnalysis.svg?branch=master)](https://travis-ci.org/chrisbbe/GoAnalysis) [![GoDoc](https://godoc.org/github.com/chrisbbe/GoAnalysis?status.svg)](https://godoc.org/github.com/chrisbbe/GoAnalysis) 4 | 5 | Analyse your Go source code to detect for typical common mistakes in Go and for high values of cyclomatic complecity in your code. 6 | 7 | ## Install 8 | 9 | Requierements: 10 | Go must be installed and $GOPATH have to be set correctly. 11 | 12 | `$ go get github.com/chrisbbe/GoAnalysis/analyzer` 13 | 14 | 15 | ## Execution 16 | 17 | `$analyzer -dir="$GOPATH/src/github.com/chrisbbe/GoAnalysis"` 18 | 19 | Exchange the example dir with the package you want to analyze. 20 | 21 | 22 | 23 | ## Tests 24 | 25 | GoAnalysis is developed using the Test-driven development (TDD) process where unit tests are extensively used to guarantee for the functionality in each package and the hole analysis as a unit. 26 | 27 | Run the following command in the root folder to execute all tests in all packages. 28 | 29 | `$ go test ./...` 30 | 31 | or just the following command at root level in each package you want to test. 32 | 33 | `$ go test` 34 | 35 | ## Code Style 36 | 37 | ### Indentation 38 | 39 | **Go** 40 | ``` 41 | Tab size: 2 42 | Indent: 2 43 | Continuous indent: 2 44 | ``` 45 | 46 | ## License 47 | Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 48 | 49 | Redistribution and use in source and binary forms, with or without 50 | modification, are permitted provided that the following conditions are 51 | met: 52 | 53 | * Redistributions of source code must retain the above copyright 54 | notice, this list of conditions and the following disclaimer. 55 | * Redistributions in binary form must reproduce the above 56 | copyright notice, this list of conditions and the following disclaimer 57 | in the documentation and/or other materials provided with the 58 | distribution. 59 | * Neither the name of Google Inc. nor the names of its 60 | contributors may be used to endorse or promote products derived from 61 | this software without specific prior written permission. 62 | 63 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 64 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 65 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 66 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 67 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 68 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 69 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 70 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 71 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 72 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 73 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | -------------------------------------------------------------------------------- /analyzer/GoAnalyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "flag" 9 | "github.com/chrisbbe/GoAnalysis/analyzer/globalvars" 10 | "github.com/chrisbbe/GoAnalysis/analyzer/linter" 11 | "log" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | var sourceRootDir = flag.String("dir", "", "Absolute path to root directory of Golang source files to be analysed.") 20 | var jsonOutput = flag.Bool("json", false, "Print result as JSON.") 21 | var printHelp = flag.Bool("help", false, "Print this usage help.") 22 | 23 | func main() { 24 | flag.Parse() 25 | 26 | if flag.NFlag() < 1 { 27 | flag.Usage() 28 | } 29 | 30 | // Option dir selected. 31 | if len(*sourceRootDir) > 0 { 32 | start := time.Now() 33 | 34 | goPackageViolations, err := linter.DetectViolations(*sourceRootDir) // Start the analysis. 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | timeUsed := time.Since(start) 39 | 40 | // Direct output to console as JSON. 41 | if *jsonOutput && len(goPackageViolations) > 0 { 42 | 43 | // Aggregate GoFiles to JSON marshalling. 44 | violationsMap := make(map[string]*linter.GoFile) 45 | for _, goPackage := range goPackageViolations { 46 | for _, goFile := range goPackage.Violations { 47 | if gf, ok := violationsMap[goFile.FilePath]; ok { 48 | gf.Violations = append(gf.Violations, goFile.Violations...) 49 | } else { 50 | violationsMap[goFile.FilePath] = goFile 51 | } 52 | } 53 | } 54 | 55 | // Convert to list for JSON marshalling. 56 | var violations []*linter.GoFile 57 | for _, value := range violationsMap { 58 | violations = append(violations, value) 59 | } 60 | 61 | json, err := json.MarshalIndent(violations, "", "\t") 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | if _, err := os.Stdout.Write(json); err != nil { 66 | panic(err) 67 | } 68 | 69 | } else { 70 | log.SetOutput(os.Stdout) // We want to send output to stdout, instead of Stderr. 71 | numberOfViolations := 0 72 | linesOfCode := 0 73 | linesOfComments := 0 74 | 75 | // Nicely print the output to the console. 76 | log.Println("-----------------------------------------------------------------------------------------------") 77 | for _, goPackage := range goPackageViolations { 78 | log.Printf("PACKAGE: %s (%s)", goPackage.Pack.Name, goPackage.Path) 79 | 80 | for _, goFile := range goPackage.Violations { 81 | log.Printf("\tViolations in %s :", filepath.Base(goFile.FilePath)) 82 | for i, vio := range goFile.Violations { 83 | log.Printf("\t\t%d) %s\n", i, vio) 84 | } 85 | linesOfCode += goFile.LinesOfCode 86 | linesOfComments += goFile.LinesOfComments 87 | } 88 | 89 | numberOfViolations += len(goPackage.Violations) 90 | log.Println("-----------------------------------------------------------------------------------------------") 91 | } 92 | log.Println("## ANALYSIS SUMMARY ##") 93 | log.Printf("Total %d violations found!\n", numberOfViolations) 94 | log.Printf("Total number of Go files: %d\n", countGoFiles(*sourceRootDir)) 95 | log.Printf("Total lines of code (LOC): %d\n", linesOfCode) 96 | log.Printf("Total lines of comments: %d\n", linesOfComments) 97 | log.Printf("Total time used: %s\n", timeUsed) 98 | log.Printf("For rule details: %s\n", globalvars.WIKI_PAGE) 99 | } 100 | 101 | } 102 | 103 | // Print help. 104 | if *printHelp { 105 | flag.Usage() 106 | } 107 | } 108 | 109 | // getGoFiles searches recursively for .go files in the searchDir path, returning the absolute path to the files. 110 | func countGoFiles(searchDir string) (counter int) { 111 | filepath.Walk(searchDir, func(pat string, file os.FileInfo, err error) error { 112 | 113 | if file != nil && !file.IsDir() && file.Mode().IsRegular() && strings.EqualFold(path.Ext(file.Name()), ".go") { 114 | // Only add if regular Go source-code file. 115 | counter++ 116 | } 117 | return nil 118 | }) 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /analyzer/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can 3 | # be found in the LICENSE file. 4 | 5 | PLATFORMS := linux/amd64 darwin/amd64 windows/amd64 6 | 7 | temp = $(subst /, ,$@) 8 | os = $(word 1, $(temp)) 9 | arch = $(word 2, $(temp)) 10 | 11 | release: $(PLATFORMS) 12 | 13 | $(PLATFORMS): 14 | GOOS=$(os) GOARCH=$(arch) go build -o 'GoAnalyzer-$(os)-$(arch)' GoAnalyzer.go 15 | 16 | #.PHONY release $(PLATFORMS) 17 | 18 | clean: 19 | if [ -f GoAnalyzer-linux-amd64 ] ; then rm GoAnalyzer-linux-amd64 ; fi 20 | if [ -f GoAnalyzer-darwin-amd64 ] ; then rm GoAnalyzer-darwin-amd64 ; fi 21 | if [ -f GoAnalyzer-windows-amd64 ] ; then rm GoAnalyzer-windows-amd64 ; fi 22 | -------------------------------------------------------------------------------- /analyzer/globalvars/gv.go: -------------------------------------------------------------------------------- 1 | // Package globalvars (Global Variables) provides shared global constants used in different modules. 2 | package globalvars 3 | 4 | // Miscellaneous. 5 | const ( 6 | PROGRAM_NAME = "GoAnalysis" 7 | VERSION = "0.1" 8 | WEBSITE = "http://chrisbbe.github.io/GoAnalysis/" 9 | WIKI_PAGE = "https://github.com/chrisbbe/GoAnalysis/wiki" 10 | ) 11 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/basicblock.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package bblock 5 | 6 | import ( 7 | "fmt" 8 | "go/ast" 9 | "go/parser" 10 | "go/token" 11 | "log" 12 | "sort" 13 | ) 14 | 15 | type BasicBlockType int 16 | 17 | //Basic Block types. 18 | const ( 19 | FUNCTION_ENTRY BasicBlockType = iota 20 | IF_CONDITION 21 | ELSE_CONDITION 22 | SWITCH_STATEMENT 23 | CASE_CLAUSE 24 | SELECT_STATEMENT 25 | COMM_CLAUSE 26 | RETURN_STMT 27 | FOR_STATEMENT 28 | RANGE_STATEMENT 29 | GO_STATEMENT 30 | CALL_EXPRESSION 31 | ELSE_BODY 32 | FOR_BODY 33 | EMPTY 34 | START 35 | EXIT 36 | UNKNOWN 37 | ) 38 | 39 | var basicBlockTypeStrings = [...]string{ 40 | FUNCTION_ENTRY: "FUNCTION_ENTRY", 41 | IF_CONDITION: "IF_CONDITION", 42 | ELSE_CONDITION: "ELSE_CONDITION", 43 | SWITCH_STATEMENT: "SWITCH_STATEMENT", 44 | CASE_CLAUSE: "CASE_CLAUSE", 45 | SELECT_STATEMENT: "SELECT_STATEMENT", 46 | COMM_CLAUSE: "COMM_CLAUSE", 47 | RETURN_STMT: "RETURN_STMT", 48 | FOR_STATEMENT: "FOR_STATEMENT", 49 | RANGE_STATEMENT: "RANGE_STATEMENT", 50 | GO_STATEMENT: "GO_STATEMENT", 51 | CALL_EXPRESSION: "CALL_EXPRESSION", 52 | ELSE_BODY: "ELSE_BODY", 53 | FOR_BODY: "FOR_BODY", 54 | EMPTY: "EMPTY", 55 | START: "Start", 56 | EXIT: "Exit", 57 | UNKNOWN: "UNKNOWN", 58 | } 59 | 60 | func (bbType BasicBlockType) String() string { 61 | return basicBlockTypeStrings[bbType] 62 | } 63 | 64 | func (basicBlock *BasicBlock) UID() string { 65 | //Both START and EXIT blocks are meta-blocks, giving them negative UID which will not be confused with 'real' blocks. 66 | if basicBlock.Type == START || basicBlock.Type == EXIT { 67 | return fmt.Sprintf("%d", 0-basicBlock.Type) 68 | } 69 | return fmt.Sprintf("%d", basicBlock.EndLine) 70 | } 71 | 72 | func (basicBlock *BasicBlock) String() string { 73 | if basicBlock.Type == START { 74 | return basicBlock.Type.String() 75 | } else if basicBlock.Type == EXIT { 76 | return basicBlock.Type.String() 77 | } 78 | return fmt.Sprintf("BLOCK NR.%d (%s) (EndLine: %d)", basicBlock.Number, basicBlock.Type.String(), basicBlock.EndLine) 79 | } 80 | 81 | func (basicBlock *BasicBlock) AddSuccessorBlock(successorBlocks ...*BasicBlock) { 82 | for _, successorBlock := range successorBlocks { 83 | basicBlock.successor[successorBlock.EndLine] = successorBlock //Update or add. 84 | } 85 | } 86 | 87 | func NewBasicBlock(blockNumber int, blockType BasicBlockType, endLine int) *BasicBlock { 88 | return &BasicBlock{Number: blockNumber, Type: blockType, EndLine: endLine, successor: map[int]*BasicBlock{}} 89 | } 90 | 91 | func (basicBlock *BasicBlock) GetSuccessorBlocks() []*BasicBlock { 92 | keys := make([]int, len(basicBlock.successor)) 93 | basicBlocks := []*BasicBlock{} 94 | 95 | i := 0 96 | for k := range basicBlock.successor { 97 | keys[i] = k 98 | i++ 99 | } 100 | sort.Ints(keys) //Sort keys from map. 101 | 102 | //Add the basic-block into the array. 103 | for _, key := range keys { 104 | basicBlocks = append(basicBlocks, basicBlock.successor[key]) 105 | } 106 | return basicBlocks 107 | } 108 | 109 | type BasicBlock struct { 110 | Number int 111 | Type BasicBlockType 112 | EndLine int 113 | successor map[int]*BasicBlock 114 | FunctionName string 115 | FunctionDeclLine int 116 | } 117 | 118 | type visitor struct { 119 | basicBlocks map[int]*BasicBlock 120 | sourceFileSet *token.FileSet 121 | 122 | lastBlock *BasicBlock 123 | prevLastBlock *BasicBlock 124 | 125 | returnBlock *BasicBlock 126 | forBlock *BasicBlock 127 | forBodyBlock *BasicBlock 128 | switchBlock *BasicBlock 129 | } 130 | 131 | // UpdateBasicBlock updates all the variables from the newBasicBlock into the basicBlock object. 132 | func (basicBlock *BasicBlock) UpdateBasicBlock(newBasicBlock *BasicBlock) { 133 | if newBasicBlock != nil { 134 | basicBlock.Number = newBasicBlock.Number 135 | basicBlock.Type = newBasicBlock.Type 136 | basicBlock.EndLine = newBasicBlock.EndLine 137 | basicBlock.successor = newBasicBlock.successor 138 | basicBlock.FunctionName = newBasicBlock.FunctionName 139 | basicBlock.FunctionDeclLine = newBasicBlock.FunctionDeclLine 140 | } 141 | } 142 | 143 | func (v *visitor) AddBasicBlock(blockType BasicBlockType, position token.Pos) *BasicBlock { 144 | line := v.sourceFileSet.File(position).Line(position) 145 | basicBlock := NewBasicBlock(-1, blockType, line) //-1 indicates number will be set later. 146 | 147 | // Bookkeeping. 148 | v.prevLastBlock = v.lastBlock 149 | v.lastBlock = basicBlock 150 | 151 | //Update the existing block., or add new block. 152 | if bb, ok := v.basicBlocks[line]; ok { 153 | bb.UpdateBasicBlock(basicBlock) 154 | v.lastBlock = bb 155 | return bb 156 | } else { 157 | v.basicBlocks[line] = basicBlock 158 | } 159 | return basicBlock 160 | } 161 | 162 | // GetBasicBlocks converts map holding the basic-blocks to the ordered set 163 | // of basic-blocks, in right order! 164 | func (v *visitor) GetBasicBlocks() []*BasicBlock { 165 | keys := make([]int, len(v.basicBlocks)) 166 | basicBlocks := make([]*BasicBlock, len(v.basicBlocks)) 167 | 168 | i := 0 169 | for k := range v.basicBlocks { 170 | keys[i] = k 171 | i++ 172 | } 173 | sort.Ints(keys) //Sort keys from map. 174 | 175 | //Add the basic-block into the array. 176 | for index, key := range keys { 177 | basicBlocks[index] = v.basicBlocks[key] 178 | basicBlocks[index].Number = index //Set basic-block number. 179 | } 180 | return basicBlocks 181 | } 182 | 183 | func GetBasicBlocksFromSourceCode(filePath string, srcFile []byte) ([]*BasicBlock, error) { 184 | fileSet := token.NewFileSet() 185 | file, err := parser.ParseFile(fileSet, filePath, srcFile, parser.ParseComments|parser.AllErrors) 186 | if err != nil { 187 | return nil, fmt.Errorf("Parse error: %s", err) 188 | } 189 | 190 | visitor := &visitor{sourceFileSet: fileSet, basicBlocks: make(map[int]*BasicBlock)} 191 | ast.Walk(visitor, file) 192 | 193 | basicBlocks := visitor.GetBasicBlocks() 194 | 195 | numberOfBasicBlocks := len(basicBlocks) 196 | for index, bBlock := range basicBlocks { 197 | if bBlock.Type != FOR_BODY && bBlock.Type != ELSE_CONDITION && bBlock.Type != ELSE_BODY && bBlock.Type != COMM_CLAUSE && bBlock.Type != CASE_CLAUSE && bBlock.Type != RETURN_STMT { 198 | if numberOfBasicBlocks > index+1 { 199 | bBlock.AddSuccessorBlock(basicBlocks[index+1]) 200 | } 201 | } 202 | } 203 | 204 | return basicBlocks, nil 205 | } 206 | 207 | func PrintBasicBlocks(basicBlocks []*BasicBlock) { 208 | for _, bb := range basicBlocks { 209 | log.Printf("%d) %s (EndLine: %d)\n", bb.Number, bb.Type.String(), bb.EndLine) 210 | 211 | for _, sBB := range bb.GetSuccessorBlocks() { 212 | log.Printf("\t-> (%d) %s (EndLine: %d)\n", sBB.Number, sBB.Type.String(), sBB.EndLine) 213 | } 214 | } 215 | } 216 | 217 | //TODO: Check after all basic-block types we have declared. 218 | func GetBasicBlockTypeFromStmt(stmtList []ast.Stmt) (BasicBlockType, ast.Stmt) { 219 | for _, stmt := range stmtList { 220 | switch stmt.(type) { 221 | case *ast.ReturnStmt: 222 | return RETURN_STMT, stmt 223 | case *ast.CaseClause: 224 | return CASE_CLAUSE, stmt 225 | case *ast.SwitchStmt: 226 | return SWITCH_STATEMENT, stmt 227 | } 228 | } 229 | return UNKNOWN, nil 230 | } 231 | 232 | func (v *visitor) Visit(node ast.Node) ast.Visitor { 233 | if node != nil { 234 | switch t := node.(type) { 235 | 236 | case *ast.FuncDecl: 237 | funcDeclBlock := v.AddBasicBlock(FUNCTION_ENTRY, t.Pos()) 238 | // Set information about Function in the block. 239 | funcDeclBlock.FunctionName = t.Name.Name 240 | funcDeclBlock.FunctionDeclLine = v.sourceFileSet.File(t.Pos()).Line(t.Pos()) 241 | 242 | if t.Body != nil { 243 | for _, s := range t.Body.List { 244 | if _, ok := s.(*ast.ReturnStmt); ok { 245 | v.returnBlock = v.AddBasicBlock(RETURN_STMT, s.End()) 246 | } 247 | } 248 | 249 | if v.returnBlock == nil { 250 | v.returnBlock = v.AddBasicBlock(RETURN_STMT, t.End()) 251 | } 252 | 253 | //Visit all statements in body. 254 | for _, s := range t.Body.List { 255 | v.Visit(s) 256 | } 257 | } 258 | 259 | v.returnBlock = nil 260 | return nil 261 | 262 | case *ast.ReturnStmt: 263 | prevBlock := v.lastBlock 264 | v.returnBlock = v.AddBasicBlock(RETURN_STMT, t.Pos()) 265 | 266 | if prevBlock != nil && prevBlock.Type != RETURN_STMT && len(prevBlock.successor) == 0 { 267 | prevBlock.AddSuccessorBlock(v.returnBlock) 268 | } 269 | 270 | if v.switchBlock != nil { 271 | v.switchBlock.AddSuccessorBlock(v.returnBlock) 272 | } 273 | 274 | case *ast.GoStmt: 275 | v.AddBasicBlock(GO_STATEMENT, t.Pos()) 276 | 277 | case *ast.IfStmt: 278 | ifBlock := v.AddBasicBlock(IF_CONDITION, t.Pos()) 279 | 280 | for _, stmt := range t.Body.List { 281 | v.Visit(stmt) 282 | } 283 | 284 | var elseConditionBlock, elseBodyBlock *BasicBlock 285 | if t.Else != nil { 286 | 287 | if v.lastBlock != nil && v.lastBlock.Type != RETURN_STMT { 288 | elseConditionBlock = v.AddBasicBlock(ELSE_CONDITION, t.Else.Pos()) 289 | } else { 290 | // We don't want to set return block as successor to elseCondition. 291 | v.returnBlock = nil 292 | } 293 | 294 | elseBodyBlock = v.AddBasicBlock(ELSE_BODY, t.Else.End()) 295 | ifBlock.AddSuccessorBlock(elseBodyBlock) 296 | 297 | if v.returnBlock != nil { 298 | if elseConditionBlock != nil { 299 | elseConditionBlock.AddSuccessorBlock(v.returnBlock) 300 | } 301 | elseBodyBlock.AddSuccessorBlock(v.returnBlock) 302 | } 303 | } 304 | 305 | case *ast.ForStmt: 306 | v.forBlock = v.AddBasicBlock(FOR_STATEMENT, t.Pos()) 307 | 308 | if v.returnBlock != nil { 309 | v.forBlock.AddSuccessorBlock(v.returnBlock) 310 | } 311 | 312 | tmpReturnBlock := v.returnBlock 313 | tmpForBlock := v.forBlock 314 | v.returnBlock = v.forBlock 315 | 316 | for _, s := range t.Body.List { 317 | v.Visit(s) 318 | } 319 | 320 | v.returnBlock = tmpReturnBlock 321 | v.forBlock = tmpForBlock 322 | 323 | if v.lastBlock.Type == FOR_STATEMENT { 324 | v.AddBasicBlock(FOR_BODY, t.End()) 325 | } 326 | 327 | if v.lastBlock.Type != RETURN_STMT { 328 | v.lastBlock.AddSuccessorBlock(v.forBlock) 329 | } 330 | 331 | v.forBlock = nil 332 | return nil 333 | 334 | case *ast.SwitchStmt: 335 | v.switchBlock = v.AddBasicBlock(SWITCH_STATEMENT, t.Pos()) 336 | if v.forBlock != nil { 337 | v.forBlock.AddSuccessorBlock(v.switchBlock) 338 | v.switchBlock.AddSuccessorBlock(v.forBlock) 339 | } 340 | 341 | if v.returnBlock != nil { 342 | v.switchBlock.AddSuccessorBlock(v.returnBlock) 343 | } 344 | 345 | for _, s := range t.Body.List { 346 | v.Visit(s) 347 | } 348 | return nil 349 | 350 | case *ast.TypeSwitchStmt: 351 | v.switchBlock = v.AddBasicBlock(SWITCH_STATEMENT, t.Pos()) 352 | if v.forBlock != nil { 353 | v.forBlock.AddSuccessorBlock(v.switchBlock) 354 | v.switchBlock.AddSuccessorBlock(v.forBlock) 355 | } 356 | 357 | for _, s := range t.Body.List { 358 | v.Visit(s) 359 | } 360 | return nil 361 | 362 | case *ast.SelectStmt: 363 | v.switchBlock = v.AddBasicBlock(SELECT_STATEMENT, t.Pos()) 364 | if v.forBlock != nil { 365 | v.forBlock.AddSuccessorBlock(v.switchBlock) 366 | v.switchBlock.AddSuccessorBlock(v.forBlock) 367 | } 368 | 369 | for _, s := range t.Body.List { 370 | v.Visit(s) 371 | } 372 | return nil 373 | 374 | case *ast.CaseClause: 375 | var caseClause *BasicBlock 376 | if basicBlockType, s := GetBasicBlockTypeFromStmt(t.Body); basicBlockType != UNKNOWN { 377 | caseClause = v.AddBasicBlock(basicBlockType, s.Pos()) 378 | } else { 379 | caseClause = v.AddBasicBlock(CASE_CLAUSE, t.End()) 380 | } 381 | 382 | if v.forBlock != nil { 383 | caseClause.AddSuccessorBlock(v.forBlock) 384 | } 385 | 386 | if v.switchBlock != nil { 387 | v.switchBlock.AddSuccessorBlock(caseClause) 388 | } 389 | 390 | if v.returnBlock != nil { 391 | caseClause.AddSuccessorBlock(v.returnBlock) 392 | } 393 | 394 | tmpSwitchBlock := v.switchBlock 395 | tmpReturnBLock := v.returnBlock 396 | for _, s := range t.Body { 397 | v.Visit(s) 398 | } 399 | v.switchBlock = tmpSwitchBlock 400 | v.returnBlock = tmpReturnBLock 401 | 402 | //TODO: Special case. 403 | //TODO: Type is always CASE_CLAUSE type 404 | if v.returnBlock != nil && caseClause.Type != RETURN_STMT && caseClause.Type != SWITCH_STATEMENT { 405 | //TODO: This must be refactored more beautiful 406 | containsForStatement := false 407 | for _, b := range caseClause.GetSuccessorBlocks() { 408 | if b.Type == FOR_STATEMENT { 409 | containsForStatement = true 410 | } 411 | } 412 | if !containsForStatement { 413 | caseClause.AddSuccessorBlock(v.returnBlock) 414 | } 415 | } 416 | 417 | case *ast.CommClause: 418 | var caseClause *BasicBlock 419 | if basicBlockType, s := GetBasicBlockTypeFromStmt(t.Body); basicBlockType != UNKNOWN { 420 | caseClause = v.AddBasicBlock(basicBlockType, s.Pos()) 421 | } else { 422 | caseClause = v.AddBasicBlock(COMM_CLAUSE, t.End()) 423 | } 424 | 425 | if v.forBlock != nil { 426 | caseClause.AddSuccessorBlock(v.forBlock) 427 | } 428 | 429 | if v.switchBlock != nil { 430 | v.switchBlock.AddSuccessorBlock(caseClause) 431 | } 432 | 433 | if v.returnBlock != nil { 434 | caseClause.AddSuccessorBlock(v.returnBlock) 435 | } 436 | 437 | tmpSwitchBlock := v.switchBlock 438 | tmpReturnBLock := v.returnBlock 439 | for _, s := range t.Body { 440 | v.Visit(s) 441 | } 442 | v.switchBlock = tmpSwitchBlock 443 | v.returnBlock = tmpReturnBLock 444 | 445 | //TODO: Special case. 446 | //TODO: Type is always CASE_CLAUSE type 447 | if v.returnBlock != nil && caseClause.Type != RETURN_STMT && caseClause.Type != SWITCH_STATEMENT { 448 | //TODO: This must be refactored more beautifully 449 | containsForStatement := false 450 | for _, b := range caseClause.GetSuccessorBlocks() { 451 | if b.Type == FOR_STATEMENT { 452 | containsForStatement = true 453 | } 454 | } 455 | if !containsForStatement { 456 | caseClause.AddSuccessorBlock(v.returnBlock) 457 | } 458 | } 459 | } 460 | } 461 | return v 462 | } 463 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/basicblock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package bblock_test 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | "testing" 10 | 11 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/bblock" 12 | ) 13 | 14 | // VerifyBasicBlocks checks the list of expected basic-blocks with the list of actual basic-blocks. 15 | func verifyBasicBlocks(expectedBasicBlocks []*bblock.BasicBlock, correctBasicBlocks []*bblock.BasicBlock) error { 16 | if len(expectedBasicBlocks) != len(correctBasicBlocks) { 17 | return fmt.Errorf("Number of basic-blocks should be %d, but are %d!\n", len(correctBasicBlocks), len(expectedBasicBlocks)) 18 | } 19 | 20 | //Loop through all generated basic-blocks and check if they are similar to the correct once. 21 | for index := range expectedBasicBlocks { 22 | if expectedBasicBlocks[index].Type != correctBasicBlocks[index].Type { 23 | //Check that basic-block type is correct. 24 | return fmt.Errorf("Basic block nr. %d should be of type %s, but are of type %s!\n", 25 | index, correctBasicBlocks[index].Type.String(), expectedBasicBlocks[index].Type.String()) 26 | } 27 | 28 | //Check that length of generate basic-blocks successors are equal correct number of successor blocks. 29 | if len(expectedBasicBlocks[index].GetSuccessorBlocks()) != len(correctBasicBlocks[index].GetSuccessorBlocks()) { 30 | return fmt.Errorf("Number of successors in basic-block nr. %d should be %d, and not %d!\n", 31 | expectedBasicBlocks[index].Number, len(correctBasicBlocks[index].GetSuccessorBlocks()), 32 | len(expectedBasicBlocks[index].GetSuccessorBlocks())) 33 | } 34 | 35 | //Check that basic block starts at right line. 36 | if expectedBasicBlocks[index].EndLine != correctBasicBlocks[index].EndLine { 37 | return fmt.Errorf("Basic block nr. %d should end at line number %d, and not %d!\n", expectedBasicBlocks[index].Number, 38 | correctBasicBlocks[index].EndLine, expectedBasicBlocks[index].EndLine) 39 | } 40 | 41 | //Check that that basic-block has correct successor blocks, and their order. 42 | for i, successorBlock := range expectedBasicBlocks[index].GetSuccessorBlocks() { 43 | if successorBlock.Number != correctBasicBlocks[index].GetSuccessorBlocks()[i].Number { 44 | return fmt.Errorf("Basic block nr. %d's successor block nr. %d should be nr. %d, and not %d!\n", 45 | index, i, correctBasicBlocks[index].GetSuccessorBlocks()[i].Number, successorBlock.Number) 46 | } 47 | } 48 | 49 | } 50 | return nil 51 | } 52 | 53 | func TestEmptyFunctionBasicBlock(t *testing.T) { 54 | filePath := "./testcode/_emptyfunction.go" 55 | sourceFile, err := ioutil.ReadFile(filePath) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 6) 65 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 9) 66 | 67 | BB0.AddSuccessorBlock(BB1) 68 | 69 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1} 70 | 71 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | 76 | func TestSingleBasicBlock(t *testing.T) { 77 | filePath := "./testcode/_simple.go" 78 | sourceFile, err := ioutil.ReadFile(filePath) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 88 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 11) 89 | 90 | BB0.AddSuccessorBlock(BB1) 91 | 92 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1} 93 | 94 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 95 | t.Fatal(err) 96 | } 97 | } 98 | 99 | func TestIfElseBasicBlock(t *testing.T) { 100 | filePath := "./testcode/_ifelse.go" 101 | srcFile, err := ioutil.ReadFile(filePath) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 111 | BB1 := bblock.NewBasicBlock(1, bblock.IF_CONDITION, 13) 112 | BB2 := bblock.NewBasicBlock(2, bblock.ELSE_CONDITION, 16) 113 | BB3 := bblock.NewBasicBlock(3, bblock.ELSE_BODY, 20) 114 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 24) 115 | 116 | BB0.AddSuccessorBlock(BB1) 117 | BB1.AddSuccessorBlock(BB2, BB3) 118 | BB2.AddSuccessorBlock(BB4) 119 | BB3.AddSuccessorBlock(BB4) 120 | 121 | correctBasicBlocks := []*bblock.BasicBlock{ 122 | BB0, BB1, BB2, BB3, BB4, 123 | } 124 | 125 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 126 | t.Fatal(err) 127 | } 128 | } 129 | 130 | func TestIfElseWithReturn(t *testing.T) { 131 | filePath := "./testcode/_ifelsereturn.go" 132 | srcFile, err := ioutil.ReadFile("./testcode/_ifelsereturn.go") 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | 141 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 142 | BB1 := bblock.NewBasicBlock(1, bblock.IF_CONDITION, 10) 143 | BB2 := bblock.NewBasicBlock(2, bblock.RETURN_STMT, 12) 144 | BB3 := bblock.NewBasicBlock(3, bblock.ELSE_BODY, 15) 145 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 16) 146 | 147 | BB0.AddSuccessorBlock(BB1) 148 | BB1.AddSuccessorBlock(BB2, BB3) 149 | BB3.AddSuccessorBlock(BB4) 150 | 151 | correctBasicBlocks := []*bblock.BasicBlock{ 152 | BB0, BB1, BB2, BB3, BB4, 153 | } 154 | 155 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 156 | t.Fatal(err) 157 | } 158 | } 159 | 160 | func TestIfElseIfBasicBlock(t *testing.T) { 161 | filePath := "./testcode/_ifelseif.go" 162 | srcFile, err := ioutil.ReadFile("./testcode/_ifelseif.go") 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | 171 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 7) 172 | BB1 := bblock.NewBasicBlock(1, bblock.IF_CONDITION, 11) 173 | BB2 := bblock.NewBasicBlock(2, bblock.ELSE_CONDITION, 13) 174 | BB3 := bblock.NewBasicBlock(3, bblock.ELSE_BODY, 16) 175 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 23) 176 | 177 | BB0.AddSuccessorBlock(BB1) 178 | BB1.AddSuccessorBlock(BB2, BB3) 179 | BB2.AddSuccessorBlock(BB4) 180 | BB3.AddSuccessorBlock(BB4) 181 | 182 | correctBasicBlocks := []*bblock.BasicBlock{ 183 | BB0, BB1, BB2, BB3, BB4, 184 | } 185 | 186 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 187 | t.Fatal(err) 188 | } 189 | } 190 | 191 | func TestNestedIfElseBasicBlock(t *testing.T) { 192 | filePath := "./testcode/_nestedifelse.go" 193 | srcFile, err := ioutil.ReadFile(filePath) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 198 | if err != nil { 199 | t.Fatal(err) 200 | } 201 | 202 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 11) 203 | BB1 := bblock.NewBasicBlock(1, bblock.IF_CONDITION, 14) 204 | BB2 := bblock.NewBasicBlock(2, bblock.IF_CONDITION, 18) 205 | BB3 := bblock.NewBasicBlock(3, bblock.ELSE_CONDITION, 21) 206 | BB4 := bblock.NewBasicBlock(4, bblock.ELSE_BODY, 25) 207 | BB5 := bblock.NewBasicBlock(5, bblock.ELSE_CONDITION, 26) 208 | BB6 := bblock.NewBasicBlock(6, bblock.ELSE_BODY, 30) 209 | BB7 := bblock.NewBasicBlock(7, bblock.RETURN_STMT, 34) 210 | 211 | BB0.AddSuccessorBlock(BB1) 212 | BB1.AddSuccessorBlock(BB2, BB6) 213 | BB2.AddSuccessorBlock(BB3, BB4) 214 | BB3.AddSuccessorBlock(BB7) 215 | BB4.AddSuccessorBlock(BB7) 216 | BB5.AddSuccessorBlock(BB7) 217 | BB6.AddSuccessorBlock(BB7) 218 | 219 | correctBasicBlocks := []*bblock.BasicBlock{ 220 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, 221 | } 222 | 223 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 224 | t.Fatal(err) 225 | } 226 | } 227 | 228 | func TestLooperBasicBlock(t *testing.T) { 229 | filePath := "./testcode/_looper.go" 230 | srcFile, err := ioutil.ReadFile(filePath) 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 235 | if err != nil { 236 | t.Fatal(err) 237 | } 238 | 239 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 240 | BB1 := bblock.NewBasicBlock(1, bblock.FOR_STATEMENT, 11) 241 | BB2 := bblock.NewBasicBlock(2, bblock.FOR_BODY, 14) 242 | BB3 := bblock.NewBasicBlock(3, bblock.RETURN_STMT, 16) 243 | 244 | BB0.AddSuccessorBlock(BB1) 245 | BB1.AddSuccessorBlock(BB2, BB3) 246 | BB2.AddSuccessorBlock(BB1) 247 | 248 | correctBasicBlocks := []*bblock.BasicBlock{ 249 | BB0, BB1, BB2, BB3, 250 | } 251 | 252 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 253 | t.Fatal(err) 254 | } 255 | } 256 | 257 | func TestSimpleSwitchBasicBlock(t *testing.T) { 258 | filePath := "./testcode/_simpleswitch.go" 259 | srcFile, err := ioutil.ReadFile(filePath) 260 | if err != nil { 261 | t.Fatal(err) 262 | } 263 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 264 | if err != nil { 265 | t.Fatal(err) 266 | } 267 | 268 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 269 | BB1 := bblock.NewBasicBlock(1, bblock.SWITCH_STATEMENT, 12) 270 | BB2 := bblock.NewBasicBlock(2, bblock.CASE_CLAUSE, 15) 271 | BB3 := bblock.NewBasicBlock(3, bblock.CASE_CLAUSE, 17) 272 | BB4 := bblock.NewBasicBlock(4, bblock.CASE_CLAUSE, 19) 273 | BB5 := bblock.NewBasicBlock(5, bblock.CASE_CLAUSE, 21) 274 | BB6 := bblock.NewBasicBlock(6, bblock.RETURN_STMT, 23) 275 | 276 | BB0.AddSuccessorBlock(BB1) 277 | BB1.AddSuccessorBlock(BB2, BB3, BB4, BB5, BB6) 278 | BB2.AddSuccessorBlock(BB6) 279 | BB3.AddSuccessorBlock(BB6) 280 | BB4.AddSuccessorBlock(BB6) 281 | BB5.AddSuccessorBlock(BB6) 282 | 283 | correctBasicBlocks := []*bblock.BasicBlock{ 284 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, 285 | } 286 | 287 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 288 | t.Fatal(err) 289 | } 290 | } 291 | 292 | func TestSwitchBasicBlock(t *testing.T) { 293 | filePath := "./testcode/_switch.go" 294 | srcFile, err := ioutil.ReadFile(filePath) 295 | if err != nil { 296 | t.Fatal(err) 297 | } 298 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 299 | if err != nil { 300 | t.Fatal(err) 301 | } 302 | 303 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 304 | BB1 := bblock.NewBasicBlock(1, bblock.SWITCH_STATEMENT, 12) 305 | BB2 := bblock.NewBasicBlock(2, bblock.CASE_CLAUSE, 15) 306 | BB3 := bblock.NewBasicBlock(3, bblock.CASE_CLAUSE, 18) 307 | BB4 := bblock.NewBasicBlock(4, bblock.CASE_CLAUSE, 20) 308 | BB5 := bblock.NewBasicBlock(5, bblock.CASE_CLAUSE, 22) 309 | BB6 := bblock.NewBasicBlock(6, bblock.RETURN_STMT, 25) 310 | BB7 := bblock.NewBasicBlock(7, bblock.CASE_CLAUSE, 27) 311 | BB8 := bblock.NewBasicBlock(8, bblock.RETURN_STMT, 29) 312 | 313 | BB0.AddSuccessorBlock(BB1) 314 | BB1.AddSuccessorBlock(BB2, BB3, BB4, BB5, BB6, BB7, BB8) 315 | BB2.AddSuccessorBlock(BB8) 316 | BB3.AddSuccessorBlock(BB8) 317 | BB4.AddSuccessorBlock(BB8) 318 | BB5.AddSuccessorBlock(BB8) 319 | BB7.AddSuccessorBlock(BB8) 320 | 321 | correctBasicBlocks := []*bblock.BasicBlock{ 322 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, BB8, 323 | } 324 | 325 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 326 | t.Fatal(err) 327 | } 328 | } 329 | 330 | func TestReturnSwitcherBasicBlock(t *testing.T) { 331 | filePath := "./testcode/_returnswitcher.go" 332 | srcFile, err := ioutil.ReadFile(filePath) 333 | if err != nil { 334 | t.Fatal(err) 335 | } 336 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 337 | if err != nil { 338 | t.Fatal(err) 339 | } 340 | 341 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 342 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 12) 343 | BB2 := bblock.NewBasicBlock(2, bblock.FUNCTION_ENTRY, 14) 344 | BB3 := bblock.NewBasicBlock(3, bblock.SWITCH_STATEMENT, 16) 345 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 18) 346 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 20) 347 | BB6 := bblock.NewBasicBlock(6, bblock.RETURN_STMT, 22) 348 | BB7 := bblock.NewBasicBlock(7, bblock.RETURN_STMT, 24) 349 | BB8 := bblock.NewBasicBlock(8, bblock.RETURN_STMT, 26) 350 | BB9 := bblock.NewBasicBlock(9, bblock.RETURN_STMT, 28) 351 | BB10 := bblock.NewBasicBlock(10, bblock.RETURN_STMT, 30) 352 | BB11 := bblock.NewBasicBlock(11, bblock.RETURN_STMT, 32) 353 | BB12 := bblock.NewBasicBlock(12, bblock.RETURN_STMT, 34) 354 | BB13 := bblock.NewBasicBlock(13, bblock.RETURN_STMT, 36) 355 | BB14 := bblock.NewBasicBlock(14, bblock.RETURN_STMT, 38) 356 | BB15 := bblock.NewBasicBlock(15, bblock.RETURN_STMT, 40) 357 | BB16 := bblock.NewBasicBlock(16, bblock.RETURN_STMT, 42) 358 | 359 | // Function main. 360 | BB0.AddSuccessorBlock(BB1) 361 | 362 | // Function monthNumberToString. 363 | BB2.AddSuccessorBlock(BB3) 364 | BB3.AddSuccessorBlock(BB4, BB5, BB6, BB7, BB8, BB9, BB10, BB11, BB12, BB13, BB14, BB15, BB16) 365 | 366 | correctBasicBlocks := []*bblock.BasicBlock{ 367 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, BB8, BB9, BB10, BB11, BB12, BB13, BB14, BB15, BB16, 368 | } 369 | 370 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 371 | t.Fatal(err) 372 | } 373 | } 374 | 375 | func TestNestedSwitchBasicBlock(t *testing.T) { 376 | filePath := "./testcode/_nestedswitch.go" 377 | srcFile, err := ioutil.ReadFile(filePath) 378 | if err != nil { 379 | t.Fatal(err) 380 | } 381 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 382 | if err != nil { 383 | t.Fatal(err) 384 | } 385 | 386 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 12) 387 | BB1 := bblock.NewBasicBlock(1, bblock.SWITCH_STATEMENT, 17) 388 | BB2 := bblock.NewBasicBlock(2, bblock.CASE_CLAUSE, 20) 389 | BB3 := bblock.NewBasicBlock(3, bblock.SWITCH_STATEMENT, 23) 390 | BB4 := bblock.NewBasicBlock(4, bblock.CASE_CLAUSE, 26) 391 | BB5 := bblock.NewBasicBlock(5, bblock.CASE_CLAUSE, 28) 392 | BB6 := bblock.NewBasicBlock(6, bblock.CASE_CLAUSE, 30) 393 | BB7 := bblock.NewBasicBlock(7, bblock.CASE_CLAUSE, 33) 394 | BB8 := bblock.NewBasicBlock(8, bblock.CASE_CLAUSE, 35) 395 | BB9 := bblock.NewBasicBlock(9, bblock.CASE_CLAUSE, 37) 396 | BB10 := bblock.NewBasicBlock(10, bblock.RETURN_STMT, 39) 397 | 398 | correctBasicBlocks := []*bblock.BasicBlock{ 399 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, BB8, BB9, BB10, 400 | } 401 | 402 | BB0.AddSuccessorBlock(BB1) 403 | BB1.AddSuccessorBlock(BB2, BB3, BB7, BB8, BB9, BB10) 404 | BB2.AddSuccessorBlock(BB10) 405 | BB3.AddSuccessorBlock(BB4, BB5, BB6, BB10) 406 | BB4.AddSuccessorBlock(BB10) 407 | BB5.AddSuccessorBlock(BB10) 408 | BB6.AddSuccessorBlock(BB10) 409 | BB7.AddSuccessorBlock(BB10) 410 | BB8.AddSuccessorBlock(BB10) 411 | BB9.AddSuccessorBlock(BB10) 412 | 413 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 414 | t.Fatal(err) 415 | } 416 | } 417 | 418 | func TestTypeSwitchBasicBlock(t *testing.T) { 419 | filePath := "./testcode/_typeswitch.go" 420 | srcFile, err := ioutil.ReadFile(filePath) 421 | if err != nil { 422 | t.Fatal(err) 423 | } 424 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 425 | if err != nil { 426 | t.Error(err) 427 | } 428 | 429 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 430 | BB1 := bblock.NewBasicBlock(1, bblock.SWITCH_STATEMENT, 14) 431 | BB2 := bblock.NewBasicBlock(2, bblock.CASE_CLAUSE, 17) 432 | BB3 := bblock.NewBasicBlock(3, bblock.CASE_CLAUSE, 19) 433 | BB4 := bblock.NewBasicBlock(4, bblock.CASE_CLAUSE, 21) 434 | BB5 := bblock.NewBasicBlock(5, bblock.CASE_CLAUSE, 23) 435 | BB6 := bblock.NewBasicBlock(6, bblock.CASE_CLAUSE, 25) 436 | BB7 := bblock.NewBasicBlock(7, bblock.RETURN_STMT, 28) 437 | 438 | BB0.AddSuccessorBlock(BB1) 439 | 440 | BB1.AddSuccessorBlock(BB2) 441 | BB1.AddSuccessorBlock(BB3) 442 | BB1.AddSuccessorBlock(BB4) 443 | BB1.AddSuccessorBlock(BB5) 444 | BB1.AddSuccessorBlock(BB6) 445 | 446 | BB2.AddSuccessorBlock(BB7) 447 | BB3.AddSuccessorBlock(BB7) 448 | BB4.AddSuccessorBlock(BB7) 449 | BB5.AddSuccessorBlock(BB7) 450 | BB6.AddSuccessorBlock(BB7) 451 | 452 | correctBasicBlocks := []*bblock.BasicBlock{ 453 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, 454 | } 455 | 456 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 457 | t.Fatal(err) 458 | } 459 | } 460 | 461 | func TestSimpleLooperSwitch(t *testing.T) { 462 | filePath := "./testcode/_simplelooperswitch.go" 463 | srcFile, err := ioutil.ReadFile(filePath) 464 | if err != nil { 465 | t.Fatal(err) 466 | } 467 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 468 | if err != nil { 469 | t.Error(err) 470 | } 471 | 472 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 473 | BB1 := bblock.NewBasicBlock(1, bblock.FOR_STATEMENT, 11) 474 | BB2 := bblock.NewBasicBlock(2, bblock.SWITCH_STATEMENT, 13) 475 | BB3 := bblock.NewBasicBlock(3, bblock.CASE_CLAUSE, 15) 476 | BB4 := bblock.NewBasicBlock(4, bblock.CASE_CLAUSE, 17) 477 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 20) 478 | 479 | BB0.AddSuccessorBlock(BB1) 480 | BB1.AddSuccessorBlock(BB2, BB5) 481 | BB2.AddSuccessorBlock(BB1, BB3, BB4) 482 | BB3.AddSuccessorBlock(BB1) 483 | BB4.AddSuccessorBlock(BB1) 484 | 485 | correctBasicBlocks := []*bblock.BasicBlock{ 486 | BB0, BB1, BB2, BB3, BB4, BB5, 487 | } 488 | 489 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 490 | t.Fatal(err) 491 | } 492 | } 493 | 494 | func TestSelectBasicBlock(t *testing.T) { 495 | filePath := "./testcode/_select.go" 496 | srcFile, err := ioutil.ReadFile(filePath) 497 | if err != nil { 498 | t.Fatal(err) 499 | } 500 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 501 | if err != nil { 502 | t.Fatal(err) 503 | } 504 | 505 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 12) 506 | BB1 := bblock.NewBasicBlock(1, bblock.GO_STATEMENT, 16) 507 | BB2 := bblock.NewBasicBlock(2, bblock.FOR_STATEMENT, 24) 508 | BB3 := bblock.NewBasicBlock(3, bblock.SELECT_STATEMENT, 26) 509 | BB4 := bblock.NewBasicBlock(4, bblock.COMM_CLAUSE, 28) 510 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 31) 511 | BB6 := bblock.NewBasicBlock(6, bblock.RETURN_STMT, 34) 512 | 513 | BB0.AddSuccessorBlock(BB1) 514 | BB1.AddSuccessorBlock(BB2) 515 | BB2.AddSuccessorBlock(BB3, BB6) 516 | BB3.AddSuccessorBlock(BB2, BB4, BB5) 517 | BB4.AddSuccessorBlock(BB2) 518 | 519 | correctBasicBlocks := []*bblock.BasicBlock{ 520 | BB0, BB1, BB2, BB3, BB4, BB5, BB6, 521 | } 522 | 523 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 524 | t.Fatal(err) 525 | } 526 | } 527 | 528 | func TestGreatestCommonDivisor(t *testing.T) { 529 | filePath := "./testcode/_gcd.go" 530 | srcFile, err := ioutil.ReadFile("./testcode/_gcd.go") 531 | if err != nil { 532 | t.Fatal(err) 533 | } 534 | expectedBasicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, srcFile) 535 | if err != nil { 536 | t.Fatal(err) 537 | } 538 | 539 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 540 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 12) 541 | BB2 := bblock.NewBasicBlock(2, bblock.FUNCTION_ENTRY, 14) 542 | BB3 := bblock.NewBasicBlock(3, bblock.FOR_STATEMENT, 16) 543 | BB4 := bblock.NewBasicBlock(4, bblock.FOR_BODY, 19) 544 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 20) 545 | 546 | BB0.AddSuccessorBlock(BB1) 547 | BB2.AddSuccessorBlock(BB3) 548 | BB3.AddSuccessorBlock(BB4) 549 | BB3.AddSuccessorBlock(BB5) 550 | BB4.AddSuccessorBlock(BB3) 551 | 552 | correctBasicBlocks := []*bblock.BasicBlock{ 553 | BB0, BB1, BB2, BB3, BB4, BB5, 554 | } 555 | 556 | if err := verifyBasicBlocks(expectedBasicBlocks, correctBasicBlocks); err != nil { 557 | t.Fatal(err) 558 | } 559 | } 560 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_emptyfunction.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file.. 4 | package main 5 | 6 | func main() { 7 | // BB #0 ending. 8 | 9 | } // BB #1 ending. 10 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_fallthrough.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | 10 | switch 5 { 11 | 12 | case 0: 13 | fmt.Println("Zero") 14 | case 1: 15 | fmt.Println("One") 16 | fallthrough 17 | case 2: 18 | fmt.Println("Two") 19 | fallthrough 20 | case 3: 21 | fmt.Println("Three") 22 | case 4: 23 | fmt.Println("Foure") 24 | case 5: 25 | fmt.Println("Five") 26 | fallthrough 27 | case 6: 28 | fmt.Println("Six") 29 | fallthrough 30 | default: 31 | fmt.Println("Default") 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_gcd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | fmt.Println(gcd(33, 77)) 11 | fmt.Println(gcd(49865, 69811)) 12 | }// BB #1 ending. 13 | 14 | func gcd(x, y int) int { 15 | // BB #2 ending. 16 | for y != 0 { 17 | // BB #3 ending. 18 | x, y = y, x % y // BB #4 ending. 19 | } 20 | return x // BB #5 ending. 21 | } 22 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_ifelse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "log" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | t := 2 + 3 11 | u := 2 - 3 12 | 13 | if 2 < 3 { 14 | // BB #1 ending. 15 | t = 5 + 5 16 | } else { 17 | // BB #2 ending. 18 | u = 10 + 3 19 | t = u - 4 20 | } // BB #3 ending. 21 | 22 | log.Printf("t = %d\n", t) 23 | log.Printf("u = %d\n", u) 24 | } // BB #4 ending. 25 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_ifelseif.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main // BB #0 starting. 5 | import "log" 6 | 7 | func main() { 8 | t := 2 + 3 // BB #1 starting. 9 | u := 2 - 3 10 | 11 | if 2 < 3 { 12 | t = 5 + 5 // BB #2 starting. 13 | } else if 3 > 2 { 14 | t = 10 + 3 // BB #3 starting. 15 | u = t - 4 16 | } 17 | 18 | t = 2 + 3 19 | u = 2 - 3 20 | 21 | log.Printf("t = %d\n", t) 22 | log.Printf("u = %d\n", u) 23 | } 24 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_ifelsereturn.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "log" 7 | 8 | func main() { 9 | 10 | if 11 == 12 { 11 | log.Println("Correct") 12 | return 13 | } else { 14 | log.Println("11 != 12") 15 | } 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_looper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | sum := 0 11 | for i := 0; i < 10; i++ { 12 | // BB #1 ending. 13 | sum += i // BB #2 ending. 14 | } 15 | fmt.Println(sum) // BB #3 ending. 16 | } 17 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_nestedifelse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | func main() { 12 | // BB #0 ending. 13 | 14 | if true { 15 | // BB #1 ending. 16 | 17 | fmt.Printf("True") 18 | if false { 19 | // BB #2 ending. 20 | fmt.Println("False") 21 | } else { 22 | // BB #3 ending. 23 | i := 10 24 | log.Printf("i = %d\n", i) 25 | }// BB #4 ending. 26 | } else { 27 | // BB #5 ending. 28 | y := 5 29 | log.Printf("y = %d\n", y) 30 | }// BB #6 ending. 31 | 32 | x := 2 + 5 33 | log.Printf("x = %d\n", x) // BB #7 ending. 34 | } 35 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_nestedswitch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | const TWO = 2 9 | 10 | // @SuppressRule("FMT_PRINTING") 11 | // @SuppressRule("ERROR_IGNORED") 12 | func main() { 13 | // BB #0 ending 14 | number := 1 15 | secondNumber := 3 16 | 17 | switch number { // BB #1 ending. 18 | 19 | case 0: 20 | fmt.Println("0") // BB #2 ending. 21 | case 1: 22 | fmt.Println("2") 23 | switch secondNumber { // BB #3 ending. 24 | 25 | case 1: 26 | fmt.Println("1") // BB #4 ending. 27 | case 2: 28 | fmt.Println(TWO) // BB #5 ending. 29 | default: 30 | fmt.Printf("No match, secondNumber is %d!\n", number) // BB #6 ending. 31 | } 32 | case 3: 33 | fmt.Println("3") // BB #7 ending. 34 | case 4: 35 | fmt.Println("4") // BB #8 ending. 36 | default: 37 | fmt.Printf("No match, number is %d!\n", number) // BB #9 ending. 38 | } 39 | } // BB #10 ending. 40 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_nestedswitches.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | number := 3 11 | secondNumber := 3 12 | 13 | switch number { // BB #1 ending. 14 | 15 | case 0: // BB #2 ending. 16 | fmt.Println("0") 17 | case 1: // BB #4 ending. 18 | fmt.Println("2") 19 | switch secondNumber { // BB #5 ending. 20 | 21 | case 1: // BB #6 ending. 22 | fmt.Println("1") 23 | case 2: // BB #7 ending. 24 | fmt.Println("2") 25 | default: // BB #8 ending. 26 | fmt.Printf("No match, secondNumber is %d!\n", number) 27 | } 28 | case 3: // BB #9 ending. 29 | fmt.Println("3") 30 | 31 | var x interface{} 32 | x = true 33 | 34 | switch t := x.(type) { // BB #10 ending. 35 | 36 | case int: // BB #11 ending. 37 | fmt.Println("Type is int") 38 | case string: // BB #12 ending. 39 | fmt.Println("Type is string") 40 | case bool: // BB #13 ending. 41 | fmt.Println("Type is bool") 42 | default: // BB #14 ending. 43 | fmt.Printf("%t is unknown type.\n", t) 44 | } 45 | 46 | case 4: // BB #15 ending. 47 | fmt.Println("4") 48 | default: // BB #16 ending. 49 | fmt.Printf("No match, number is %d!\n", number) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_returnswitcher.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | month := 10 11 | fmt.Printf("Month %d is %s\n", month, monthNumberToString(month)) 12 | } // BB #1 ending. 13 | 14 | func monthNumberToString(month int) string { 15 | // BB #2 ending 16 | switch month { // BB #3 ending. 17 | case 1: 18 | return "January" // BB #4 ending. 19 | case 2: 20 | return "Febrary" // BB #5 ending. 21 | case 3: 22 | return "March" // BB #6 ending. 23 | case 4: 24 | return "April" // BB #7 ending. 25 | case 5: 26 | return "May" // BB #8 ending. 27 | case 6: 28 | return "June" // BB #9 ending. 29 | case 7: 30 | return "Juni" // BB #10 ending. 31 | case 8: 32 | return "August" // BB #11 ending. 33 | case 9: 34 | return "September" // BB #12 ending. 35 | case 10: 36 | return "October" // BB #13 ending. 37 | case 11: 38 | return "November" // BB #14 ending. 39 | case 12: 40 | return "Desember" // BB #15 ending. 41 | } 42 | return "Unknown month" 43 | } // BB #16 ending. 44 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_select.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | // BB #0 ending. 14 | stop := make(chan int) 15 | 16 | go func() { 17 | // BB #1 ending. 18 | os.Stdin.Read(make([]byte, 1)) 19 | stop <- 1 20 | }() 21 | 22 | fmt.Println("Started timer, press return to stop watch.") 23 | tick := time.Tick(time.Second) 24 | for time := 0; ; time++ { 25 | // BB #2 ending. 26 | select { // BB #3 ending. 27 | case <-tick: 28 | fmt.Println(time) // BB #5 ending. 29 | case <-stop: 30 | fmt.Printf("Watch stopped after %d seconds.\n", time) 31 | return // BB #5 ending. 32 | } 33 | } 34 | } // BB #6 ending. 35 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_simple.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | fmt.Println("Hello World\n!") 11 | } // BB #1 ending. 12 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_simplelooperswitch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | 11 | for i := 0; ; i++ { 12 | // BB #1 ending. 13 | switch i { // BB #2 ending. 14 | case 0: 15 | fmt.Printf("Zero") // BB #3 ending. 16 | case 1: 17 | fmt.Printf("One") // BB #4 ending. 18 | } 19 | } 20 | } // BB #5 ending. 21 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_simpleswitch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | number := 3 11 | 12 | switch number { // BB #1 ending. 13 | 14 | case 0: // BB #2 ending. 15 | fmt.Println("0") 16 | case 1: // BB #3 ending. 17 | fmt.Println("1") 18 | case 2: // BB #4 ending. 19 | fmt.Println("2") 20 | default: // BB #5 ending. 21 | fmt.Println("Invalid number") // BB #6 ending. 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_switch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | number := 3 11 | 12 | switch number { // BB #1 ending. 13 | 14 | case 0: // BB #2 ending. 15 | fmt.Println("0") 16 | case 1: // BB #3 ending. 17 | fmt.Println("1") 18 | fmt.Println("1.a") 19 | case 2: // BB #4 ending. 20 | fmt.Println("2") 21 | case 3: // BB #5 ending. 22 | fmt.Println("3") 23 | case 4: // BB #6 ending. 24 | fmt.Println("4") 25 | return // BB #7 ending. 26 | default: // BB #8 ending. 27 | fmt.Printf("No match, number is %d!\n", number) 28 | } 29 | } // BB #9 ending. 30 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/bblock/testcode/_typeswitch.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | var x interface{} 11 | x = true 12 | dataType := "" 13 | 14 | switch x.(type) { // BB #1 ending. 15 | 16 | case nil: 17 | dataType = "nil" // BB #2 ending. 18 | case int: 19 | dataType = "int" // BB #3 ending. 20 | case bool: 21 | dataType = "bool" // BB #4 ending. 22 | case string: 23 | dataType = "string" // BB #5 ending. 24 | default: 25 | dataType = "unknown" // BB #6 ending. 26 | } 27 | fmt.Printf("Type is: %s\n", dataType) 28 | } // BB #8 ending. 29 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/controlflowgraph.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package cfgraph 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/chrisbbe/GoAnalysis/analyzer/globalvars" 10 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/bblock" 11 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph" 12 | "io" 13 | "os" 14 | "os/exec" 15 | "time" 16 | ) 17 | 18 | type ControlFlowGraph struct { 19 | *graph.Graph 20 | } 21 | 22 | func New() *ControlFlowGraph { 23 | return &ControlFlowGraph{graph.NewGraph()} 24 | } 25 | 26 | func (controlFlowGraph ControlFlowGraph) Draw(name string) error { 27 | dottyFile, err := os.Create(name + ".dot") 28 | if err != nil { 29 | return err 30 | } 31 | defer dottyFile.Close() 32 | 33 | var content bytes.Buffer 34 | 35 | // Write header information. 36 | content.WriteString("/* --------------------------------------------------- */\n") 37 | content.WriteString(fmt.Sprintf("/* Generated by %s\n", globalvars.PROGRAM_NAME)) 38 | content.WriteString(fmt.Sprintf("/* Version: %s\n", globalvars.VERSION)) 39 | content.WriteString(fmt.Sprintf("/* Website: %s\n", globalvars.WEBSITE)) 40 | content.WriteString(fmt.Sprintf("/* Date: %s\n", time.Now().String())) 41 | content.WriteString("/* --------------------------------------------------- */\n") 42 | 43 | // Start writing the graph. 44 | content.WriteString("digraph ControlFlowGraph {\n") 45 | content.WriteString("\trankdir=TB;\n") 46 | content.WriteString("\tnode [shape = doublecircle]; Start Exit;") 47 | content.WriteString("\tnode [shape = ellipse];") 48 | for _, node := range controlFlowGraph.Nodes { 49 | for _, outNode := range node.GetOutNodes() { 50 | content.WriteString(fmt.Sprintf("\t\"%s\" -> \"%s\";\n", node, outNode)) 51 | } 52 | } 53 | content.WriteString("}\n") 54 | 55 | if _, err := io.WriteString(dottyFile, content.String()); err != nil { 56 | return err 57 | } 58 | cmd := exec.Command("dot", "-Tpdf", dottyFile.Name(), "-o", name+".pdf") 59 | return cmd.Run() 60 | } 61 | 62 | // GetControlFlowGraph generates the control flow graph for each function or 63 | // method found in the sequence of basic-blocks. Returning an array of control 64 | // flow graphs where each entry represents an function or method. 65 | func GetControlFlowGraph(basicBlocks []*bblock.BasicBlock) (cfg []*ControlFlowGraph) { 66 | prevFunctionBlockIndex := -1 // -1 indicates that no FUNCTION_ENTRY has been found. 67 | 68 | //Slicing up basic-blocks containing function or method. 69 | for index, basicBlock := range basicBlocks { 70 | if basicBlock.Type == bblock.FUNCTION_ENTRY && prevFunctionBlockIndex == -1 { 71 | prevFunctionBlockIndex = index //FUNCTION_ENTRY detected, set index. 72 | } else if basicBlock.Type == bblock.FUNCTION_ENTRY { 73 | cfg = append(cfg, getControlFlowGraph(basicBlocks[prevFunctionBlockIndex:index])) 74 | prevFunctionBlockIndex = index // Track which block was the last FUNCTION_ENTRY block. 75 | } 76 | } 77 | 78 | if prevFunctionBlockIndex >= 0 { 79 | cfg = append(cfg, getControlFlowGraph(basicBlocks[prevFunctionBlockIndex:])) 80 | } 81 | return 82 | } 83 | 84 | // getControlFlowGraph generates and returns the control-flow graph based on the array of basic blocks. 85 | func getControlFlowGraph(basicBlocks []*bblock.BasicBlock) *ControlFlowGraph { 86 | controlFlowGraph := New() 87 | var lastBlockAdded *bblock.BasicBlock 88 | 89 | startNode := &graph.Node{Value: bblock.NewBasicBlock(-1, bblock.START, 0)} 90 | exitNode := &graph.Node{Value: bblock.NewBasicBlock(-1, bblock.EXIT, 0)} 91 | 92 | for _, basicBlock := range basicBlocks { 93 | lastBlockAdded = basicBlock 94 | basicBlockNode := &graph.Node{Value: basicBlock} 95 | controlFlowGraph.InsertNode(basicBlockNode) 96 | 97 | if basicBlock.Type == bblock.RETURN_STMT { 98 | // RETURN_STMT terminates functior and methods, should therefor always have a edge to EXIT. 99 | controlFlowGraph.InsertEdge(&graph.Node{Value: basicBlock}, exitNode) 100 | } 101 | 102 | for _, successorBlock := range basicBlock.GetSuccessorBlocks() { 103 | controlFlowGraph.InsertEdge(basicBlockNode, &graph.Node{Value: successorBlock}) 104 | lastBlockAdded = successorBlock 105 | } 106 | } 107 | 108 | controlFlowGraph.InsertEdge(startNode, controlFlowGraph.Root) 109 | if lastBlockAdded.Type != bblock.RETURN_STMT { 110 | // Edge between RETURN_STMT and EXIT is already added higher up! 111 | controlFlowGraph.InsertEdge(&graph.Node{Value: lastBlockAdded}, exitNode) 112 | } 113 | controlFlowGraph.InsertEdge(exitNode, startNode) 114 | 115 | return controlFlowGraph 116 | } 117 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/controlflowgraph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package cfgraph_test 5 | 6 | import ( 7 | "fmt" 8 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/bblock" 9 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/cfgraph" 10 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph" 11 | "io/ioutil" 12 | "testing" 13 | ) 14 | 15 | // verifyControlFlowGraphs is an helper method for tests to check if the generated expectedCfGraph 16 | // is equal the actual graph. 17 | func VerifyControlFlowGraphs(expectedCfGraph *cfgraph.ControlFlowGraph, correctCfGraph *graph.Graph) error { 18 | if expectedCfGraph.GetNumberOfNodes() != correctCfGraph.GetNumberOfNodes() { 19 | return fmt.Errorf("Number of nodes in graph should be %d, but are %d!", correctCfGraph.GetNumberOfNodes(), 20 | expectedCfGraph.GetNumberOfNodes()) 21 | } 22 | if expectedCfGraph.GetNumberOfEdges() != correctCfGraph.GetNumberOfEdges() { 23 | return fmt.Errorf("Number of edges in graph should be %d, but are %d!", correctCfGraph.GetNumberOfEdges(), 24 | expectedCfGraph.GetNumberOfEdges()) 25 | } 26 | 27 | for key, correctNode := range correctCfGraph.Nodes { 28 | if expectedNode, ok := expectedCfGraph.Nodes[key]; ok { 29 | //Node exist in graph, now check its edges. 30 | for index, correctEdge := range correctNode.GetOutNodes() { 31 | if correctNode.GetOutDegree() != expectedNode.GetOutDegree() { 32 | return fmt.Errorf("Node %s should have %d out-edges!\n", correctNode, correctNode.GetOutDegree()) 33 | } 34 | if correctEdge.UID() != expectedNode.GetOutNodes()[index].UID() { 35 | return fmt.Errorf("Edge ( %s -> %s ) should not be present!", correctNode, correctEdge.Value) 36 | } 37 | } 38 | 39 | } else { 40 | return fmt.Errorf("Node %s is not found in the graph!", correctNode) 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | // VerifyBasicBlocks checks the list of expected basic-blocks with the list of actual basic-blocks. 47 | func VerifyBasicBlocks(expectedBasicBlocks []*bblock.BasicBlock, correctBasicBlocks []*bblock.BasicBlock) error { 48 | if len(expectedBasicBlocks) != len(correctBasicBlocks) { 49 | return fmt.Errorf("Number of basic-blocks should be %d, but are %d!\n", len(correctBasicBlocks), len(expectedBasicBlocks)) 50 | } 51 | 52 | //Loop through all generated basic-blocks and check if they are similar to the correct once. 53 | for index := range expectedBasicBlocks { 54 | if expectedBasicBlocks[index].Type != correctBasicBlocks[index].Type { 55 | //Check that basic-block type is correct. 56 | return fmt.Errorf("Basic block nr. %d should be of type %s, but are of type %s!\n", 57 | index, correctBasicBlocks[index].Type.String(), expectedBasicBlocks[index].Type.String()) 58 | } 59 | 60 | //Check that length of generate basic-blocks successors are equal correct number of successor blocks. 61 | if len(expectedBasicBlocks[index].GetSuccessorBlocks()) != len(correctBasicBlocks[index].GetSuccessorBlocks()) { 62 | return fmt.Errorf("Number of successors in basic-block nr. %d should be %d, and not %d!\n", 63 | expectedBasicBlocks[index].Number, len(correctBasicBlocks[index].GetSuccessorBlocks()), 64 | len(expectedBasicBlocks[index].GetSuccessorBlocks())) 65 | } 66 | 67 | //Check that basic block starts at right line. 68 | if expectedBasicBlocks[index].EndLine != correctBasicBlocks[index].EndLine { 69 | return fmt.Errorf("Basic block nr. %d should end at line number %d, and not %d!\n", expectedBasicBlocks[index].Number, 70 | correctBasicBlocks[index].EndLine, expectedBasicBlocks[index].EndLine) 71 | } 72 | 73 | //Check that that basic-block has correct successor blocks, and their order. 74 | for i, successorBlock := range expectedBasicBlocks[index].GetSuccessorBlocks() { 75 | if successorBlock.Number != correctBasicBlocks[index].GetSuccessorBlocks()[i].Number { 76 | return fmt.Errorf("Basic block nr. %d's successor block nr. %d should be nr. %d, and not %d!\n", 77 | index, i, correctBasicBlocks[index].GetSuccessorBlocks()[i].Number, successorBlock.Number) 78 | } 79 | } 80 | 81 | } 82 | return nil 83 | } 84 | 85 | func TestSimpleControlFlowGraph(t *testing.T) { 86 | filePath := "./testcode/_simple.go" 87 | sourceFile, err := ioutil.ReadFile(filePath) 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | basicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | expectedGraph := cfgraph.GetControlFlowGraph(basicBlocks) 96 | correctGraph := graph.NewGraph() 97 | 98 | START := bblock.NewBasicBlock(-1, bblock.START, 0) 99 | EXIT := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 100 | 101 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 102 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 11) 103 | 104 | BB0.AddSuccessorBlock(BB1) 105 | 106 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1} 107 | 108 | //Test basic-blocks. 109 | if err := VerifyBasicBlocks(basicBlocks, correctBasicBlocks); err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | correctGraph.InsertEdge(&graph.Node{Value: START}, &graph.Node{Value: BB0}) 114 | correctGraph.InsertEdge(&graph.Node{Value: BB0}, &graph.Node{Value: BB1}) 115 | correctGraph.InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: EXIT}) 116 | correctGraph.InsertEdge(&graph.Node{Value: EXIT}, &graph.Node{Value: START}) 117 | 118 | if err := VerifyControlFlowGraphs(expectedGraph[0], correctGraph); err != nil { 119 | t.Fatal(err) 120 | } 121 | } 122 | 123 | func TestIfElseControlFlowGraph(t *testing.T) { 124 | filePath := "./testcode/_ifelse.go" 125 | sourceFile, err := ioutil.ReadFile(filePath) 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | basicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | expectedGraph := cfgraph.GetControlFlowGraph(basicBlocks) 134 | correctGraph := graph.NewGraph() 135 | 136 | START := bblock.NewBasicBlock(-1, bblock.START, 0) 137 | EXIT := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 138 | 139 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 140 | BB1 := bblock.NewBasicBlock(1, bblock.IF_CONDITION, 13) 141 | BB2 := bblock.NewBasicBlock(2, bblock.ELSE_CONDITION, 16) 142 | BB3 := bblock.NewBasicBlock(3, bblock.ELSE_BODY, 20) 143 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 23) 144 | 145 | BB0.AddSuccessorBlock(BB1) 146 | BB1.AddSuccessorBlock(BB2, BB3) 147 | BB2.AddSuccessorBlock(BB4) 148 | BB3.AddSuccessorBlock(BB4) 149 | 150 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1, BB2, BB3, BB4} 151 | 152 | //Test basic-blocks. 153 | if err := VerifyBasicBlocks(basicBlocks, correctBasicBlocks); err != nil { 154 | t.Fatal(err) 155 | } 156 | 157 | correctGraph.InsertEdge(&graph.Node{Value: START}, &graph.Node{Value: BB0}) 158 | correctGraph.InsertEdge(&graph.Node{Value: BB0}, &graph.Node{Value: BB1}) 159 | correctGraph.InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB2}) 160 | correctGraph.InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB3}) 161 | correctGraph.InsertEdge(&graph.Node{Value: BB2}, &graph.Node{Value: BB4}) 162 | correctGraph.InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB4}) 163 | correctGraph.InsertEdge(&graph.Node{Value: BB4}, &graph.Node{Value: EXIT}) 164 | correctGraph.InsertEdge(&graph.Node{Value: EXIT}, &graph.Node{Value: START}) 165 | 166 | if err := VerifyControlFlowGraphs(expectedGraph[0], correctGraph); err != nil { 167 | t.Fatal(err) 168 | } 169 | } 170 | 171 | func TestForLoopControlFlowGraph(t *testing.T) { 172 | filePath := "./testcode/_looper.go" 173 | sourceFile, err := ioutil.ReadFile(filePath) 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | basicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 178 | if err != nil { 179 | t.Fatal(err) 180 | } 181 | expectedGraph := cfgraph.GetControlFlowGraph(basicBlocks) 182 | correctGraph := graph.NewGraph() 183 | 184 | START := bblock.NewBasicBlock(-1, bblock.START, 0) 185 | EXIT := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 186 | 187 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 188 | BB1 := bblock.NewBasicBlock(1, bblock.FOR_STATEMENT, 11) 189 | BB2 := bblock.NewBasicBlock(2, bblock.FOR_BODY, 14) 190 | BB3 := bblock.NewBasicBlock(3, bblock.RETURN_STMT, 16) 191 | 192 | BB0.AddSuccessorBlock(BB1) 193 | BB1.AddSuccessorBlock(BB2, BB3) 194 | BB2.AddSuccessorBlock(BB1) 195 | 196 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1, BB2, BB3} 197 | 198 | //Test basic-blocks. 199 | if err := VerifyBasicBlocks(basicBlocks, correctBasicBlocks); err != nil { 200 | t.Fatal(err) 201 | } 202 | 203 | correctGraph.InsertEdge(&graph.Node{Value: START}, &graph.Node{Value: BB0}) 204 | correctGraph.InsertEdge(&graph.Node{Value: BB0}, &graph.Node{Value: BB1}) 205 | correctGraph.InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB2}) 206 | correctGraph.InsertEdge(&graph.Node{Value: BB2}, &graph.Node{Value: BB1}) 207 | correctGraph.InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB3}) 208 | correctGraph.InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: EXIT}) 209 | correctGraph.InsertEdge(&graph.Node{Value: EXIT}, &graph.Node{Value: START}) 210 | 211 | //Test control-flow-graph. 212 | if err := VerifyControlFlowGraphs(expectedGraph[0], correctGraph); err != nil { 213 | t.Fatal(err) 214 | } 215 | } 216 | 217 | func TestSwitchControlFlowGraph(t *testing.T) { 218 | filePath := "./testcode/_switcher.go" 219 | sourceFile, err := ioutil.ReadFile(filePath) 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | basicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 224 | if err != nil { 225 | t.Fatal(err) 226 | } 227 | expectedGraphs := cfgraph.GetControlFlowGraph(basicBlocks) 228 | correctGraph := []*graph.Graph{ 229 | graph.NewGraph(), // func 'main' 230 | graph.NewGraph(), // func 'integerToString' 231 | } 232 | 233 | START := bblock.NewBasicBlock(-1, bblock.START, 0) 234 | EXIT := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 235 | 236 | // Function 'main' 237 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 238 | BB1 := bblock.NewBasicBlock(1, bblock.RETURN_STMT, 11) 239 | 240 | // Function 'integerToString' 241 | BB2 := bblock.NewBasicBlock(2, bblock.FUNCTION_ENTRY, 13) 242 | BB3 := bblock.NewBasicBlock(3, bblock.SWITCH_STATEMENT, 14) 243 | BB4 := bblock.NewBasicBlock(4, bblock.RETURN_STMT, 16) 244 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 18) 245 | BB6 := bblock.NewBasicBlock(6, bblock.RETURN_STMT, 20) 246 | BB7 := bblock.NewBasicBlock(7, bblock.RETURN_STMT, 22) 247 | BB8 := bblock.NewBasicBlock(8, bblock.RETURN_STMT, 24) 248 | 249 | BB0.AddSuccessorBlock(BB1) 250 | BB2.AddSuccessorBlock(BB3) 251 | BB3.AddSuccessorBlock(BB4, BB5, BB6, BB7, BB8) 252 | 253 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1, BB2, BB3, BB4, BB5, BB6, BB7, BB8} 254 | 255 | //Test basic-blocks. 256 | if err := VerifyBasicBlocks(basicBlocks, correctBasicBlocks); err != nil { 257 | t.Fatal(err) 258 | } 259 | 260 | // Control flow graph for function 'main'. 261 | correctGraph[0].InsertEdge(&graph.Node{Value: START}, &graph.Node{Value: BB0}) 262 | correctGraph[0].InsertEdge(&graph.Node{Value: BB0}, &graph.Node{Value: BB1}) 263 | correctGraph[0].InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: EXIT}) 264 | correctGraph[0].InsertEdge(&graph.Node{Value: EXIT}, &graph.Node{Value: START}) 265 | 266 | // Control flow graph for function 'integerToString'. 267 | correctGraph[1].InsertEdge(&graph.Node{Value: START}, &graph.Node{Value: BB2}) 268 | correctGraph[1].InsertEdge(&graph.Node{Value: BB2}, &graph.Node{Value: BB3}) 269 | correctGraph[1].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB4}) 270 | correctGraph[1].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB5}) 271 | correctGraph[1].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB6}) 272 | correctGraph[1].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB7}) 273 | correctGraph[1].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: BB8}) 274 | correctGraph[1].InsertEdge(&graph.Node{Value: BB4}, &graph.Node{Value: EXIT}) 275 | correctGraph[1].InsertEdge(&graph.Node{Value: BB5}, &graph.Node{Value: EXIT}) 276 | correctGraph[1].InsertEdge(&graph.Node{Value: BB6}, &graph.Node{Value: EXIT}) 277 | correctGraph[1].InsertEdge(&graph.Node{Value: BB7}, &graph.Node{Value: EXIT}) 278 | correctGraph[1].InsertEdge(&graph.Node{Value: BB8}, &graph.Node{Value: EXIT}) 279 | correctGraph[1].InsertEdge(&graph.Node{Value: EXIT}, &graph.Node{Value: START}) 280 | 281 | if err := VerifyControlFlowGraphs(expectedGraphs[0], correctGraph[0]); err != nil { 282 | t.Fatal(err) 283 | } 284 | if err := VerifyControlFlowGraphs(expectedGraphs[1], correctGraph[1]); err != nil { 285 | t.Fatal(err) 286 | } 287 | } 288 | 289 | func TestGreatestCommonDivisorControlFlowGraph(t *testing.T) { 290 | filePath := "./testcode/_gcd.go" 291 | sourceFile, err := ioutil.ReadFile(filePath) 292 | if err != nil { 293 | t.Fatal(err) 294 | } 295 | basicBlocks, err := bblock.GetBasicBlocksFromSourceCode(filePath, sourceFile) 296 | if err != nil { 297 | t.Fatal(err) 298 | } 299 | 300 | expectedGraphs := cfgraph.GetControlFlowGraph(basicBlocks) 301 | correctGraph := []*graph.Graph{ 302 | graph.NewGraph(), // func 'gcd' 303 | graph.NewGraph(), // func 'main' 304 | } 305 | 306 | // Function 'gcd' 307 | START0 := bblock.NewBasicBlock(-1, bblock.START, 0) 308 | EXIT0 := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 309 | BB0 := bblock.NewBasicBlock(0, bblock.FUNCTION_ENTRY, 8) 310 | BB1 := bblock.NewBasicBlock(1, bblock.FOR_STATEMENT, 10) 311 | BB2 := bblock.NewBasicBlock(2, bblock.FOR_BODY, 13) 312 | BB3 := bblock.NewBasicBlock(3, bblock.RETURN_STMT, 14) 313 | 314 | // Function 'main' 315 | START1 := bblock.NewBasicBlock(-1, bblock.START, 0) 316 | EXIT1 := bblock.NewBasicBlock(-1, bblock.EXIT, 0) 317 | BB4 := bblock.NewBasicBlock(4, bblock.FUNCTION_ENTRY, 17) 318 | BB5 := bblock.NewBasicBlock(5, bblock.RETURN_STMT, 21) 319 | 320 | BB0.AddSuccessorBlock(BB1) 321 | BB1.AddSuccessorBlock(BB2, BB3) 322 | BB2.AddSuccessorBlock(BB1) 323 | BB4.AddSuccessorBlock(BB5) 324 | 325 | correctBasicBlocks := []*bblock.BasicBlock{BB0, BB1, BB2, BB3, BB4, BB5} 326 | 327 | // Function 'gcd' 328 | correctGraph[0].InsertEdge(&graph.Node{Value: START0}, &graph.Node{Value: BB0}) 329 | correctGraph[0].InsertEdge(&graph.Node{Value: BB0}, &graph.Node{Value: BB1}) 330 | correctGraph[0].InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB2}) 331 | correctGraph[0].InsertEdge(&graph.Node{Value: BB2}, &graph.Node{Value: BB1}) 332 | correctGraph[0].InsertEdge(&graph.Node{Value: BB1}, &graph.Node{Value: BB3}) 333 | correctGraph[0].InsertEdge(&graph.Node{Value: BB3}, &graph.Node{Value: EXIT0}) 334 | correctGraph[0].InsertEdge(&graph.Node{Value: EXIT0}, &graph.Node{Value: START0}) 335 | 336 | // Function 'main' 337 | correctGraph[1].InsertEdge(&graph.Node{Value: START1}, &graph.Node{Value: BB4}) 338 | correctGraph[1].InsertEdge(&graph.Node{Value: BB4}, &graph.Node{Value: BB5}) 339 | correctGraph[1].InsertEdge(&graph.Node{Value: BB5}, &graph.Node{Value: EXIT1}) 340 | correctGraph[1].InsertEdge(&graph.Node{Value: EXIT1}, &graph.Node{Value: START1}) 341 | 342 | // Test basic-blocks. 343 | if err := VerifyBasicBlocks(basicBlocks, correctBasicBlocks); err != nil { 344 | t.Fatal(err) 345 | } 346 | // Test control-flow graph. 347 | if err := VerifyControlFlowGraphs(expectedGraphs[0], correctGraph[0]); err != nil { 348 | t.Fatal(err) 349 | } 350 | if err := VerifyControlFlowGraphs(expectedGraphs[1], correctGraph[1]); err != nil { 351 | t.Fatal(err) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/testcode/_gcd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func gcd(x, y int) int { 9 | // BB #0 ending. 10 | for y != 0 { 11 | // BB #1 ending. 12 | x, y = y, x % y // BB #2 ending. 13 | } 14 | return x // BB #3 ending. 15 | } 16 | 17 | func main() { 18 | // BB #4 ending. 19 | fmt.Println(gcd(33, 77)) 20 | fmt.Println(gcd(49865, 69811)) // BB #5 ending. 21 | } 22 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/testcode/_ifelse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "log" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | t := 2 + 3 11 | u := 2 - 3 12 | 13 | if 2 < 3 { 14 | // BB #1 ending. 15 | t = 5 + 5 16 | } else { 17 | // BB #2 ending. 18 | u = 10 + 3 19 | t = t - 4 // BB #3 ending. 20 | } 21 | log.Printf("t = %d\n", t) 22 | log.Printf("u = %d\n", u) 23 | } 24 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/testcode/_looper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | sum := 0 11 | for i := 0; i < 10; i++ { 12 | // BB #1 ending. 13 | sum += i // BB #2 ending. 14 | } 15 | fmt.Println(sum) // BB #3 ending. 16 | } 17 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/testcode/_simple.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #0 ending. 10 | fmt.Println("Hello World\n!") 11 | } // BB #1 ending. 12 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cfgraph/testcode/_switcher.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | number := 3 10 | fmt.Printf("Number %d is %s\n", number, integerToString(2)) 11 | } 12 | 13 | func integerToString(number int) string { 14 | switch number { 15 | case 1: 16 | return "1" 17 | case 2: 18 | return "2" 19 | case 3: 20 | return "3" 21 | default: 22 | return "Invalid number" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cyclocomplexity.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package ccomplexity 5 | 6 | import ( 7 | "fmt" 8 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/bblock" 9 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/cfgraph" 10 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph" 11 | ) 12 | 13 | // FunctionComplexity represents cyclomatic complexity in a function or method. 14 | type FunctionComplexity struct { 15 | Name string //Function name. 16 | SrcLine int //Line number in source file where func is declared. 17 | Complexity int //Cyclomatic complexity value. 18 | ControlFlowGraph *cfgraph.ControlFlowGraph //Control-flow graph in function. 19 | BasicBlocks []*bblock.BasicBlock //Basic-blocks in function. 20 | } 21 | 22 | func (funCC *FunctionComplexity) String() string { 23 | return fmt.Sprintf("%s() at line %d has CC: %d\n", funCC.Name, funCC.SrcLine, funCC.Complexity) 24 | } 25 | 26 | func (function *FunctionComplexity) GetNumberOfNodes() int { 27 | return function.ControlFlowGraph.GetNumberOfNodes() 28 | } 29 | 30 | func (function *FunctionComplexity) GetNumberOfEdges() int { 31 | return function.ControlFlowGraph.GetNumberOfEdges() 32 | } 33 | 34 | func (function *FunctionComplexity) GetNumberOfSCC() int { 35 | return function.ControlFlowGraph.GetNumberOfSCComponents() 36 | } 37 | 38 | func (function *FunctionComplexity) GetSCComponents() []*graph.StronglyConnectedComponent { 39 | return function.ControlFlowGraph.GetSCComponents() 40 | } 41 | 42 | func GetCyclomaticComplexity(cfg *cfgraph.ControlFlowGraph) int { 43 | return cfg.GetNumberOfEdges() - cfg.GetNumberOfNodes() + cfg.GetNumberOfSCComponents() 44 | } 45 | 46 | func GetCyclomaticComplexityFunctionLevel(goFilePath string, goSrcFile []byte) (functions []*FunctionComplexity, err error) { 47 | blocks, err := bblock.GetBasicBlocksFromSourceCode(goFilePath, goSrcFile) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | for _, cfg := range cfgraph.GetControlFlowGraph(blocks) { 53 | complexity := GetCyclomaticComplexity(cfg) 54 | funcBlock := cfg.Root.Value.(*bblock.BasicBlock) 55 | functions = append(functions, &FunctionComplexity{ 56 | Name: funcBlock.FunctionName, 57 | SrcLine: funcBlock.FunctionDeclLine, 58 | Complexity: complexity, 59 | ControlFlowGraph: cfg, 60 | BasicBlocks: blocks, 61 | }) 62 | } 63 | return functions, nil 64 | } 65 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/cyclocomplexity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package ccomplexity_test 5 | 6 | import ( 7 | "fmt" 8 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity" 9 | "io/ioutil" 10 | "testing" 11 | ) 12 | 13 | func verifyCyclomaticComplexity(expectedComplexity []*ccomplexity.FunctionComplexity, correctComplexity []ccomplexity.FunctionComplexity) error { 14 | if len(expectedComplexity) != len(correctComplexity) { 15 | return fmt.Errorf("Number of functions should be %d, but are %d!\n", len(expectedComplexity), len(correctComplexity)) 16 | } 17 | 18 | for index, expectedComplexity := range expectedComplexity { 19 | if expectedComplexity.Name != correctComplexity[index].Name { 20 | return fmt.Errorf("Function nr. %d name should be %s, ant not %s.\n", index, correctComplexity[index].Name, 21 | expectedComplexity.Name) 22 | } 23 | 24 | if expectedComplexity.Complexity != correctComplexity[index].Complexity { 25 | return fmt.Errorf("Function nr. %d (%s) should have cyclomatic complexity %d, but has %d!\n", index, 26 | expectedComplexity.Name, correctComplexity[index].Complexity, expectedComplexity.Complexity) 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func TestSimpleComplexityFunctionLevel(t *testing.T) { 33 | filePath := "./testcode/_helloworld.go" 34 | srcFile, err := ioutil.ReadFile(filePath) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | expectedCyclomaticComplexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | correctCyclomaticComplexity := []ccomplexity.FunctionComplexity{ 44 | {Name: "main", Complexity: 1}, 45 | } 46 | 47 | if err := verifyCyclomaticComplexity(expectedCyclomaticComplexity, correctCyclomaticComplexity); err != nil { 48 | t.Error(err) 49 | } 50 | } 51 | 52 | func TestIfElseComplexityFunctionLevel(t *testing.T) { 53 | filePath := "./testcode/_ifelse.go" 54 | srcFile, err := ioutil.ReadFile(filePath) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | expectedCyclomaticComplexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | correctCyclomaticComplexity := []ccomplexity.FunctionComplexity{ 64 | {Name: "main", Complexity: 2}, 65 | } 66 | 67 | if err := verifyCyclomaticComplexity(expectedCyclomaticComplexity, correctCyclomaticComplexity); err != nil { 68 | t.Error(err) 69 | } 70 | } 71 | 72 | func TestMethodComplexity(t *testing.T) { 73 | filePath := "./testcode/_swap.go" 74 | srcFile, err := ioutil.ReadFile(filePath) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | expectedCyclomaticComplexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | 83 | correctCyclomaticComplexity := []ccomplexity.FunctionComplexity{ 84 | {Name: "main", Complexity: 1}, 85 | {Name: "swap", Complexity: 1}, 86 | } 87 | 88 | if err := verifyCyclomaticComplexity(expectedCyclomaticComplexity, correctCyclomaticComplexity); err != nil { 89 | t.Error(err) 90 | } 91 | } 92 | 93 | func TestSwitcherComplexity(t *testing.T) { 94 | filePath := "./testcode/_switcher.go" 95 | srcFile, err := ioutil.ReadFile(filePath) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | expectedCyclomaticComplexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | correctCyclomaticComplexity := []ccomplexity.FunctionComplexity{ 105 | {Name: "main", Complexity: 1}, 106 | {Name: "monthNumberToString", Complexity: 14}, 107 | } 108 | 109 | if err := verifyCyclomaticComplexity(expectedCyclomaticComplexity, correctCyclomaticComplexity); err != nil { 110 | t.Error(err) 111 | } 112 | } 113 | 114 | func TestGreatestCommonDivisorComplexity(t *testing.T) { 115 | filePath := "./testcode/_gcd.go" 116 | srcFile, err := ioutil.ReadFile(filePath) 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | expectedCyclomaticComplexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | correctCyclomaticComplexity := []ccomplexity.FunctionComplexity{ 125 | {Name: "gcd", Complexity: 2}, 126 | {Name: "main", Complexity: 1}, 127 | } 128 | 129 | if err := verifyCyclomaticComplexity(expectedCyclomaticComplexity, correctCyclomaticComplexity); err != nil { 130 | t.Error(err) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/graph/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package graph 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/chrisbbe/GoAnalysis/analyzer/globalvars" 10 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph/stack" 11 | "io" 12 | "os" 13 | "os/exec" 14 | "time" 15 | ) 16 | 17 | // All node-values in a graph implements the Value interface. 18 | type Value interface { 19 | UID() string 20 | String() string 21 | } 22 | 23 | // Graph is the main data-structure representing the graph. 24 | // Holding references to the root node and all nodes in the graph. 25 | type Graph struct { 26 | Root *Node //First node added to the graph. 27 | Nodes map[string]*Node //All nodes in the graph. 28 | scc []*StronglyConnectedComponent //Holds strongly-connected components sets. 29 | preCount int // Used by SCC. 30 | stack stack.Stack // Used by SCC. 31 | } 32 | 33 | // Node represents a single node in the graph, holding the node value. 34 | type Node struct { 35 | Value //Holds node interface value. 36 | visited bool //Used by DFS. 37 | inEdges []*Node //Holds in-going edges. 38 | outEdges []*Node //Holds out-going edges. 39 | low int // Used by SCC. 40 | } 41 | 42 | // StronglyConnectedComponent holds a set of nodes in a strongly connected components. 43 | type StronglyConnectedComponent struct { 44 | Nodes []*Node 45 | } 46 | 47 | // NewGraph initialize and returns a pointer to a new graph object. 48 | func NewGraph() *Graph { 49 | return &Graph{Nodes: map[string]*Node{}} 50 | } 51 | 52 | func (graph *Graph) InsertNode(node *Node) { 53 | if graph.Root == nil { 54 | graph.Root = node 55 | graph.Nodes[graph.Root.Value.UID()] = graph.Root 56 | } else if graph.Nodes[node.Value.UID()] == nil { 57 | graph.Nodes[node.Value.UID()] = node 58 | } 59 | } 60 | 61 | // insertOutNode inserts outgoing directed edge to 'node'. 62 | func (n *Node) insertOutEdge(node *Node) { 63 | n.outEdges = append(n.outEdges, node) 64 | } 65 | 66 | // insertInEdge inserts ingoing directed edge to 'node'. 67 | func (n *Node) insertInEdge(node *Node) { 68 | n.inEdges = append(n.inEdges, node) 69 | } 70 | 71 | // Returning back the interface function from NodeValue. 72 | func (n *Node) String() string { 73 | return n.Value.String() 74 | } 75 | 76 | // InsertEdge inserts directed edge between leftNode and rightNode. It also inserts the node in the graph correctly 77 | // if the node does not already exist in the graph. 78 | func (graph *Graph) InsertEdge(leftNode *Node, rightNode *Node) { 79 | if len(graph.Nodes) == 0 { 80 | graph.Root = leftNode 81 | graph.Root.insertOutEdge(rightNode) 82 | rightNode.insertInEdge(graph.Root) 83 | graph.Nodes[graph.Root.Value.UID()] = graph.Root 84 | graph.Nodes[rightNode.Value.UID()] = rightNode 85 | } else { 86 | //Get left and right node if they already exist. 87 | if graph.Nodes[leftNode.Value.UID()] != nil { 88 | leftNode = graph.Nodes[leftNode.Value.UID()] 89 | } 90 | if graph.Nodes[rightNode.Value.UID()] != nil { 91 | rightNode = graph.Nodes[rightNode.Value.UID()] 92 | } 93 | 94 | if graph.Nodes[leftNode.Value.UID()] == nil { 95 | leftNode.insertOutEdge(rightNode) 96 | rightNode.insertInEdge(leftNode) 97 | graph.Nodes[leftNode.Value.UID()] = leftNode 98 | graph.Nodes[rightNode.Value.UID()] = rightNode 99 | } else { 100 | leftNode.insertOutEdge(rightNode) 101 | rightNode.insertInEdge(leftNode) 102 | graph.Nodes[rightNode.Value.UID()] = rightNode 103 | } 104 | } 105 | } 106 | 107 | // getDFS is an internal helper method for GetDFS() to perform depth-first-search on the graph. 108 | func (node *Node) getDFS() (nodes []*Node) { 109 | if !node.visited { 110 | nodes = append(nodes, node) 111 | } 112 | node.visited = true 113 | for _, n := range node.outEdges { 114 | if !n.visited { 115 | nodes = append(nodes, n.getDFS()...) 116 | } 117 | } 118 | return nodes 119 | } 120 | 121 | // GetDFS performs depth first search in the 'graph' and returns the result in 'nodes'. 122 | func (graph *Graph) GetDFS() (nodes []*Node) { 123 | nodes = graph.Root.getDFS() 124 | //Clean up nodes by setting visited = false 125 | for _, node := range graph.Nodes { 126 | node.visited = false 127 | } 128 | return nodes 129 | } 130 | 131 | // dfs is internal modified depth first search method for GetSCComponents to find strongly connected components. 132 | func (graph *Graph) dfs(v *Node) { 133 | v.low = graph.preCount 134 | graph.preCount++ 135 | v.visited = true 136 | graph.stack.Push(v) 137 | 138 | min := v.low 139 | for _, w := range v.outEdges { 140 | if !w.visited { 141 | graph.dfs(w) 142 | } 143 | if w.low < min { 144 | min = w.low 145 | } 146 | } 147 | 148 | if min < v.low { 149 | v.low = min 150 | return 151 | } 152 | 153 | component := []*Node{} 154 | var w interface{} 155 | var err error 156 | for ok := true; ok; ok = w.(*Node) != v { 157 | w, err = graph.stack.Pop() 158 | if err != nil { 159 | panic(err) 160 | } 161 | component = append(component, w.(*Node)) 162 | w.(*Node).low = len(graph.Nodes) - 1 163 | } 164 | 165 | graph.scc = append(graph.scc, &StronglyConnectedComponent{Nodes: component}) 166 | } 167 | 168 | // GetSCComponents performs Tarjans algorithm to detect 169 | // strongly connected components in 'graph', returns 170 | // a list of lists containing nodes in each strongly 171 | // connected component. 172 | func (graph *Graph) GetSCComponents() []*StronglyConnectedComponent { 173 | graph.scc = []*StronglyConnectedComponent{} //Init list. 174 | for _, node := range graph.Nodes { 175 | if !node.visited { 176 | graph.dfs(node) 177 | } 178 | } 179 | //Clean up nodes by setting visited = false 180 | for _, node := range graph.Nodes { 181 | node.visited = false 182 | node.low = 0 183 | } 184 | 185 | //Clean up graph. 186 | graph.stack = stack.Stack{} 187 | graph.preCount = 0 188 | return graph.scc 189 | } 190 | 191 | // GetNumberOfNodes returns number of 192 | // nodes in 'graph'. 193 | func (graph *Graph) GetNumberOfNodes() int { 194 | return len(graph.Nodes) 195 | } 196 | 197 | // GetNumberOfEdges returns number of 198 | // edges in 'graph'. 199 | func (graph *Graph) GetNumberOfEdges() (numberOfEdges int) { 200 | for _, node := range graph.Nodes { 201 | numberOfEdges += node.GetOutDegree() 202 | } 203 | return numberOfEdges 204 | } 205 | 206 | // GetNumberOfSCComponents return number of 207 | // strongly connected components in 'graph'. 208 | func (graph *Graph) GetNumberOfSCComponents() int { 209 | //We don't want to run Tarjan's once again if algorithm is already executed. 210 | if graph.scc == nil { 211 | return len(graph.GetSCComponents()) 212 | } 213 | return len(graph.scc) 214 | } 215 | 216 | // GetInDegree returns number of ingoing edges to 'node'. 217 | func (node *Node) GetInDegree() int { 218 | return len(node.inEdges) 219 | } 220 | 221 | // GetOutDegree returns number of outgoing edges from 'node'. 222 | func (node *Node) GetOutDegree() int { 223 | return len(node.outEdges) 224 | } 225 | 226 | func (node *Node) GetOutNodes() []*Node { 227 | return node.outEdges 228 | } 229 | 230 | func (node *Node) GetInNodes() []*Node { 231 | return node.inEdges 232 | } 233 | 234 | // Draw writes the graph to file according to the Graphviz (www.graphviz.org) format 235 | // and tries to compile the graph to PDF by using dot. 236 | func (graph *Graph) Draw(name string) (err error) { 237 | dottyFile, err := os.Create(name + ".dot") 238 | if err != nil { 239 | return err 240 | } 241 | defer func() { 242 | err = dottyFile.Close() 243 | }() 244 | 245 | var content bytes.Buffer 246 | 247 | // Write header information. 248 | content.WriteString("/* --------------------------------------------------- */\n") 249 | content.WriteString(fmt.Sprintf("/* Generated by %s\n", globalvars.PROGRAM_NAME)) 250 | content.WriteString(fmt.Sprintf("/* Version: %s\n", globalvars.VERSION)) 251 | content.WriteString(fmt.Sprintf("/* Website: %s\n", globalvars.WEBSITE)) 252 | content.WriteString(fmt.Sprintf("/* Date: %s\n", time.Now().String())) 253 | content.WriteString("/* --------------------------------------------------- */\n") 254 | 255 | // Start writing the graph. 256 | if _, err := content.WriteString("digraph AST {\n"); err != nil { 257 | return err 258 | } 259 | 260 | for _, node := range graph.Nodes { 261 | for _, outNode := range node.GetOutNodes() { 262 | if _, err = content.WriteString(fmt.Sprintf("\t\"%s\" -> \"%s\";\n", node, outNode)); err != nil { 263 | return err 264 | } 265 | } 266 | } 267 | if _, err := content.WriteString("}\n"); err != nil { 268 | return err 269 | } 270 | 271 | if _, err := io.WriteString(dottyFile, content.String()); err != nil { 272 | return err 273 | } 274 | cmd := exec.Command("dot", "-Tpdf", dottyFile.Name(), "-o", name+".pdf") 275 | return cmd.Run() 276 | } 277 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/graph/graph_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package graph_test 5 | 6 | import ( 7 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph" 8 | "testing" 9 | ) 10 | 11 | // Letter is the type to store in the graph. 12 | type Letter struct { 13 | letter string 14 | } 15 | 16 | // Satisfies the Value interface. 17 | func (l Letter) UID() string { 18 | return l.letter 19 | } 20 | 21 | // Letter must satisfies the Value interface in graph. 22 | func (l Letter) String() string { 23 | return l.letter 24 | } 25 | 26 | func sccExists(correctSCCList, actualSCCList []*graph.StronglyConnectedComponent) bool { 27 | existCounter := 0 28 | 29 | for _, correctScc := range correctSCCList { 30 | for _, actualScc := range actualSCCList { 31 | if existInList(correctScc.Nodes, actualScc.Nodes) { 32 | existCounter++ 33 | } 34 | } 35 | } 36 | 37 | return existCounter == len(correctSCCList) 38 | } 39 | 40 | // Compare two lists. 41 | func existInList(elementList, list []*graph.Node) bool { 42 | existCounter := 0 43 | 44 | for _, e := range elementList { 45 | for _, r := range list { 46 | if e.Value == r.Value { 47 | existCounter++ 48 | } 49 | } 50 | } 51 | return existCounter == len(elementList) 52 | } 53 | 54 | func TestDirectedGraph(test *testing.T) { 55 | //Create some nodes. 56 | a := graph.Node{Value: Letter{"A"}} 57 | b := graph.Node{Value: Letter{"B"}} 58 | c := graph.Node{Value: Letter{"C"}} 59 | d := graph.Node{Value: Letter{"D"}} 60 | e := graph.Node{Value: Letter{"E"}} 61 | f := graph.Node{Value: Letter{"F"}} 62 | g := graph.Node{Value: Letter{"G"}} 63 | h := graph.Node{Value: Letter{"H"}} 64 | 65 | graph := graph.NewGraph() 66 | 67 | //Add directed node-pairs to graph. 68 | graph.InsertEdge(&a, &b) 69 | graph.InsertEdge(&a, &d) 70 | graph.InsertEdge(&b, &d) 71 | graph.InsertEdge(&b, &c) 72 | graph.InsertEdge(&c, &e) 73 | graph.InsertEdge(&e, &g) 74 | graph.InsertEdge(&e, &f) 75 | graph.InsertEdge(&f, &h) 76 | 77 | //Test number of nodes in graph. 78 | if graph.GetNumberOfNodes() != 8 { 79 | test.Fatalf("Graph should contain 8 nodes, not %d!\n", graph.GetNumberOfNodes()) 80 | } 81 | 82 | //Node A should be root node. 83 | if graph.Root.Value != a.Value { 84 | test.Errorf("Node A should be root node, not node %v\n", graph.Root.Value) 85 | } 86 | 87 | //Test node A. 88 | if a.GetInDegree() != 0 { 89 | test.Errorf("Node A in-degree should be 0, not %d\n", a.GetInDegree()) 90 | } 91 | if a.GetOutDegree() != 2 { 92 | test.Errorf("Node A out-degree should be 2, not %d\n", a.GetInDegree()) 93 | } 94 | 95 | //Test node B. 96 | if b.GetInDegree() != 1 { 97 | test.Errorf("Node A in-degree should be 1, not %d\n", b.GetInDegree()) 98 | } 99 | if b.GetOutDegree() != 2 { 100 | test.Errorf("Node A out-degree should be 2, not %d\n", b.GetInDegree()) 101 | } 102 | 103 | //Test node C. 104 | if c.GetInDegree() != 1 { 105 | test.Errorf("Node A in-degree should be 1, not %d\n", c.GetInDegree()) 106 | } 107 | if c.GetOutDegree() != 1 { 108 | test.Errorf("Node A out-degree should be 1, not %d\n", c.GetInDegree()) 109 | } 110 | 111 | //Test node D. 112 | if d.GetInDegree() != 2 { 113 | test.Errorf("Node A in-degree should be 2, not %d\n", d.GetInDegree()) 114 | } 115 | if d.GetOutDegree() != 0 { 116 | test.Errorf("Node A out-degree should be 0, not %d\n", d.GetInDegree()) 117 | } 118 | 119 | //Test node E. 120 | if e.GetInDegree() != 1 { 121 | test.Errorf("Node A in-degree should be 1, not %d\n", e.GetInDegree()) 122 | } 123 | if e.GetOutDegree() != 2 { 124 | test.Errorf("Node A out-degree should be 2, not %d\n", e.GetInDegree()) 125 | } 126 | 127 | //Test node F. 128 | if f.GetInDegree() != 1 { 129 | test.Errorf("Node A in-degree should be 1, not %d\n", f.GetInDegree()) 130 | } 131 | if f.GetOutDegree() != 1 { 132 | test.Errorf("Node A out-degree should be 1, not %d\n", f.GetInDegree()) 133 | } 134 | 135 | //Test node G. 136 | if g.GetInDegree() != 1 { 137 | test.Errorf("Node A in-degree should be 1, not %d\n", g.GetInDegree()) 138 | } 139 | if g.GetOutDegree() != 0 { 140 | test.Errorf("Node A out-degree should be 0, not %d\n", g.GetInDegree()) 141 | } 142 | 143 | //Test node H. 144 | if h.GetInDegree() != 1 { 145 | test.Errorf("Node A in-degree should be 1, not %d\n", h.GetInDegree()) 146 | } 147 | if h.GetOutDegree() != 0 { 148 | test.Errorf("Node A out-degree should be 0, not %d\n", h.GetInDegree()) 149 | } 150 | 151 | } 152 | 153 | func TestDepthFirstSearchInGraph(t *testing.T) { 154 | //Create some nodes. 155 | a := graph.Node{Value: Letter{"A"}} 156 | b := graph.Node{Value: Letter{"B"}} 157 | c := graph.Node{Value: Letter{"C"}} 158 | d := graph.Node{Value: Letter{"D"}} 159 | e := graph.Node{Value: Letter{"E"}} 160 | f := graph.Node{Value: Letter{"F"}} 161 | g := graph.Node{Value: Letter{"G"}} 162 | h := graph.Node{Value: Letter{"H"}} 163 | 164 | graph := graph.NewGraph() 165 | 166 | //Add directed node-pairs to graph. 167 | graph.InsertEdge(&a, &b) 168 | graph.InsertEdge(&a, &d) 169 | graph.InsertEdge(&b, &d) 170 | graph.InsertEdge(&b, &c) 171 | graph.InsertEdge(&c, &e) 172 | graph.InsertEdge(&e, &g) 173 | graph.InsertEdge(&e, &f) 174 | graph.InsertEdge(&f, &h) 175 | 176 | expectedDepthFirstSearch := graph.GetDFS() 177 | correctDfs := []Letter{{"A"}, {"B"}, {"D"}, {"C"}, {"E"}, {"G"}, {"F"}, {"H"}} 178 | 179 | //Equal length. 180 | if len(correctDfs) != len(expectedDepthFirstSearch) { 181 | t.Errorf("Length of DFS (%d) is not equal length of correct DFS (%d)!\n", len(expectedDepthFirstSearch), len(correctDfs)) 182 | } 183 | 184 | //Check nodes in depth first search. 185 | for index, node := range expectedDepthFirstSearch { 186 | if node.String() != correctDfs[index].String() { 187 | t.Error("Faen i helvete") 188 | } 189 | } 190 | 191 | } 192 | 193 | func TestDepthFirstSearchInCycleGraph(t *testing.T) { 194 | //Create some nodes. 195 | a := graph.Node{Value: Letter{"A"}} 196 | b := graph.Node{Value: Letter{"B"}} 197 | c := graph.Node{Value: Letter{"C"}} 198 | d := graph.Node{Value: Letter{"D"}} 199 | e := graph.Node{Value: Letter{"E"}} 200 | f := graph.Node{Value: Letter{"F"}} 201 | g := graph.Node{Value: Letter{"G"}} 202 | 203 | directGraph := graph.NewGraph() 204 | 205 | directGraph.InsertEdge(&a, &b) 206 | directGraph.InsertEdge(&a, &c) 207 | directGraph.InsertEdge(&a, &e) 208 | directGraph.InsertEdge(&b, &d) 209 | directGraph.InsertEdge(&b, &f) 210 | directGraph.InsertEdge(&c, &g) 211 | directGraph.InsertEdge(&f, &e) 212 | 213 | expectedDepthFirstSearch := directGraph.GetDFS() 214 | correctDepthFirstSearch := []Letter{ 215 | {"A"}, {"B"}, {"D"}, {"F"}, {"E"}, {"C"}, {"G"}, 216 | } 217 | 218 | //Compare DFS with correct DFS. 219 | for index := 0; index < len(expectedDepthFirstSearch); index++ { 220 | if expectedDepthFirstSearch[index].Value.String() != correctDepthFirstSearch[index].letter { 221 | t.Errorf("Element nr. %d in DFS should be %s, not %s\n", index, correctDepthFirstSearch[index].letter, 222 | expectedDepthFirstSearch[index].Value.String()) 223 | } 224 | } 225 | 226 | } 227 | 228 | func TestStronglyConnectedComponentsInGraph(t *testing.T) { 229 | directedGraph := graph.NewGraph() 230 | 231 | a := graph.Node{Value: Letter{"A"}} 232 | b := graph.Node{Value: Letter{"B"}} 233 | c := graph.Node{Value: Letter{"C"}} 234 | d := graph.Node{Value: Letter{"D"}} 235 | e := graph.Node{Value: Letter{"E"}} 236 | f := graph.Node{Value: Letter{"F"}} 237 | g := graph.Node{Value: Letter{"G"}} 238 | h := graph.Node{Value: Letter{"H"}} 239 | 240 | directedGraph.InsertEdge(&a, &b) 241 | directedGraph.InsertEdge(&b, &c) 242 | directedGraph.InsertEdge(&c, &d) 243 | directedGraph.InsertEdge(&d, &c) 244 | directedGraph.InsertEdge(&d, &h) 245 | directedGraph.InsertEdge(&h, &d) 246 | directedGraph.InsertEdge(&c, &g) 247 | directedGraph.InsertEdge(&h, &g) 248 | directedGraph.InsertEdge(&f, &g) 249 | directedGraph.InsertEdge(&g, &f) 250 | directedGraph.InsertEdge(&b, &f) 251 | directedGraph.InsertEdge(&e, &f) 252 | directedGraph.InsertEdge(&e, &a) 253 | directedGraph.InsertEdge(&b, &e) 254 | 255 | expectedStronglyConnectedComponents := directedGraph.GetSCComponents() 256 | correctStronglyConnectedComponents := []*graph.StronglyConnectedComponent{ 257 | { 258 | Nodes: []*graph.Node{ 259 | {Value: Letter{"F"}}, 260 | {Value: Letter{"G"}}, 261 | }}, 262 | { 263 | Nodes: []*graph.Node{ 264 | {Value: Letter{"H"}}, 265 | {Value: Letter{"C"}}, 266 | {Value: Letter{"D"}}, 267 | }}, 268 | { 269 | Nodes: []*graph.Node{ 270 | {Value: Letter{"B"}}, 271 | {Value: Letter{"A"}}, 272 | {Value: Letter{"E"}}, 273 | }}, 274 | } 275 | 276 | // Check the number of SCC sets. 277 | if len(expectedStronglyConnectedComponents) != len(correctStronglyConnectedComponents) { 278 | t.Fatalf("Number of strongly connected components be %d, but are %d!\n", len(correctStronglyConnectedComponents), 279 | len(expectedStronglyConnectedComponents)) 280 | } 281 | 282 | if !sccExists(correctStronglyConnectedComponents, expectedStronglyConnectedComponents) { 283 | t.Error("Not all SCC exists") 284 | } 285 | 286 | } 287 | 288 | func TestStronglyConnectedComponentsInGraph2(t *testing.T) { 289 | directedGraph := graph.NewGraph() 290 | 291 | a := graph.Node{Value: Letter{"A"}} 292 | b := graph.Node{Value: Letter{"B"}} 293 | c := graph.Node{Value: Letter{"C"}} 294 | d := graph.Node{Value: Letter{"D"}} 295 | e := graph.Node{Value: Letter{"E"}} 296 | f := graph.Node{Value: Letter{"F"}} 297 | g := graph.Node{Value: Letter{"G"}} 298 | h := graph.Node{Value: Letter{"H"}} 299 | 300 | directedGraph.InsertEdge(&a, &b) 301 | directedGraph.InsertEdge(&a, &f) 302 | directedGraph.InsertEdge(&b, &f) 303 | directedGraph.InsertEdge(&b, &c) 304 | directedGraph.InsertEdge(&c, &d) 305 | directedGraph.InsertEdge(&c, &g) 306 | directedGraph.InsertEdge(&e, &a) 307 | directedGraph.InsertEdge(&f, &e) 308 | directedGraph.InsertEdge(&f, &g) 309 | directedGraph.InsertEdge(&g, &c) 310 | directedGraph.InsertEdge(&h, &g) 311 | 312 | expectedStronglyConnectedComponents := directedGraph.GetSCComponents() 313 | correctStronglyConnectedComponents := []*graph.StronglyConnectedComponent{ 314 | { 315 | Nodes: []*graph.Node{ 316 | {Value: Letter{"D"}}, 317 | }}, 318 | { 319 | Nodes: []*graph.Node{ 320 | {Value: Letter{"C"}}, 321 | {Value: Letter{"G"}}, 322 | }}, 323 | { 324 | Nodes: []*graph.Node{ 325 | {Value: Letter{"A"}}, 326 | {Value: Letter{"B"}}, 327 | {Value: Letter{"E"}}, 328 | {Value: Letter{"F"}}, 329 | }}, 330 | { 331 | Nodes: []*graph.Node{ 332 | {Value: Letter{"H"}}, 333 | }}, 334 | } 335 | 336 | // Check the number of SCC sets. 337 | if len(expectedStronglyConnectedComponents) != len(correctStronglyConnectedComponents) { 338 | t.Fatalf("Number of strongly connected components be %d, but are %d!\n", len(correctStronglyConnectedComponents), 339 | len(expectedStronglyConnectedComponents)) 340 | } 341 | 342 | if !sccExists(correctStronglyConnectedComponents, expectedStronglyConnectedComponents) { 343 | t.Error("Not all SCC exists") 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/graph/stack/stacker.go: -------------------------------------------------------------------------------- 1 | //Package graph.stack provides an implementation of the 2 | //Stack abstract-datastructure, basically providing 3 | //the standard graph.stack operating primitives. 4 | // 5 | //Author: Mark Summerfield, found in the book: 6 | //"Programming in Go, Creating Applications for the 21st Century" 7 | // 8 | //Author: Christian Bergum Bergersen, added comments to satisfy 9 | //Golang code convention. 10 | package stack 11 | 12 | import "errors" 13 | 14 | //Stack is an array data-structure with the properties of a LIFO queue. 15 | type Stack []interface{} 16 | 17 | //Len returns the size(length) of the graph.stack. 18 | func (stack Stack) Len() int { 19 | return len(stack) 20 | } 21 | 22 | //Cap returns the capacity of the graph.stack. 23 | func (stack Stack) Cap() int { 24 | return cap(stack) 25 | } 26 | 27 | //IsEmpty returns true if graph.stack is empty, false otherwise. 28 | func (stack Stack) IsEmpty() bool { 29 | return len(stack) == 0 30 | } 31 | 32 | //Push puts x on the top of the graph.stack. 33 | func (stack *Stack) Push(x interface{}) { 34 | *stack = append(*stack, x) 35 | } 36 | 37 | //Pop removes and returns the first(top) element of the 38 | //graph.stack, or returns an error message if the graph.stack is empty. 39 | func (stack *Stack) Pop() (interface{}, error) { 40 | theStack := *stack 41 | if len(theStack) == 0 { 42 | return nil, errors.New("can't Pop() an empty stack") 43 | } 44 | x := theStack[len(theStack)-1] 45 | *stack = theStack[:len(theStack)-1] 46 | return x, nil 47 | } 48 | 49 | //Top returns the first first(top) element in the 50 | //graph.stack, or error message if the graph.stack is empty. 51 | func (stack Stack) Top() (interface{}, error) { 52 | if len(stack) == 0 { 53 | return nil, errors.New("cant't Top() an empty stack") 54 | } 55 | return stack[len(stack)-1], nil 56 | } 57 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/graph/stack/stacker_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity/graph/stack" 5 | "testing" 6 | ) 7 | 8 | func TestStackSize(t *testing.T) { 9 | stacker := stack.Stack{} 10 | 11 | stacker.Push("First") 12 | stacker.Push("Seconds") 13 | 14 | if stacker.Len() != 2 { 15 | t.Errorf("Size of stack should be 2, not %d\n", stacker.Len()) 16 | } 17 | 18 | stacker.Pop() 19 | 20 | if stacker.Len() != 1 { 21 | t.Errorf("Size of stack should be 1, not %d\n", stacker.Len()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/testcode/_gcd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func gcd(x, y int) int { 9 | // BB #0 ending. 10 | for y != 0 { 11 | // BB #1 ending. 12 | x, y = y, x % y // BB #2 ending. 13 | } 14 | return x // BB #3 ending. 15 | } 16 | 17 | func main() { 18 | // BB #4 ending. 19 | fmt.Println(gcd(33, 77)) 20 | fmt.Println(gcd(49865, 69811)) // BB #5 ending. 21 | } 22 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/testcode/_helloworld.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main // BB #0 starting. 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | // BB #1 starting. 10 | fmt.Println("Hello World") 11 | } 12 | 13 | // Cyclomatic Complexity M = E - N + 2P. 14 | // E = Number of edges in control flow graph. 15 | // N = Number of nodes in control flow graph. 16 | // P = Number of connected components in graph. 17 | // File level: M = 1 - 2 + 2 * 2 = 3 18 | // Function level: M = 0 - 1 + 2 * 1 = 1 19 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/testcode/_ifelse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "log" 7 | 8 | func main() { 9 | line := "FooBar" 10 | 11 | if len(line) == 0 { 12 | log.Println("line is empty") 13 | } else { 14 | log.Println("line is not empty") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/testcode/_swap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | stringA := "A" 10 | stringB := "B" 11 | 12 | fmt.Printf("Before swap:\t stringA = %s, stringB = %s\n", stringA, stringB) 13 | swap(&stringA, &stringB) 14 | fmt.Printf("After swap:\t stringA = %s, stringB = %s\n", stringA, stringB) 15 | } 16 | 17 | func swap(a *string, b *string) { 18 | tmp := a 19 | *a = *b 20 | *b = *tmp 21 | } 22 | -------------------------------------------------------------------------------- /analyzer/linter/ccomplexity/testcode/_switcher.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | month := 10 10 | fmt.Printf("Month %d is %s\n", month, monthNumberToString(month)) 11 | } 12 | 13 | func monthNumberToString(month int) string { 14 | switch month { 15 | case 1: 16 | return "January" 17 | case 2: 18 | return "February" 19 | case 3: 20 | return "March" 21 | case 4: 22 | return "April" 23 | case 5: 24 | return "May" 25 | case 6: 26 | return "June" 27 | case 7: 28 | return "Juni" 29 | case 8: 30 | return "August" 31 | case 9: 32 | return "September" 33 | case 10: 34 | return "October" 35 | case 11: 36 | return "November" 37 | case 12: 38 | return "Desember" 39 | default: 40 | return "Invalid month" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /analyzer/linter/linter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package linter 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "go/ast" 10 | "go/importer" 11 | "go/parser" 12 | "go/token" 13 | "go/types" 14 | "os" 15 | "regexp" 16 | "strings" 17 | 18 | "bytes" 19 | "github.com/chrisbbe/GoAnalysis/analyzer/linter/ccomplexity" 20 | "io/ioutil" 21 | "log" 22 | "path/filepath" 23 | ) 24 | 25 | const CC_LIMIT = 10 // Upper limit of cyclomatic complexity measures. 26 | const ERROR_OUTPUT_FILE = "GoAnalyzerError.log" 27 | 28 | var errorFileLogger *log.Logger 29 | 30 | // Set logging to file!. 31 | func init() { 32 | errorLogFile, err := os.OpenFile(ERROR_OUTPUT_FILE, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 33 | if err != nil { 34 | panic(err) 35 | } 36 | errorFileLogger = log.New(errorLogFile, "", log.Lshortfile) 37 | } 38 | 39 | type Rule int 40 | 41 | //Rules. 42 | const ( 43 | RACE_CONDITION Rule = iota 44 | FMT_PRINTING 45 | STRING_CALLS_ITSELF 46 | MAP_ALLOCATED_WITH_NEW 47 | ERROR_IGNORED 48 | EMPTY_IF_BODY 49 | EMPTY_ELSE_BODY 50 | EMPTY_FOR_BODY 51 | GOTO_USED 52 | CONDITION_EVALUATED_STATICALLY 53 | BUFFER_NOT_FLUSHED 54 | RETURN_KILLS_CODE 55 | CYCLOMATIC_COMPLEXITY 56 | ) 57 | 58 | var ruleStrings = map[Rule]string{ 59 | RACE_CONDITION: "RACE_CONDITION", 60 | FMT_PRINTING: "FMT_PRINTING", 61 | STRING_CALLS_ITSELF: "STRING_CALLS_ITSELF", 62 | MAP_ALLOCATED_WITH_NEW: "MAP_ALLOCATED_WITH_NEW", 63 | ERROR_IGNORED: "ERROR_IGNORED", 64 | EMPTY_IF_BODY: "EMPTY_IF_BODY", 65 | EMPTY_ELSE_BODY: "EMPTY_ELSE_BODY", 66 | EMPTY_FOR_BODY: "EMPTY_FOR_BODY", 67 | GOTO_USED: "GOTO_USED", 68 | CONDITION_EVALUATED_STATICALLY: "CONDITION_EVALUATED_STATICALLY", 69 | BUFFER_NOT_FLUSHED: "NO_BUFFERED_FLUSHING", 70 | RETURN_KILLS_CODE: "RETURN_KILLS_CODE", 71 | CYCLOMATIC_COMPLEXITY: "CYCLOMATIC_COMPLEXITY", 72 | } 73 | 74 | type GoFile struct { 75 | FilePath string 76 | LinesOfCode int 77 | LinesOfComments int 78 | Violations []*Violation 79 | 80 | goFileNode *ast.File 81 | fileSet *token.FileSet 82 | typeInfo *types.Info 83 | 84 | typeErrorLogFile *os.File 85 | } 86 | 87 | type GoPackage struct { 88 | Violations []*GoFile 89 | 90 | Path string 91 | Pack *ast.Package 92 | 93 | fileSet *token.FileSet 94 | typeInfo *types.Info 95 | } 96 | 97 | func (goPackage *GoPackage) GetFileNodes() (goFiles []*ast.File) { 98 | for _, goFile := range goPackage.Pack.Files { 99 | goFiles = append(goFiles, goFile) 100 | } 101 | return 102 | } 103 | 104 | type Violation struct { 105 | Type Rule 106 | Description string 107 | SrcLine int 108 | } 109 | 110 | func (rule Rule) String() string { 111 | return ruleStrings[rule] 112 | } 113 | 114 | func (violation *Violation) String() string { 115 | return fmt.Sprintf("%s (Line %d) - %s.", violation.Type, violation.SrcLine, violation.Description) 116 | } 117 | 118 | // Marshal Rule string value instead of int value. 119 | func (rule Rule) MarshalText() ([]byte, error) { 120 | var buffer bytes.Buffer 121 | if _, err := buffer.WriteString(rule.String()); err != nil { 122 | return buffer.Bytes(), err 123 | } 124 | return buffer.Bytes(), nil 125 | } 126 | 127 | func isDirectory(path string) (bool, error) { 128 | fileInfo, err := os.Stat(path) 129 | if err != nil { 130 | return false, err 131 | } 132 | return fileInfo.IsDir(), err 133 | } 134 | 135 | func getAllDirectories(searchDir string) (map[string]map[string]*ast.Package, *token.FileSet) { 136 | dirPackages := make(map[string]map[string]*ast.Package) 137 | fileSet := token.NewFileSet() 138 | 139 | err := filepath.Walk(searchDir, func(folderPath string, f os.FileInfo, err error) error { 140 | if f.IsDir() { 141 | pks, firstErr := parser.ParseDir(fileSet, folderPath, nil, parser.ParseComments) 142 | if firstErr != nil { 143 | return firstErr 144 | } 145 | dirPackages[folderPath] = pks 146 | } 147 | return nil 148 | }) 149 | 150 | if err != nil { 151 | log.Fatalf("Parsing error: %s", err) 152 | } 153 | return dirPackages, fileSet 154 | } 155 | 156 | func DetectViolations(goSourceDir string) (goPackageViolations []*GoPackage, err error) { 157 | if isDir, err := isDirectory(goSourceDir); err == nil && !isDir { 158 | return goPackageViolations, fmt.Errorf("%s is not a directory", goSourceDir) 159 | } else if err != nil { 160 | return goPackageViolations, err 161 | } 162 | 163 | sourceDirPackages, fileSet := getAllDirectories(goSourceDir) 164 | 165 | for path, pkgs := range sourceDirPackages { 166 | for _, packNode := range pkgs { 167 | 168 | goPackage := &GoPackage{ 169 | Path: path, 170 | Pack: packNode, 171 | fileSet: fileSet, 172 | } 173 | 174 | goPackage.measureCyclomaticComplexity(CC_LIMIT) 175 | goPackage.detectBugsAndCodeSmells() 176 | 177 | if len(goPackage.Violations) > 0 { 178 | goPackageViolations = append(goPackageViolations, goPackage) 179 | } 180 | } 181 | } 182 | 183 | return goPackageViolations, nil 184 | } 185 | 186 | // countLinesInFile counts number of lines which are code and comments and returns the result. 187 | func (goFile *GoFile) countLinesInFile() { 188 | file, err := os.Open(goFile.FilePath) 189 | if err != nil { 190 | panic(err) 191 | } 192 | // Close file after reading! 193 | defer func() { 194 | if err := file.Close(); err != nil { 195 | panic(err) 196 | } 197 | }() 198 | 199 | scanner := bufio.NewScanner(file) 200 | for scanner.Scan() { 201 | if len(scanner.Text()) > 0 { 202 | line := strings.TrimSpace(scanner.Text()) 203 | if strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*/") { 204 | goFile.LinesOfComments++ 205 | } else { 206 | goFile.LinesOfCode++ 207 | } 208 | } 209 | } 210 | } 211 | 212 | func (goPackage *GoPackage) measureCyclomaticComplexity(upperLimit int) error { 213 | for filePath, goFile := range goPackage.Pack.Files { 214 | srcFile, err := ioutil.ReadFile(filePath) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | complexity, err := ccomplexity.GetCyclomaticComplexityFunctionLevel(filePath, srcFile) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | for _, funCC := range complexity { 225 | if funCC.Complexity > upperLimit { 226 | 227 | goFile := &GoFile{ 228 | FilePath: filePath, 229 | goFileNode: goFile, 230 | } 231 | goFile.countLinesInFile() 232 | 233 | goFile.Violations = append(goFile.Violations, &Violation{ 234 | Type: CYCLOMATIC_COMPLEXITY, 235 | SrcLine: funCC.SrcLine, 236 | Description: fmt.Sprintf("Cyclomatic complexity in %s() is %d, upper limit is %d.", 237 | funCC.Name, funCC.Complexity, upperLimit), 238 | }) 239 | goPackage.Violations = append(goPackage.Violations, goFile) 240 | } 241 | } 242 | } 243 | return nil 244 | } 245 | 246 | func (goPackage *GoPackage) detectBugsAndCodeSmells() error { 247 | conf := types.Config{ 248 | Importer: importer.Default(), 249 | FakeImportC: true, 250 | DisableUnusedImportCheck: true, 251 | } 252 | 253 | goPackage.typeInfo = &types.Info{ 254 | Types: make(map[ast.Expr]types.TypeAndValue), 255 | Uses: make(map[*ast.Ident]types.Object), 256 | } 257 | 258 | if _, err := conf.Check(goPackage.Pack.Name, goPackage.fileSet, goPackage.GetFileNodes(), goPackage.typeInfo); err != nil { 259 | errorFileLogger.Printf("Error: %s", err) 260 | } 261 | 262 | goPackage.Analyze() 263 | 264 | return nil 265 | } 266 | 267 | func (goPackage *GoPackage) Analyze() { 268 | for filePath, file := range goPackage.Pack.Files { 269 | goFile := &GoFile{ 270 | FilePath: filePath, 271 | goFileNode: file, 272 | typeInfo: goPackage.typeInfo, 273 | fileSet: goPackage.fileSet, 274 | } 275 | 276 | goFile.Analyse() 277 | 278 | if len(goFile.Violations) > 0 { 279 | goFile.countLinesInFile() 280 | goPackage.Violations = append(goPackage.Violations, goFile) 281 | } 282 | } 283 | } 284 | 285 | // Analyse fires off all detection algorithms on the goFile. 286 | func (goFile *GoFile) Analyse() { 287 | goFile.detectFmtPrinting() 288 | goFile.detectMapsAllocatedWithNew() 289 | goFile.detectEmptyIfBody() 290 | goFile.detectEmptyElseBody() 291 | goFile.detectEmptyForBody() 292 | goFile.detectGoToStatements() 293 | goFile.detectRaceInGoRoutine() 294 | goFile.detectIgnoredErrors() 295 | goFile.detectStaticCondition() 296 | goFile.detectRecursiveStringMethods() 297 | goFile.detectReturnKillingCode() 298 | goFile.detectBufferNotFlushed() 299 | } 300 | 301 | type walker func(ast.Node) bool 302 | 303 | func (w walker) Visit(node ast.Node) ast.Visitor { 304 | if w(node) { 305 | return w 306 | } 307 | return nil 308 | } 309 | 310 | func (f *GoFile) walk(fn func(ast.Node) bool) { 311 | ast.Walk(walker(fn), f.goFileNode) 312 | } 313 | 314 | // ruleIgnored inspects the commentGroup after a @SuppressRule("RULE_NAME") annotation 315 | // corresponding to the ruleToIgnore specified. Return TRUE if found, ELSE otherwise. 316 | func ruleIgnored(ruleToIgnore Rule, commentGroup *ast.CommentGroup) bool { 317 | suppressRule := regexp.MustCompile(`@SuppressRule\("(?P\S+)"\)`) 318 | if commentGroup != nil { 319 | for _, comment := range commentGroup.List { 320 | if result := suppressRule.FindStringSubmatch(comment.Text); len(result) > 0 { 321 | if result[1] == ruleToIgnore.String() { 322 | return true 323 | } 324 | } 325 | } 326 | } 327 | return false 328 | } 329 | 330 | // validateParams checks that all statements in function body is referencing 331 | // local variable (including function argument), and not referencing outer scope. 332 | func validateParams(exprStmt *ast.ExprStmt, List []*ast.Field) bool { 333 | if callExpr, ok := exprStmt.X.(*ast.CallExpr); ok { 334 | // It is a call expression. 335 | for _, argument := range callExpr.Args { 336 | // check all function arguments 337 | if argumentName, ok := argument.(*ast.Ident); ok { 338 | if !nameInFieldList(argumentName, List) { 339 | return false 340 | } 341 | } 342 | } 343 | } 344 | return true 345 | } 346 | 347 | // nameInFieldList check ifs 'fieldList' (field/method/parameter) contains the name 'name'. 348 | func nameInFieldList(name *ast.Ident, fieldList []*ast.Field) bool { 349 | for _, field := range fieldList { 350 | if field != nil { 351 | // Field might be anonymous. 352 | for _, fieldName := range field.Names { 353 | if fieldName.Name == name.Name { 354 | return true 355 | } 356 | } 357 | } 358 | } 359 | return false 360 | } 361 | 362 | // getSourceCodeLineNumber returns the line number in the parsed source code, according to the tokens position. 363 | func getSourceCodeLineNumber(fileSet *token.FileSet, position token.Pos) int { 364 | return fileSet.File(position).Line(position) 365 | } 366 | 367 | // AddViolation adds a new violation as specified trough its argument to the GoFile list of violations. 368 | func (f *GoFile) AddViolation(tokPosition token.Pos, violationType Rule, description string) *Violation { 369 | srcLine := getSourceCodeLineNumber(f.fileSet, tokPosition) 370 | violation := &Violation{ 371 | Type: violationType, 372 | Description: description, 373 | SrcLine: srcLine, 374 | } 375 | f.Violations = append(f.Violations, violation) 376 | return violation 377 | } 378 | 379 | // Detect violations of rule: FMT_PRINTING. 380 | func (goFile *GoFile) detectFmtPrinting() { 381 | ignored := false 382 | goFile.walk(func(node ast.Node) bool { 383 | fmtMethods := []string{"Print", "Println", "Printf"} 384 | 385 | switch t := node.(type) { 386 | case *ast.FuncDecl: 387 | ignored = ruleIgnored(FMT_PRINTING, t.Doc) 388 | case *ast.SelectorExpr: 389 | if !ignored { 390 | if packName, ok := t.X.(*ast.Ident); ok { 391 | for _, method := range fmtMethods { 392 | if packName.Name == "fmt" && t.Sel.Name == method { 393 | goFile.AddViolation( 394 | t.Pos(), 395 | FMT_PRINTING, 396 | fmt.Sprint("Printing from the fmt package are not synchronized and usually intended for"+ 397 | " debugging purposes. Consider to use the log package!"), 398 | ) 399 | return false 400 | } 401 | } 402 | } 403 | } 404 | } 405 | return true 406 | }) 407 | } 408 | 409 | // Detect violations of rule: MAP_ALLOCATED_WITH_NEW. 410 | func (goFile *GoFile) detectMapsAllocatedWithNew() { 411 | goFile.walk(func(node ast.Node) bool { 412 | newKeyword := "new" 413 | 414 | switch t := node.(type) { 415 | case *ast.CallExpr: 416 | if value, ok := t.Fun.(*ast.Ident); ok { 417 | if ok && value.Name == newKeyword { 418 | if _, ok := t.Args[0].(*ast.MapType); ok { 419 | goFile.AddViolation( 420 | t.Pos(), 421 | MAP_ALLOCATED_WITH_NEW, 422 | fmt.Sprint("Maps must be initialized with make(), new() allocates a nil map causing runtime "+ 423 | "panic on write operations!"), 424 | ) 425 | return false 426 | } 427 | } 428 | } 429 | } 430 | return true 431 | }) 432 | } 433 | 434 | // Detect violations of rule: EMPTY_IF_BODY. 435 | func (goFile *GoFile) detectEmptyIfBody() { 436 | goFile.walk(func(node ast.Node) bool { 437 | if ifStmt, ok := node.(*ast.IfStmt); ok { 438 | if len(ifStmt.Body.List) == 0 { 439 | goFile.AddViolation( 440 | ifStmt.Pos(), 441 | EMPTY_IF_BODY, 442 | fmt.Sprint("If body is empty, wasteful to not do anything with the if condition."), 443 | ) 444 | return false 445 | } 446 | } 447 | return true 448 | }) 449 | } 450 | 451 | // Detect violations of rule: EMPTY_ELSE_BODY. 452 | func (goFile *GoFile) detectEmptyElseBody() { 453 | goFile.walk(func(node ast.Node) bool { 454 | if ifStmt, ok := node.(*ast.IfStmt); ok { 455 | if elseBody, ok := ifStmt.Else.(*ast.BlockStmt); ok { 456 | if len(elseBody.List) == 0 { 457 | goFile.AddViolation( 458 | elseBody.Pos(), 459 | EMPTY_ELSE_BODY, 460 | fmt.Sprint("ELse body is empty, wasteful to not do anything with the else condition."), 461 | ) 462 | return false 463 | } 464 | } 465 | } 466 | return true 467 | }) 468 | } 469 | 470 | // Detect violations of rule: EMPTY_FOR_BODY. 471 | func (goFile *GoFile) detectEmptyForBody() { 472 | goFile.walk(func(node ast.Node) bool { 473 | if forStmt, ok := node.(*ast.ForStmt); ok { 474 | if len(forStmt.Body.List) == 0 { 475 | goFile.AddViolation( 476 | forStmt.Pos(), 477 | EMPTY_FOR_BODY, 478 | fmt.Sprint("For body is empty, wasteful to not do anything with the for condition."), 479 | ) 480 | return false 481 | } 482 | } 483 | return true 484 | }) 485 | } 486 | 487 | // Detect violations of rule: GOTO_USED. 488 | func (goFile *GoFile) detectGoToStatements() { 489 | goFile.walk(func(node ast.Node) bool { 490 | if branchStmt, ok := node.(*ast.BranchStmt); ok { 491 | if branchStmt.Label != nil { 492 | goFile.AddViolation( 493 | branchStmt.Pos(), 494 | GOTO_USED, 495 | fmt.Sprint("Please dont use GOTO statements, they lead to spagehetti code!"), 496 | ) 497 | return false 498 | } 499 | } 500 | return true 501 | }) 502 | } 503 | 504 | // Detect violations of rule: RACE_CONDITION. 505 | func (goFile *GoFile) detectRaceInGoRoutine() { 506 | goFile.walk(func(node ast.Node) bool { 507 | if goStmt, ok := node.(*ast.GoStmt); ok { 508 | 509 | if goFunc, ok := goStmt.Call.Fun.(*ast.FuncLit); ok { 510 | goFuncParams := goFunc.Type.Params.List 511 | 512 | for _, goFuncBodyStmt := range goFunc.Body.List { 513 | if exprStmt, ok := goFuncBodyStmt.(*ast.ExprStmt); ok { 514 | 515 | if !validateParams(exprStmt, goFuncParams) { 516 | goFile.AddViolation( 517 | node.Pos(), 518 | RACE_CONDITION, 519 | fmt.Sprint("Loop iterator variables must be passed as argument to Goroutine, not referenced."), 520 | ) 521 | return false 522 | } 523 | 524 | } 525 | } 526 | } 527 | } 528 | return true 529 | }) 530 | } 531 | 532 | // Detect violations of rule: RETURN_KILLS_CODE. 533 | func (goFile *GoFile) detectReturnKillingCode() { 534 | goFile.walk(func(node ast.Node) bool { 535 | 536 | if funcDecl, ok := node.(*ast.FuncDecl); ok { 537 | if funcDecl.Body != nil { 538 | bodyLength := len(funcDecl.Body.List) - 1 539 | for index, bodyStmt := range funcDecl.Body.List { 540 | 541 | if _, ok := bodyStmt.(*ast.ReturnStmt); ok && bodyLength > index { 542 | goFile.AddViolation( 543 | bodyStmt.Pos(), 544 | RETURN_KILLS_CODE, 545 | fmt.Sprint("Code is dead because of return! There is no possible execution path to the code below in "+ 546 | "this scope!"), 547 | ) 548 | return false 549 | } 550 | } 551 | } 552 | } 553 | return true 554 | }) 555 | } 556 | 557 | // Detect violations of rule: ERROR_IGNORE. 558 | func (goFile *GoFile) detectIgnoredErrors() { 559 | errorType := "error" 560 | ignored := false 561 | var returnResults []ast.Expr 562 | var rightHandSideCallExpr []*ast.CallExpr // Holds CallExpr taken part in AssignStmt, avoid checking these CallExpr. 563 | 564 | goFile.walk(func(node ast.Node) bool { 565 | switch t := node.(type) { 566 | case *ast.FuncDecl: 567 | ignored = ruleIgnored(ERROR_IGNORED, t.Doc) 568 | 569 | case *ast.ReturnStmt: 570 | // Hold the list of return result expressions. 571 | returnResults = t.Results 572 | 573 | case *ast.AssignStmt: 574 | // Save CalLExpr that is part of Rhs. 575 | if callExpr, ok := t.Rhs[0].(*ast.CallExpr); ok { 576 | rightHandSideCallExpr = append(rightHandSideCallExpr, callExpr) 577 | } 578 | 579 | if !ignored { 580 | var ignoredReturnIndex []int 581 | for index, expr := range t.Lhs { 582 | if varName, ok := expr.(*ast.Ident); ok { 583 | if varName.Name == "_" { 584 | ignoredReturnIndex = append(ignoredReturnIndex, index) 585 | } 586 | } 587 | } 588 | if len(ignoredReturnIndex) != 0 { 589 | if tv, ok := goFile.typeInfo.Types[t.Rhs[0]]; ok { 590 | if tuple, ok := tv.Type.(*types.Tuple); ok { 591 | for _, returnIndex := range ignoredReturnIndex { 592 | if returnIndex <= tuple.Len() && tuple.At(returnIndex).Type().String() == errorType { 593 | goFile.AddViolation( 594 | t.Pos(), 595 | ERROR_IGNORED, 596 | fmt.Sprint("Never ignore erros, ignoring them can lead to program crashes"), 597 | ) 598 | return false 599 | } 600 | } 601 | } 602 | } 603 | } 604 | } 605 | return true 606 | 607 | case *ast.CallExpr: 608 | if !ignored { 609 | // Loop through return result expression to check whether CallExpr is part of return. 610 | // Flagging errors not assigned to variable in return statement is wrong! 611 | for _, returnResult := range returnResults { 612 | if returnCallExpr, ok := returnResult.(*ast.CallExpr); ok { 613 | if returnCallExpr == t { 614 | // Call Expression is in return, stop looking after calls on functions returning error. 615 | return false 616 | } 617 | } 618 | } 619 | 620 | // We don't want to check CallExpressions that is part of AssignStmt. 621 | for _, rhsCallExpr := range rightHandSideCallExpr { 622 | if t == rhsCallExpr { 623 | return false 624 | } 625 | } 626 | 627 | // CallExpr is not part of return result, check further! 628 | if tv, ok := goFile.typeInfo.Types[t]; ok { 629 | if name, ok := tv.Type.(*types.Named); ok { 630 | 631 | if name.String() == errorType { 632 | goFile.AddViolation( 633 | t.Pos(), 634 | ERROR_IGNORED, 635 | fmt.Sprint("Never ignore erros, ignoring them can lead to program crashes"), 636 | ) 637 | return false 638 | } 639 | } else if tuple, ok := tv.Type.(*types.Tuple); ok { 640 | for i := 0; i < tuple.Len(); i++ { 641 | if tuple.At(i).Type().String() == errorType { 642 | goFile.AddViolation( 643 | t.Pos(), 644 | ERROR_IGNORED, 645 | fmt.Sprint("Never ignore erros, ignoring them can lead to program crashes"), 646 | ) 647 | return false 648 | } 649 | } 650 | } 651 | } 652 | } 653 | } 654 | return true 655 | }) 656 | } 657 | 658 | func (goFile *GoFile) detectStaticCondition() { 659 | var booleanOperand = map[token.Token]bool{ 660 | token.LAND: true, // && 661 | token.LOR: true, // || 662 | token.LEQ: true, // <= 663 | token.GEQ: true, // >= 664 | token.NOT: true, // ! 665 | token.GTR: true, // > 666 | token.LSS: true, // < 667 | token.EQL: true, // == 668 | token.NEQ: true, // != 669 | } 670 | var lastViolation *Violation 671 | 672 | goFile.walk(func(node ast.Node) bool { 673 | switch t := node.(type) { 674 | 675 | case *ast.IfStmt: 676 | if cond, ok := t.Cond.(*ast.Ident); ok && (cond.Name == "true" || cond.Name == "false") { 677 | // Catch obvious static conditions. 678 | lastViolation = goFile.AddViolation( 679 | t.Pos(), 680 | CONDITION_EVALUATED_STATICALLY, 681 | fmt.Sprintf("Condition will always be %s", cond), 682 | ) 683 | } 684 | 685 | case *ast.BinaryExpr: 686 | if booleanOperand[t.Op] { 687 | if _, ok := t.X.(*ast.BasicLit); ok { 688 | if _, ok := t.Y.(*ast.BasicLit); ok { 689 | // We don't want to add flag the same violation more than once in composed expressions eg. (x || y && k) 690 | // where x,y and k is basic literals. 691 | if lastViolation != nil && lastViolation.SrcLine != getSourceCodeLineNumber(goFile.fileSet, t.Pos()) { 692 | lastViolation = goFile.AddViolation( 693 | t.Pos(), 694 | CONDITION_EVALUATED_STATICALLY, 695 | fmt.Sprint("Both left and right operand is basic literal that can be eveualted at compile time"), 696 | ) 697 | } 698 | } 699 | } 700 | } 701 | if t.Op == token.EQL || t.Op == token.NEQ { 702 | // If this is a comparison against nil, find the other operand. 703 | var other ast.Expr 704 | if goFile.typeInfo.Types[t.X].IsNil() { 705 | other = t.Y 706 | } else if goFile.typeInfo.Types[t.Y].IsNil() { 707 | other = t.X 708 | } 709 | 710 | // Find the object. 711 | var obj types.Object 712 | 713 | if other != nil { 714 | switch v := other.(type) { 715 | case *ast.Ident: 716 | obj = goFile.typeInfo.Uses[v] 717 | case *ast.SelectorExpr: 718 | obj = goFile.typeInfo.Uses[v.Sel] 719 | } 720 | } 721 | 722 | if obj != nil { 723 | if _, ok := obj.(*types.Func); ok { 724 | lastViolation = goFile.AddViolation( 725 | t.Pos(), 726 | CONDITION_EVALUATED_STATICALLY, 727 | fmt.Sprintf("Comparison of function %s is always %v", obj.Name(), t.Op == token.NEQ), 728 | ) 729 | } 730 | } 731 | } 732 | 733 | } 734 | return true 735 | }) 736 | } 737 | 738 | // TODO: Implement. 739 | func (goFile *GoFile) detectRecursiveStringMethods() { 740 | goFile.walk(func(node ast.Node) bool { 741 | return false 742 | }) 743 | } 744 | 745 | //TODO : Is it possible to actually do this without escape analysis? 746 | func (goFile *GoFile) detectBufferNotFlushed() { 747 | goFile.walk(func(node ast.Node) bool { 748 | return true 749 | }) 750 | } 751 | -------------------------------------------------------------------------------- /analyzer/linter/linter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package linter_test 5 | 6 | import ( 7 | "fmt" 8 | "github.com/chrisbbe/GoAnalysis/analyzer/linter" 9 | "testing" 10 | ) 11 | 12 | // actualViolation is a type used in testing, to represent correct violation 13 | // that should be detected. 14 | type actualViolation struct { 15 | SrcLine int 16 | Type linter.Rule 17 | } 18 | 19 | // verifyViolations checks the list of expected Violations with the list of actual Violations. 20 | // Errors found is reported and logged through t. 21 | func verifyViolations(expectedViolations []*linter.Violation, actualViolations []actualViolation) error { 22 | if len(expectedViolations) != len(actualViolations) { 23 | return fmt.Errorf("Number of violations should be %d, but are %d!\n", len(actualViolations), len(expectedViolations)) 24 | } 25 | 26 | for index, expectedMistake := range expectedViolations { 27 | if actualViolations[index].Type != expectedMistake.Type { 28 | return fmt.Errorf("Violation should be of type %s, and not type %s!\n", actualViolations[index].Type, expectedMistake.Type) 29 | } 30 | if actualViolations[index].SrcLine != expectedMistake.SrcLine { 31 | return fmt.Errorf("Violation (%s) should be found on line %d, and not on line %d!\n", actualViolations[index].Type, actualViolations[index].SrcLine, expectedMistake.SrcLine) 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | // Printing from fmt package is not thread safe and should be avoided in production and detected! 38 | // Testing rule: FMT_PRINTING 39 | func TestDetectionOfPrintingFromFmtPackage(t *testing.T) { 40 | expectedViolations, err := linter.DetectViolations("./testcode/fmtprinting") 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | actualViolations := []actualViolation{ 46 | {SrcLine: 11, Type: linter.FMT_PRINTING}, 47 | {SrcLine: 12, Type: linter.FMT_PRINTING}, 48 | {SrcLine: 13, Type: linter.FMT_PRINTING}, 49 | } 50 | 51 | if len(expectedViolations) <= 0 { 52 | t.Fatal("There is no functions containing violations.") 53 | } 54 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 55 | t.Fatal(err) 56 | } 57 | } 58 | 59 | // Testing rule: NEVER_ALLOCATED_MAP_WITH_NEW 60 | // Allocating maps with new returns a nil pointer, therefor one should use make. 61 | func TestDetectionOfAllocatingMapWithNew(t *testing.T) { 62 | expectedViolations, _ := linter.DetectViolations("./testcode/newmap") 63 | actualViolations := []actualViolation{ 64 | {SrcLine: 11, Type: linter.MAP_ALLOCATED_WITH_NEW}, 65 | } 66 | 67 | if len(expectedViolations) <= 0 { 68 | t.Fatal("There is no functions containing violations.") 69 | } 70 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | } 75 | 76 | // Testing rule: RACE_CONDITION 77 | // Races will occur, since multiple Go-routines will share the same counter variable. 78 | func TestDetectionOfRacesInLoopClosures(t *testing.T) { 79 | expectedViolations, err := linter.DetectViolations("./testcode/threadlooping") 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | actualViolations := []actualViolation{ 85 | {SrcLine: 18, Type: linter.RACE_CONDITION}, 86 | } 87 | 88 | if len(expectedViolations) <= 0 { 89 | t.Fatal("There is no functions containing violations.") 90 | } 91 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 92 | t.Fatal(err) 93 | } 94 | } 95 | 96 | // Testing rule: EMPTY_IF_BODY 97 | // Empty if-bodies are unnecessary and ineffective. 98 | func TestDetectionOfEmptyIfBody(t *testing.T) { 99 | expectedViolations, err := linter.DetectViolations("./testcode/emptyifbody") 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | actualViolations := []actualViolation{ 105 | {SrcLine: 11, Type: linter.EMPTY_IF_BODY}, 106 | } 107 | 108 | if len(expectedViolations) <= 0 { 109 | t.Fatal("There is no functions containing violations.") 110 | } 111 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 112 | t.Fatal(err) 113 | } 114 | } 115 | 116 | // Testing rule: EMPTY_ELSE_BODY 117 | // Empty else-bodies are unnecessary and ineffective. 118 | func TestDetectionOfEmptyElseBody(t *testing.T) { 119 | expectedViolations, err := linter.DetectViolations("./testcode/emptyelsebody") 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | actualViolations := []actualViolation{ 125 | {SrcLine: 16, Type: linter.EMPTY_ELSE_BODY}, 126 | } 127 | 128 | if len(expectedViolations) <= 0 { 129 | t.Fatal("There is no functions containing violations.") 130 | } 131 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 132 | t.Fatal(err) 133 | } 134 | } 135 | 136 | // Testing rule: EMPTY_FOR_BODY 137 | // Empty for-bodies are unnecessary and ineffective. 138 | func TestDetectionOfEmptyForBody(t *testing.T) { 139 | expectedViolations, err := linter.DetectViolations("./testcode/emptyforbody") 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | 144 | actualViolations := []actualViolation{ 145 | {SrcLine: 8, Type: linter.EMPTY_FOR_BODY}, 146 | } 147 | 148 | if len(expectedViolations) <= 0 { 149 | t.Fatal("There is no functions containing violations.") 150 | } 151 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 152 | t.Fatal(err) 153 | } 154 | } 155 | 156 | // Testing rule: RETURN_KILLS_CODE 157 | // One should never return unconditionally, except from the last statement in a func or method. 158 | func TestDetectionOfEarlyReturn(t *testing.T) { 159 | expectedViolations, err := linter.DetectViolations("./testcode/earlyreturn") 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | actualViolations := []actualViolation{ 165 | {SrcLine: 13, Type: linter.RETURN_KILLS_CODE}, 166 | } 167 | 168 | if len(expectedViolations) <= 0 { 169 | t.Fatal("There is no functions containing violations.") 170 | } 171 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 172 | t.Fatal(err) 173 | } 174 | } 175 | 176 | // Testing rule: GOTO_USED 177 | // Jumping around in the code using GOTO (including BREAK, CONTINUE, GOTO, FALLTHROUGH) 178 | // is considered confusing and harmful. 179 | func TestDetectionOfGoTo(t *testing.T) { 180 | expectedViolations, err := linter.DetectViolations("./testcode/goto") 181 | if err != nil { 182 | t.Fatal(err) 183 | } 184 | 185 | actualViolations := []actualViolation{ 186 | {SrcLine: 12, Type: linter.GOTO_USED}, 187 | } 188 | 189 | if len(expectedViolations) <= 0 { 190 | t.Fatal("There is no functions containing violations.") 191 | } 192 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 193 | t.Fatal(err) 194 | } 195 | } 196 | 197 | // Testing rule: GOTO_USED 198 | // Jumping around in the code using GOTO (including BREAK, CONTINUE, GOTO, FALLTHROUGH) 199 | // is considered confusing and harmful. 200 | func TestDetectionOfLabeledBranching(t *testing.T) { 201 | expectedViolations, err := linter.DetectViolations("./testcode/labeledbranch") 202 | if err != nil { 203 | t.Fatal(err) 204 | } 205 | 206 | actualViolations := []actualViolation{ 207 | {SrcLine: 15, Type: linter.GOTO_USED}, 208 | } 209 | 210 | if len(expectedViolations) <= 0 { 211 | t.Fatal("There is no functions containing violations.") 212 | } 213 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 214 | t.Fatal(err) 215 | } 216 | } 217 | 218 | // Testing rule: ERROR_IGNORED 219 | // Errors should never be ignored, might lead to program crashes. 220 | func TestDetectionOfIgnoredErrors(t *testing.T) { 221 | expectedViolations, err := linter.DetectViolations("./testcode/errorignored") 222 | if err != nil { 223 | t.Fatal(err) 224 | } 225 | 226 | actualViolations := []actualViolation{ 227 | {SrcLine: 16, Type: linter.ERROR_IGNORED}, 228 | {SrcLine: 17, Type: linter.ERROR_IGNORED}, 229 | {SrcLine: 22, Type: linter.ERROR_IGNORED}, 230 | {SrcLine: 27, Type: linter.ERROR_IGNORED}, 231 | {SrcLine: 35, Type: linter.ERROR_IGNORED}, 232 | {SrcLine: 51, Type: linter.ERROR_IGNORED}, 233 | } 234 | 235 | if len(expectedViolations) <= 0 { 236 | t.Fatal("There is no functions containing violations.") 237 | } 238 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 239 | t.Fatal(err) 240 | } 241 | } 242 | 243 | // Testing rule: CONDITION_EVALUATED_STATICALLY 244 | // Condition that can be evaluated statically are wasted and performance-reducing. 245 | func TestDetectionOfConditionEvaluatedStatically(t *testing.T) { 246 | expectedViolations, err := linter.DetectViolations("./testcode/staticconditions") 247 | if err != nil { 248 | t.Fatal(err) 249 | } 250 | 251 | actualViolations := []actualViolation{ 252 | {SrcLine: 14, Type: linter.CONDITION_EVALUATED_STATICALLY}, 253 | {SrcLine: 18, Type: linter.CONDITION_EVALUATED_STATICALLY}, 254 | {SrcLine: 22, Type: linter.CONDITION_EVALUATED_STATICALLY}, 255 | {SrcLine: 26, Type: linter.CONDITION_EVALUATED_STATICALLY}, 256 | //{SrcLine: 32, Type: linter.CONDITION_EVALUATED_STATICALLY}, TODO: Possible to statically trace a variable value? 257 | {SrcLine: 38, Type: linter.CONDITION_EVALUATED_STATICALLY}, 258 | {SrcLine: 41, Type: linter.CONDITION_EVALUATED_STATICALLY}, 259 | } 260 | 261 | if len(expectedViolations) <= 0 { 262 | t.Fatal("There is no functions containing violations.") 263 | } 264 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 265 | t.Fatal(err) 266 | } 267 | } 268 | 269 | // GitHub Issue #4 list a scenario where the tool detects a false positive of rule ERROR_IGNORED. 270 | // This test verifies correction of the behaviour. 271 | func TestDetectionOfErrorIgnoredInReturn(t *testing.T) { 272 | expectedViolations, err := linter.DetectViolations("./testcode/errorinreturn") 273 | if err != nil { 274 | t.Fatal(err) 275 | } 276 | 277 | actualViolations := []actualViolation{} 278 | 279 | if len(expectedViolations) > 0 { 280 | // Only verify violations if there is more than one file containing violations! 281 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 282 | t.Fatal(err) 283 | } 284 | } 285 | } 286 | 287 | func TestDetectionOfHighCyclomatiComplexity(t *testing.T) { 288 | expectedViolations, err := linter.DetectViolations("./testcode/cyclomaticomplexity") 289 | if err != nil { 290 | t.Fatal(err) 291 | } 292 | 293 | actualViolations := []actualViolation{ 294 | {SrcLine: 13, Type: linter.CYCLOMATIC_COMPLEXITY}, 295 | } 296 | 297 | if len(expectedViolations) > 0 { 298 | // Only verify violations if there is more than one file containing violations! 299 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, actualViolations); err != nil { 300 | t.Fatal(err) 301 | } 302 | } 303 | } 304 | 305 | //TODO: Implement! 306 | /* 307 | // Testing rule: STRING_METHOD_DEFINES_ITSELF 308 | func TestDetectionOfStringMethodDefiningItself(t *testing.T) { 309 | expectedViolations, err := linter.DetectViolations("./testcode/stringmethod") 310 | if err != nil { 311 | t.Fatal(err) 312 | } 313 | 314 | correctViolations := []actualViolation{ 315 | {SrcLine: 48, Type: linter.STRING_CALLS_ITSELF}, 316 | {SrcLine: 58, Type: linter.STRING_CALLS_ITSELF}, 317 | {SrcLine: 63, Type: linter.STRING_CALLS_ITSELF}, 318 | {SrcLine: 69, Type: linter.STRING_CALLS_ITSELF}, 319 | } 320 | 321 | if len(expectedViolations) <= 0 { 322 | t.Fatal("There is no functions containing violations.") 323 | } 324 | if err := verifyViolations(expectedViolations[0].Violations[0].Violations, correctViolations); err != nil { 325 | t.Fatal(err) 326 | } 327 | } 328 | */ 329 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/bufferwriting/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | w := bufio.NewWriter(os.Stdout) 14 | fmt.Fprint(w, "Hello, World") 15 | closeBuffer(w) 16 | } 17 | 18 | func closeBuffer(buf *bufio.Writer) { 19 | buf.Flush() 20 | } 21 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/cyclomaticomplexity/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | month := 10 10 | fmt.Printf("Month %d is %s\n", month, monthNumberToString(month)) 11 | } 12 | 13 | func monthNumberToString(month int) string { 14 | switch month { 15 | case 1: 16 | return "January" 17 | case 2: 18 | return "February" 19 | case 3: 20 | return "March" 21 | case 4: 22 | return "April" 23 | case 5: 24 | return "May" 25 | case 6: 26 | return "June" 27 | case 7: 28 | return "Juni" 29 | case 8: 30 | return "August" 31 | case 9: 32 | return "September" 33 | case 10: 34 | return "October" 35 | case 11: 36 | return "November" 37 | case 12: 38 | return "Desember" 39 | default: 40 | return "Invalid month" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/earlyreturn/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "log" 8 | ) 9 | 10 | func main() { 11 | log.Println("Hello World") 12 | a := "Hei" 13 | return 14 | log.Printf("Finally done, a = %s\n", a) 15 | } 16 | 17 | func swap(a, b interface{}) (c, d interface{}) { 18 | c = b 19 | d = a 20 | return 21 | } 22 | 23 | func getBiggest(a, b float32) float32 { 24 | if a > b { 25 | return a 26 | } 27 | return b 28 | } 29 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/emptyelsebody/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "log" 8 | "math/rand" 9 | ) 10 | 11 | func main() { 12 | randomInt := rand.Intn(100) 13 | 14 | if randomInt == 50 { 15 | log.Print("50") 16 | } else { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/emptyforbody/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | func main() { 7 | 8 | for i := 0; i < 10; i++ { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/emptyifbody/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "math/rand" 7 | 8 | func main() { 9 | randomInt := rand.Intn(100) 10 | 11 | if randomInt == 50 { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/errorignored/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "log" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | logger := log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) 14 | 15 | // Returning error should be checked with error != nil 16 | file, _ := os.Open("file.go") 17 | defer file.Close() 18 | 19 | logger.Println(file) 20 | 21 | // Returning error should not be ignored with _ 22 | file2, _ := OpenFile("log.txt") 23 | 24 | logger.Println(file2) 25 | 26 | // Error returned here should be handled! 27 | writeToConsole("Hello console") 28 | 29 | err := writeToConsole("Hello console") 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | // Should also detect the error not detected here! 35 | os.Open("file.go") 36 | } 37 | 38 | func OpenFile(filePath string) (*os.File, error) { 39 | f, err := os.Open(filePath) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return f, nil 44 | } 45 | 46 | func writeToConsole(line string) error { 47 | w := bufio.NewWriter(os.Stdout) 48 | if _, err := w.WriteString(line); err != nil { 49 | return err 50 | } 51 | w.Flush() 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/errorinreturn/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "log" 9 | ) 10 | 11 | func main() { 12 | result, err := divide(2, 2) 13 | if err != nil { 14 | log.Println(err) 15 | } 16 | log.Printf("Result: %f\n", result) 17 | } 18 | 19 | // GitHub issue #4 concerns with false positives detected of ERROR_IGNORED 20 | // on line 23. 21 | func divide(x, y float32) (float32, error) { 22 | if x == 0.0 || y == 0.0 { 23 | return -1, errors.New("Zero divison is not smart!") 24 | } 25 | return x / y, nil 26 | } 27 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/fmtprinting/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import "fmt" 7 | 8 | // Unit-test, ignore other errors then what we are testing. 9 | // @SuppressRule("ERROR_IGNORED") 10 | func main() { 11 | fmt.Print("Printing with fmt.Print()") 12 | fmt.Printf("Printing with fmt.Printf()") 13 | fmt.Println("Printing with fmt.Println()") 14 | } 15 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/goto/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | func main() { 7 | counter := 0 8 | 9 | LOOP: 10 | if 100 > counter { 11 | counter++ 12 | goto LOOP 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/labeledbranch/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "log" 8 | ) 9 | 10 | func main() { 11 | LABEL1: 12 | for i := 0; i <= 5; i++ { 13 | for j := 0; j <= 5; j++ { 14 | if j == 4 { 15 | continue LABEL1 16 | } 17 | log.Printf("i is: %d, and j is: %d\n", i, j) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/newmap/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "log" 8 | ) 9 | 10 | func main() { 11 | myMap := new(map[string]float64) 12 | log.Println(*myMap) 13 | } 14 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/staticconditions/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "log" 9 | "math/rand" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | if true { 15 | log.Println("Always true") 16 | } 17 | 18 | if false { 19 | log.Println("Should never be printed") 20 | } 21 | 22 | if 1 >= 2 { 23 | log.Println("Not possible") 24 | } 25 | 26 | if 2 >= 1 && 1 >= 0 || 0 >= 0 { 27 | log.Println("Possible") 28 | } 29 | 30 | aString := "a" 31 | bString := "b" 32 | 33 | if aString == bString { 34 | log.Println("This should never be printed") 35 | } 36 | 37 | var buf bytes.Buffer 38 | if buf.Bytes == nil { 39 | // Not that one is comparing function, not function result, () missed. 40 | log.Println("This is alway false") 41 | } else if true { 42 | log.Println("Might be printed") 43 | } 44 | 45 | if 1 == rand.Intn(10) { 46 | log.Println("Result is 1") 47 | } 48 | 49 | var Line interface{} = "This is a line" 50 | if value, ok := Line.(string); ok { 51 | // Should not be flagged as static condition. 52 | log.Printf("Value contains: %v\n", value) 53 | } 54 | 55 | result := os.Args[0] == "main.go" 56 | if result { 57 | // Should not be flagged as static condition. 58 | log.Println("Arg[0] = main.go") 59 | } 60 | 61 | } 62 | 63 | func GetBiggest(a, b float32) float32 { 64 | if a > b { 65 | return a 66 | } 67 | return b 68 | } 69 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/stringmethod/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "log" 10 | ) 11 | 12 | // Trivial type to hold address. 13 | type Address struct { 14 | Street, City string 15 | } 16 | 17 | type Person struct { 18 | FirstName, LastName string 19 | } 20 | 21 | type T1 string 22 | type T2 string 23 | type T3 string 24 | 25 | var logger *log.Logger 26 | 27 | func main() { 28 | myAddress := Address{ 29 | Street: "Trimveien 6", 30 | City: "Oslo", 31 | } 32 | log.Println(myAddress) 33 | 34 | me := Person{ 35 | FirstName: "Christian", 36 | LastName: "Bergum Bergersen", 37 | } 38 | log.Println(me) 39 | 40 | var foo T1 41 | foo = "foo" 42 | log.Println(foo) 43 | 44 | var bar T3 45 | bar = "bar" 46 | log.Println(bar) 47 | } 48 | 49 | // Calls itself. 50 | func (address Address) String() string { 51 | return fmt.Sprintf("%s", address) 52 | } 53 | 54 | // Correct. 55 | func (person Person) String() string { 56 | return fmt.Sprintf("%s %s", person.FirstName, person.LastName) 57 | } 58 | 59 | // Calls itself. 60 | func (t1 T1) String() string { 61 | return fmt.Sprint(t1) 62 | } 63 | 64 | // Calls itself. 65 | func (t2 T2) String() string { 66 | log.Print("Calling String() for : %v", t2) 67 | return fmt.Sprintln(t2) 68 | } 69 | 70 | // Calls itself. 71 | func (bar T3) String() string { 72 | var buf bytes.Buffer 73 | logger = log.New(&buf, "logger: ", log.Lshortfile) 74 | logger.Printf("Calling String() for %+v", bar) 75 | return fmt.Sprintf("Bar") 76 | } 77 | -------------------------------------------------------------------------------- /analyzer/linter/testcode/threadlooping/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can 3 | // be found in the LICENSE file. 4 | package main 5 | 6 | import ( 7 | "log" 8 | ) 9 | 10 | // This small piece of code illustrates 11 | // one of the common mistakes new Go 12 | // people do when using Goroutines inside 13 | // for-loops, referencing the loop-variable. 14 | // 15 | func main() { 16 | //Not-thread safe. 17 | for num := 0; num < 5; num++ { 18 | go func() { 19 | log.Printf("Goroutine #%d\n", num) 20 | }() 21 | } 22 | 23 | //Safe loop, no Go routine! 24 | for val := 0; val < 50; val++ { 25 | log.Println(val) 26 | } 27 | 28 | //Thread safe loop. 29 | for val := 0; val < 5; val++ { 30 | go func(val int) { 31 | log.Println(val) 32 | }(val) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /analyzer/make.bat: -------------------------------------------------------------------------------- 1 | :: Copyright (c) 2015-2016 The GoAnalysis Authors. All rights reserved. 2 | :: Use of this source code is governed by a BSD-style license that can 3 | :: be found in the LICENSE file. 4 | 5 | echo ** Building linux/amd64 ** 6 | set GOARCH=amd64 7 | set GOOS=linux 8 | go build -o build/amd64/GoAnalyzerLinux GoAnalyzer.go 9 | 10 | echo ** Building darwin/amd64 ** 11 | set GOARCH=amd64 12 | set GOOS=darwin 13 | go build -o build/amd64/GoAnalyzerMac GoAnalyzer.go 14 | 15 | echo ** Building windows/amd64 ** 16 | set GOARCH=amd64 17 | set GOOS=windows 18 | go build -o build/amd64/GoAnalyzerWindows.exe GoAnalyzer.go 19 | -------------------------------------------------------------------------------- /sonar-go-plugin/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | This is the general PMD ruleset for the Go SonarQube plugin. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /sonar-go-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.sonarsource.plugins.golang 7 | sonar-go-plugin 8 | sonar-plugin 9 | 0.1-SNAPSHOT 10 | 11 | Go Plugin for SonarQube 12 | The Go Programming Language Plugin for SonarQube 13 | 2016 14 | https://github.com/chrisbbe/GoAnalysis/tree/master/sonar-go-plugin 15 | 16 | GitHub 17 | https://github.com/chrisbbe/GoAnalysis/issues 18 | 19 | 20 | 21 | chrisbbe 22 | Christian Bergum Bergersen 23 | chrisbbe@ifi.uio.no 24 | http://www.bergersenweb.com 25 | Department of Informatics, University of Oslo 26 | http://www.mn.uio.no/ifi/english/ 27 | 28 | 29 | 30 | https://github.com/chrisbbe/GoAnalysis/tree/master/sonar-go-plugin 31 | https://github.com/chrisbbe/GoAnalysis/tree/master/sonar-go-plugin 32 | 33 | 34 | 35 | UTF-8 36 | 5.6 37 | 1.8 38 | . 39 | ${pmdRuleSetDir}/pmd-ruleset.xml 40 | 41 | 42 | 43 | 44 | org.sonarsource.sonarqube 45 | sonar-plugin-api 46 | ${sonar.apiVersion} 47 | provided 48 | 49 | 50 | 51 | commons-lang 52 | commons-lang 53 | 2.6 54 | 55 | 56 | com.google.code.gson 57 | gson 58 | 2.7 59 | 60 | 61 | 62 | 63 | org.sonarsource.sonarqube 64 | sonar-testing-harness 65 | ${sonar.apiVersion} 66 | test 67 | 68 | 69 | junit 70 | junit 71 | 4.11 72 | test 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.sonarsource.sonar-packaging-maven-plugin 80 | sonar-packaging-maven-plugin 81 | 1.16 82 | true 83 | 84 | org.sonarsource.plugins.go.GoPlugin 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-compiler-plugin 90 | 3.5.1 91 | 92 | ${jdk.min.version} 93 | ${jdk.min.version} 94 | 95 | 96 | 97 | 98 | org.codehaus.mojo 99 | native2ascii-maven-plugin 100 | 1.0-beta-1 101 | 102 | 103 | 104 | native2ascii 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-pmd-plugin 112 | 3.6 113 | 114 | ${jdk.min.version} 115 | true 116 | 117 | ${pmdRuleSet} 118 | 119 | 120 | 121 | 122 | 123 | validate 124 | 125 | check 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-jxr-plugin 138 | 2.5 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoAnalyzer/ExecuteGoAnalyzer.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.GoAnalyzer; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.reflect.TypeToken; 5 | import org.sonar.api.utils.log.Logger; 6 | import org.sonar.api.utils.log.Loggers; 7 | 8 | import java.io.File; 9 | import java.lang.reflect.Type; 10 | import java.net.URI; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | 16 | public class ExecuteGoAnalyzer { 17 | private static final Logger LOGGER = Loggers.get(ExecuteGoAnalyzer.class); 18 | 19 | private final HashMap analyzers; 20 | 21 | public ExecuteGoAnalyzer() { 22 | this.analyzers = new HashMap<>(); 23 | this.analyzers.put("Linux", "analyzer/GoAnalyzerLinux"); 24 | this.analyzers.put("Mac", "analyzer/GoAnalyzerMac"); 25 | this.analyzers.put("Windows", "analyzer/GoAnalyzerWindows.exe"); 26 | } 27 | 28 | public List runAnalyzer(final String filePath) { 29 | LOGGER.info("Starting runAnalyzer"); 30 | JarExtractor jarExtractor = new JarExtractor(); 31 | Gson gson = new Gson(); 32 | LOGGER.info("Done creating jar and gson objects"); 33 | 34 | List goFileViolations = null; 35 | 36 | String operatingSystem = System.getProperty("os.name"); 37 | String architecture = System.getProperty("os.arch"); 38 | LOGGER.info("OS: " + operatingSystem); 39 | LOGGER.info("Architecture: " + architecture); 40 | 41 | String analyzerFile = this.analyzers.get(operatingSystem.split(" ")[0]); 42 | if (analyzerFile == null) { 43 | LOGGER.error("OS " + operatingSystem + " is not supported! Exiting!"); 44 | System.exit(1); //TODO: Should maybe throw an exception or something, not nice! 45 | } 46 | 47 | LOGGER.info("AnalyzerFile: " + analyzerFile); 48 | try { 49 | final URI exe = jarExtractor.extractFileFromJar(analyzerFile); 50 | 51 | String[] command = new String[4]; 52 | command[0] = exe.getPath(); 53 | command[1] = "-json"; // We want JSON output. 54 | command[2] = "-dir"; 55 | command[3] = filePath; 56 | 57 | File workingDir = new File(exe.getPath()).getParentFile(); 58 | ProcessExec processExec = new ProcessExec(); 59 | LOGGER.info("Before executing process"); 60 | LOGGER.info("Command: " + Arrays.toString(command)); 61 | processExec.executeProcess(command, workingDir.getPath()); 62 | LOGGER.info("After executing process"); 63 | 64 | LOGGER.info("Error output: " + processExec.getErrorOutput()); 65 | if (processExec.getErrorOutput().length() > 0) { 66 | LOGGER.info("###################################### GO ANALYZER ERRORS ######################################"); 67 | LOGGER.info(processExec.getErrorOutput()); 68 | System.exit(1); //TODO: Should maybe throw an exception or something, not nice! 69 | } 70 | 71 | Type violationType = new TypeToken>() { 72 | }.getType(); 73 | LOGGER.info("Before converting JSON to objects"); 74 | goFileViolations = gson.fromJson(processExec.getOutput(), violationType); 75 | LOGGER.info("After converting JSON to objects: Number of GoFiles: " + goFileViolations.size()); 76 | 77 | } catch (Exception e) { 78 | LOGGER.info(e.toString()); 79 | } 80 | return goFileViolations; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoAnalyzer/GoFileViolation.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.GoAnalyzer; 2 | 3 | // This class maps JSON from the external GoAnalyzer tool into Java objects, 4 | // that's the reason for capital variables as they correspond against JSON variables. 5 | @SuppressWarnings("PMD.VariableNamingConventions") 6 | public class GoFileViolation { 7 | private String FilePath; 8 | private int LinesOfCode; 9 | private int LinesOfComments; 10 | private Violation[] Violations; 11 | 12 | public String getFilePath() { 13 | return FilePath; 14 | } 15 | 16 | public int getLinesOfCode() { 17 | return LinesOfCode; 18 | } 19 | 20 | public int getLinesOfComments() { 21 | return LinesOfComments; 22 | } 23 | 24 | public Violation[] getViolations() { 25 | return Violations; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoAnalyzer/JarExtractor.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.GoAnalyzer; 2 | 3 | import org.sonarsource.plugins.go.GoPlugin; 4 | 5 | import java.io.*; 6 | import java.net.URI; 7 | import java.net.URISyntaxException; 8 | import java.net.URL; 9 | import java.security.CodeSource; 10 | import java.security.ProtectionDomain; 11 | import java.util.zip.ZipEntry; 12 | import java.util.zip.ZipFile; 13 | 14 | class JarExtractor { 15 | 16 | private static URI getJarURI() throws URISyntaxException { 17 | final ProtectionDomain domain; 18 | final CodeSource source; 19 | final URL url; 20 | final URI uri; 21 | 22 | domain = GoPlugin.class.getProtectionDomain(); 23 | source = domain.getCodeSource(); 24 | url = source.getLocation(); 25 | uri = url.toURI(); 26 | 27 | return uri; 28 | } 29 | 30 | private static URI getFile(final URI where, final String fileName) throws IOException { 31 | final File location = new File(where); 32 | final URI fileURI; 33 | 34 | // not in a JAR, just return the path on disk 35 | if (location.isDirectory()) { 36 | fileURI = URI.create(where.toString() + fileName); 37 | } else { 38 | final ZipFile zipFile; 39 | 40 | zipFile = new ZipFile(location); 41 | 42 | try { 43 | fileURI = extract(zipFile, fileName); 44 | } finally { 45 | zipFile.close(); 46 | } 47 | } 48 | 49 | return fileURI; 50 | } 51 | 52 | private static URI extract(final ZipFile zipFile, final String fileName) throws IOException { 53 | final File tempFile; 54 | final ZipEntry entry; 55 | final InputStream zipStream; 56 | OutputStream fileStream; 57 | 58 | tempFile = File.createTempFile(fileName, Long.toString(System.currentTimeMillis())); 59 | tempFile.deleteOnExit(); 60 | entry = zipFile.getEntry(fileName); 61 | 62 | if (entry == null) { 63 | throw new FileNotFoundException("cannot find file: " + fileName + " in archive: " + zipFile.getName()); 64 | } 65 | 66 | zipStream = zipFile.getInputStream(entry); 67 | fileStream = null; 68 | 69 | try { 70 | final byte[] buf; 71 | int i; 72 | 73 | fileStream = new FileOutputStream(tempFile); 74 | buf = new byte[1024]; 75 | 76 | while ((i = zipStream.read(buf)) != -1) { 77 | fileStream.write(buf, 0, i); 78 | } 79 | 80 | tempFile.setReadable(true); 81 | tempFile.setExecutable(true); 82 | 83 | } finally { 84 | zipStream.close(); 85 | if (fileStream != null) { 86 | fileStream.close(); 87 | } 88 | } 89 | 90 | return tempFile.toURI(); 91 | } 92 | 93 | public URI extractFileFromJar(final String fileName) throws URISyntaxException, IOException { 94 | return getFile(getJarURI(), fileName); 95 | } 96 | 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoAnalyzer/ProcessExec.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.GoAnalyzer; 2 | 3 | import org.sonar.api.utils.log.Logger; 4 | import org.sonar.api.utils.log.Loggers; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.util.Scanner; 11 | import java.util.concurrent.BrokenBarrierException; 12 | import java.util.concurrent.CyclicBarrier; 13 | 14 | class ProcessExec { 15 | private static final Logger LOGGER = Loggers.get(ProcessExec.class); 16 | 17 | private String output; 18 | private String errorOutput; 19 | 20 | void executeProcess(String[] command, String workingDir) throws IOException, InterruptedException, BrokenBarrierException { 21 | ProcessBuilder processBuilder = new ProcessBuilder(command); 22 | 23 | processBuilder.directory(new File(workingDir)); 24 | Process process = processBuilder.start(); 25 | 26 | CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // Barrier to wait for IO to be consumed. 27 | 28 | IOThreadHandler outputHandler = new IOThreadHandler(process.getInputStream(), cyclicBarrier); 29 | IOThreadHandler errorHandler = new IOThreadHandler(process.getErrorStream(), cyclicBarrier); 30 | outputHandler.start(); 31 | errorHandler.start(); 32 | 33 | LOGGER.info("Before process.waitFor()"); 34 | LOGGER.info("GoAnalyzer performs inspection. Please wait..."); 35 | process.waitFor(); 36 | LOGGER.info("After process.waitFor()"); 37 | 38 | LOGGER.info("Before cyclicBarrier.await()"); 39 | cyclicBarrier.await(); // Output and errorOutput should be filled! 40 | LOGGER.info("After cyclicBarrier.await()"); 41 | 42 | LOGGER.info("Before output"); 43 | this.output = outputHandler.getOutput(); 44 | this.errorOutput = errorHandler.getOutput(); 45 | LOGGER.info("After output"); 46 | } 47 | 48 | String getOutput() { 49 | return output; 50 | } 51 | 52 | public String getErrorOutput() { 53 | return errorOutput; 54 | } 55 | 56 | private class IOThreadHandler extends Thread { 57 | private final InputStream inputStream; 58 | private final CyclicBarrier cyclicBarrier; 59 | private String output; 60 | 61 | IOThreadHandler(InputStream inputStream, CyclicBarrier cyclicBarrier) { 62 | this.inputStream = inputStream; 63 | this.cyclicBarrier = cyclicBarrier; 64 | } 65 | 66 | @Override 67 | public void run() { 68 | StringBuilder stringBuilder = new StringBuilder(); 69 | 70 | try (Scanner br = new Scanner(new InputStreamReader(inputStream))) { 71 | String line; 72 | while (br.hasNextLine()) { 73 | line = br.nextLine(); 74 | stringBuilder.append(line); 75 | } 76 | } finally { 77 | try { 78 | output = stringBuilder.toString(); 79 | LOGGER.info("Thread " + this.getId() + " done!"); 80 | this.cyclicBarrier.await(); 81 | } catch (Exception e) { 82 | LOGGER.info(e.toString()); 83 | } 84 | } 85 | 86 | } 87 | 88 | String getOutput() { 89 | return output; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoAnalyzer/Violation.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.GoAnalyzer; 2 | 3 | // This class maps JSON from the external GoAnalyzer tool into Java objects, 4 | // that's the reason for capital variables as they correspond against JSON variables. 5 | @SuppressWarnings("PMD.VariableNamingConventions") 6 | public class Violation { 7 | private String Type; 8 | private String Description; 9 | private int SrcLine; 10 | 11 | public String getType() { 12 | return Type; 13 | } 14 | 15 | public void setType(String type) { 16 | Type = type; 17 | } 18 | 19 | public String getDescription() { 20 | return Description; 21 | } 22 | 23 | public void setDescription(String description) { 24 | Description = description; 25 | } 26 | 27 | public int getSrcLine() { 28 | return SrcLine; 29 | } 30 | 31 | public void setSrcLine(int srcLine) { 32 | SrcLine = srcLine; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/GoPlugin.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go; 2 | 3 | import org.sonar.api.Plugin; 4 | import org.sonarsource.plugins.go.languages.GoLanguage; 5 | import org.sonarsource.plugins.go.languages.GoQualityProfile; 6 | import org.sonarsource.plugins.go.rules.GoIssuesLoaderSensor; 7 | import org.sonarsource.plugins.go.rules.GoRulesDefinition; 8 | 9 | /** 10 | * This class is the entry point for all extensions. It is referenced in pom.xml. 11 | */ 12 | public class GoPlugin implements Plugin { 13 | 14 | @Override 15 | public void define(Context context) { 16 | // Language. 17 | context.addExtensions(GoLanguage.class, GoQualityProfile.class); 18 | 19 | // Rules. 20 | context.addExtensions(GoRulesDefinition.class, GoIssuesLoaderSensor.class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/languages/GoLanguage.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.languages; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.sonar.api.config.Settings; 5 | import org.sonar.api.resources.AbstractLanguage; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * This class defines the fictive Go language. 12 | */ 13 | public final class GoLanguage extends AbstractLanguage { 14 | public static final String KEY = "go"; 15 | private static final String NAME = "Go"; 16 | private static final String FILE_SUFFIXES_PROPERTY_KEY = "sonar.go.file.suffixes"; 17 | private static final String DEFAULT_FILE_SUFFIXES = "go"; 18 | 19 | private final Settings settings; 20 | 21 | public GoLanguage(Settings settings) { 22 | super(KEY, NAME); 23 | this.settings = settings; 24 | } 25 | 26 | @Override 27 | public String[] getFileSuffixes() { 28 | String[] suffixes = filterEmptyStrings(settings.getStringArray(FILE_SUFFIXES_PROPERTY_KEY)); 29 | if (suffixes.length == 0) { 30 | suffixes = StringUtils.split(DEFAULT_FILE_SUFFIXES, ","); 31 | } 32 | return suffixes; 33 | } 34 | 35 | private String[] filterEmptyStrings(String[] stringArray) { 36 | List nonEmptyStrings = new ArrayList<>(); 37 | for (String string : stringArray) { 38 | if (StringUtils.isNotBlank(string.trim())) { 39 | nonEmptyStrings.add(string.trim()); 40 | } 41 | } 42 | return nonEmptyStrings.toArray(new String[nonEmptyStrings.size()]); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/languages/GoQualityProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SonarQube Protocol Buffers Plugin 3 | * Copyright (C) 2015 SonarSource 4 | * sonarqube@googlegroups.com 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package org.sonarsource.plugins.go.languages; 21 | 22 | import org.sonar.api.profiles.ProfileDefinition; 23 | import org.sonar.api.profiles.RulesProfile; 24 | import org.sonar.api.rules.Rule; 25 | import org.sonar.api.rules.RuleFinder; 26 | import org.sonar.api.rules.RuleQuery; 27 | import org.sonar.api.utils.ValidationMessages; 28 | import org.sonarsource.plugins.go.rules.GoRulesDefinition; 29 | 30 | /** 31 | * Default Quality profile for the projects having files of language "go" 32 | */ 33 | public final class GoQualityProfile extends ProfileDefinition { 34 | private static final String REPOSITORY_KEY = GoRulesDefinition.getRepositoryKeyForLanguage(GoLanguage.KEY); 35 | 36 | private final RuleFinder ruleFinder; 37 | 38 | public GoQualityProfile(RuleFinder ruleFinder) { 39 | this.ruleFinder = ruleFinder; 40 | } 41 | 42 | @Override 43 | public RulesProfile createProfile(ValidationMessages validation) { 44 | RulesProfile ruleProfile = RulesProfile.create("Go Rules", GoLanguage.KEY); 45 | 46 | for (Rule rule : ruleFinder.findAll(RuleQuery.create().withRepositoryKey(REPOSITORY_KEY))) { 47 | ruleProfile.activateRule(rule, null); 48 | } 49 | return ruleProfile; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/rules/GoIssue.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.rules; 2 | 3 | 4 | class GoIssue { 5 | private final String type; 6 | private final String description; 7 | private final String filePath; 8 | private final int line; 9 | 10 | // String type is actual, externalRuleKey. 11 | public GoIssue(final String type, final String description, final String filePath, final int line) { 12 | this.type = type; 13 | this.description = description; 14 | this.filePath = filePath; 15 | this.line = line; 16 | } 17 | 18 | public String getType() { 19 | return type; 20 | } 21 | 22 | public String getDescription() { 23 | return description; 24 | } 25 | 26 | public String getFilePath() { 27 | return filePath; 28 | } 29 | 30 | public int getLine() { 31 | return line; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return type + 37 | "|" + 38 | description + 39 | "|" + 40 | filePath + 41 | "(" + 42 | line + 43 | ")"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/rules/GoIssuesLoaderSensor.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.rules; 2 | 3 | import org.sonar.api.batch.fs.FileSystem; 4 | import org.sonar.api.batch.fs.InputFile; 5 | import org.sonar.api.batch.sensor.Sensor; 6 | import org.sonar.api.batch.sensor.SensorContext; 7 | import org.sonar.api.batch.sensor.SensorDescriptor; 8 | import org.sonar.api.component.ResourcePerspectives; 9 | import org.sonar.api.issue.Issuable; 10 | import org.sonar.api.issue.Issue; 11 | import org.sonar.api.measures.CoreMetrics; 12 | import org.sonar.api.measures.Metric; 13 | import org.sonar.api.rule.RuleKey; 14 | import org.sonar.api.utils.log.Logger; 15 | import org.sonar.api.utils.log.Loggers; 16 | import org.sonarsource.plugins.go.GoAnalyzer.ExecuteGoAnalyzer; 17 | import org.sonarsource.plugins.go.GoAnalyzer.GoFileViolation; 18 | import org.sonarsource.plugins.go.GoAnalyzer.Violation; 19 | import org.sonarsource.plugins.go.languages.GoLanguage; 20 | 21 | import java.io.Serializable; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | 25 | import static java.lang.String.format; 26 | 27 | /** 28 | * The goal of this Sensor is to load the results of an analysis performed by a fictive external tool named: FooLint 29 | * Results are provided as an xml file and are corresponding to the rules defined in 'rules.xml'. 30 | * To be very abstract, these rules are applied on source files made with the fictive language Foo. 31 | */ 32 | public class GoIssuesLoaderSensor implements Sensor { 33 | private static final Logger LOGGER = Loggers.get(GoIssuesLoaderSensor.class); 34 | private static final String REPORT_PATH_KEY = "sonar.go.reportPath"; 35 | private final FileSystem fileSystem; 36 | private final ResourcePerspectives perspectives; 37 | private SensorContext sensorContext; 38 | 39 | /** 40 | * Use of IoC to get Settings, FileSystem, RuleFinder and ResourcePerspectives 41 | */ 42 | public GoIssuesLoaderSensor(final FileSystem fileSystem, final ResourcePerspectives perspectives) { 43 | this.fileSystem = fileSystem; 44 | this.perspectives = perspectives; 45 | } 46 | 47 | protected String reportPathKey() { 48 | return REPORT_PATH_KEY; 49 | } 50 | 51 | @SuppressWarnings("PMD.ConfusingTernary") 52 | private boolean saveIssue(InputFile inputFile, int line, String externalRuleKey, String message) { 53 | RuleKey rule = RuleKey.of(GoRulesDefinition.getRepositoryKeyForLanguage(inputFile.language()), externalRuleKey); 54 | 55 | Issuable issuable = perspectives.as(Issuable.class, inputFile); 56 | boolean result = false; 57 | if (issuable != null) { 58 | LOGGER.debug("Issuable is not null: %s", issuable.toString()); 59 | Issuable.IssueBuilder issueBuilder = issuable.newIssueBuilder() 60 | .ruleKey(rule) 61 | .message(message); 62 | if (line > 0) { 63 | LOGGER.debug("line is > 0"); 64 | issueBuilder = issueBuilder.line(line); 65 | } 66 | Issue issue = issueBuilder.build(); 67 | LOGGER.debug("issue == null? " + (issue == null)); 68 | try { 69 | result = issuable.addIssue(issue); 70 | LOGGER.debug("after addIssue: result={}", result); 71 | } catch (org.sonar.api.utils.MessageException me) { 72 | LOGGER.error(format("Can't add issue on file %s at line %d.", inputFile.absolutePath(), line), me); 73 | } 74 | 75 | } else { 76 | LOGGER.debug("Can't find an Issuable corresponding to InputFile:" + inputFile.absolutePath()); 77 | } 78 | return result; 79 | } 80 | 81 | @Override 82 | public void describe(final SensorDescriptor descriptor) { 83 | descriptor.name("GoLang Issues Loader Sensor"); 84 | descriptor.onlyOnLanguage(GoLanguage.KEY); 85 | } 86 | 87 | @Override 88 | public void execute(final SensorContext sensorContext) { 89 | this.sensorContext = sensorContext; 90 | ExecuteGoAnalyzer executeGoAnalyzer = new ExecuteGoAnalyzer(); 91 | Iterable goFiles = sensorContext.fileSystem().inputFiles(fileSystem.predicates().hasLanguage("go")); 92 | 93 | HashMap hashMap = new HashMap<>(); 94 | for (InputFile goFile : goFiles) { 95 | hashMap.put(goFile.path().toString(), goFile); 96 | } 97 | 98 | LOGGER.info("Started external GoAnalyzer... :)"); 99 | LOGGER.info("BaseDir: " + fileSystem.baseDir().getPath()); 100 | // Execute the external tool. 101 | List goFileViolationList = executeGoAnalyzer.runAnalyzer(fileSystem.baseDir().getPath()); 102 | LOGGER.info("Got number of files: " + goFileViolationList.size()); 103 | 104 | for (GoFileViolation goFileViolation : goFileViolationList) { 105 | InputFile goInputFile = hashMap.get(goFileViolation.getFilePath()); 106 | if (goInputFile != null) { 107 | 108 | // Save measure metrics. 109 | saveMetricOnFile(goInputFile, CoreMetrics.NCLOC, goFileViolation.getLinesOfCode()); // Lines of code. 110 | 111 | // Save issues. 112 | for (Violation violation : goFileViolation.getViolations()) { 113 | boolean result = saveIssue( 114 | goInputFile, 115 | violation.getSrcLine(), 116 | violation.getType(), 117 | violation.getDescription() 118 | ); 119 | LOGGER.debug("Issues: " + violation.getType() + " - Saved: " + result); 120 | } 121 | } 122 | } 123 | LOGGER.info("Number of Go files processed: " + hashMap.size()); 124 | } 125 | 126 | private void saveMetricOnFile(InputFile inputFile, Metric metric, T value) { 127 | sensorContext.newMeasure() 128 | .withValue(value) 129 | .forMetric(metric) 130 | .on(inputFile) 131 | .save(); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/java/org/sonarsource/plugins/go/rules/GoRulesDefinition.java: -------------------------------------------------------------------------------- 1 | package org.sonarsource.plugins.go.rules; 2 | 3 | import org.sonar.api.server.rule.RulesDefinition; 4 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 5 | import org.sonarsource.plugins.go.languages.GoLanguage; 6 | 7 | import java.io.InputStream; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.Locale; 10 | 11 | public final class GoRulesDefinition implements RulesDefinition { 12 | private static final String KEY = "go"; 13 | private static final String NAME = "Go"; 14 | 15 | public static String getRepositoryKeyForLanguage(String languageKey) { 16 | return languageKey.toLowerCase(Locale.ENGLISH) + "-" + KEY; 17 | } 18 | 19 | private static String getRepositoryNameForLanguage(String languageKey) { 20 | return languageKey.toUpperCase(Locale.ENGLISH) + " " + NAME; 21 | } 22 | 23 | private String rulesDefinitionFilePath() { 24 | return "/ruleset/go-rules.xml"; 25 | } 26 | 27 | private void defineRulesForLanguage(Context context, String repositoryKey, String repositoryName, String languageKey) { 28 | NewRepository repository = context.createRepository(repositoryKey, languageKey).setName(repositoryName); 29 | 30 | InputStream rulesXml = this.getClass().getResourceAsStream(rulesDefinitionFilePath()); 31 | if (rulesXml != null) { 32 | RulesDefinitionXmlLoader rulesLoader = new RulesDefinitionXmlLoader(); 33 | rulesLoader.load(repository, rulesXml, StandardCharsets.UTF_8.name()); 34 | } 35 | 36 | repository.done(); 37 | } 38 | 39 | @Override 40 | public void define(Context context) { 41 | String repositoryKey = GoRulesDefinition.getRepositoryKeyForLanguage(GoLanguage.KEY); 42 | String repositoryName = GoRulesDefinition.getRepositoryNameForLanguage(GoLanguage.KEY); 43 | defineRulesForLanguage(context, repositoryKey, repositoryName, GoLanguage.KEY); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/resources/analyzer/GoAnalyzerMac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbbe/GoAnalysis/433e7636b9a6a6a09541d263e2df46bb2a0309c7/sonar-go-plugin/src/main/resources/analyzer/GoAnalyzerMac -------------------------------------------------------------------------------- /sonar-go-plugin/src/main/resources/ruleset/go-rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CYCLOMATIC_COMPLEXITY 4 | Methods and functions should not be too complex 5 | CYCLOMATIC_COMPLEXITY 6 | The cyclomatic complexity of methods should not exceed a defined threshold. 7 | Complex code can perform poorly and will in any case be difficult to understand and therefore to maintain. 8 | 9 | MAJOR 10 | SINGLE 11 | READY 12 | brain-overload 13 | 14 | 15 | FMT_PRINTING 16 | Printing from the fmt package. 17 | FMT_PRINTING 18 | Printing directly to stdout is not synchronized among Goroutines and might clog the 19 | standard output 20 | 21 | INFO 22 | SINGLE 23 | READY 24 | bad-practice 25 | 26 | 27 | MAP_ALLOCATED_WITH_NEW 28 | Map allocated with new() 29 | MAP_ALLOCATED_WITH_NEW 30 | Maps are allocated, not initialized with new(). Use make() to allocate and initialize 31 | the map. 32 | 33 | CRITICAL 34 | SINGLE 35 | READY 36 | bug 37 | 38 | 39 | EMPTY_IF_BODY 40 | If statement has empty body. 41 | EMPTY_IF_BODY 42 | An empty If statement body does nothing. 43 | MINOR 44 | SINGLE 45 | READY 46 | bad-practice 47 | 48 | 49 | EMPTY_ELSE_BODY 50 | Else statement has empty body. 51 | EMPTY_ELSE_BODY 52 | An empty Else statement body does nothing. 53 | MINOR 54 | SINGLE 55 | READY 56 | bad-practice 57 | 58 | 59 | EMPTY_FOR_BODY 60 | For statement has empty body. 61 | EMPTY_FOR_BODY 62 | An empty For statement body does nothing. 63 | MINOR 64 | SINGLE 65 | READY 66 | bad-practice 67 | 68 | 69 | GOTO_USED 70 | For statement has empty body. 71 | GOTO_USED 72 | Usage of GOTO statements might lead to spaghetti code. 73 | MAJOR 74 | SINGLE 75 | READY 76 | bad-practice 77 | 78 | 79 | RACE_CONDITION 80 | Goroutines on loop iterator variables creates races 81 | RACE_CONDITION 82 | Goroutines on loop iterator variables creates races. 83 | BLOCKER 84 | SINGLE 85 | READY 86 | bug 87 | 88 | 89 | RETURN_KILLS_CODE 90 | . 91 | RETURN_KILLS_CODE 92 | Goroutines on loop iterator variables creates races. 93 | MINOR 94 | SINGLE 95 | READY 96 | bad-practice 97 | 98 | 99 | ERROR_IGNORED 100 | Error ignored 101 | ERROR_IGNORED 102 | Never ignore errors, ingoring them can lead to program crashes! 103 | MINOR 104 | SINGLE 105 | READY 106 | bad-practice 107 | 108 | 109 | STRING_CALLS_ITSELF 110 | String() method calls itself. 111 | STRING_DEFINES_ITSELF 112 | String() method calls itself by using format functions with %v or %s directly on the method type. 113 | 114 | BLOCKER 115 | SINGLE 116 | READY 117 | bug 118 | 119 | 120 | CONDITION_EVALUATED_STATICALLY 121 | Condition can be evaluated statically. 122 | CONDITION_EVALUATED_STATICALLY 123 | There is no need to evaluate condition that clearly are false or true. 124 | MINOR 125 | SINGLE 126 | READY 127 | bad-practice 128 | 129 | 130 | --------------------------------------------------------------------------------