Golang静态代码扫描

1. gitlab中配置静态代码检查

在Go语言中,可以使用一些第三方工具来进行静态代码扫描,常用的工具包括:

  • Go vet:Go语言官方提供的工具,用于检查代码中的常见错误和问题;
  • oLint:静态分析工具,可以检查代码中的一些不规范的写法和风格;
  • GoMetaLinter:Go语言的多工具静态代码分析器,可以集成多种静态分析工具,例如Go vet、GoLint、gofmt、gocyclo等。其中,GoMetaLinter是一个比较强大的静态代码扫描工具,可以集成多个静态分析工具,并且支持多种输出格式和配置选项。

在GitLab中配置静态代码扫描也比较简单,可以使用.gitlab-ci.yml文件来定义CI/CD流程。以下是一个示例:

stages:
  - test
  - scan

unit-test:
  stage: test
  script:
    - go test -v ./...

static-scan:
  stage: scan
  image: golangci/golangci-lint:v1.42.1
  script:
    - golangci-lint run --timeout 5m

上面的示例定义了两个阶段(stage),分别为test和scan。在test阶段中,使用go test命令运行单元测试。在scan阶段中,使用golangci-lint工具运行静态代码扫描,并将输出结果显示在控制台中。

需要注意的是,上面的示例使用了golangci-lint工具来进行静态代码扫描,因此需要在Docker中安装该工具。在image字段中指定需要使用的Docker镜像,可以选择golangci/golangci-lint或者其他适合自己的镜像。

另外,还需要在项目中添加.gitlab-ci.yml文件,并在GitLab的设置中启用CI/CD流程。在GitLab中,可以通过点击项目设置的CI/CD选项,进入CI/CD设置页面,并在其中添加一个Runner。添加完成后,GitLab会在每次代码提交时自动运行CI/CD流程,并进行单元测试和静态代码扫描。

2. 静态检查

可用于 Go 语言代码分析的工具有很多,比如 golint、gofmt、misspell 等,如果一一引用配置,就会比较烦琐,所以通常我们不会单独地使用它们,而是使用 golangci-lint。

官网地址:

golangci-lint 是一个集成工具,它集成了很多静态代码分析工具,便于我们使用。有以下特点:

  • 速度非常快:golangci-lint 是基于 gometalinter 开发的,但是平均速度要比 gometalinter 快 5 倍。速度快的原因有三个:可以并行检查代码;可以复用 go build 缓存;会缓存分析结果。
  • 可配置:支持 YAML 格式的配置文件,让检查更灵活,更可控。
  • IDE 集成:可以集成进多个主流的 IDE,例如 VS Code、GNU Emacs、Sublime Text、Goland 等。
  • linter 聚合器:1.41.1 版本集成了 76 个 linter,并且还支持自定义 linter。
  • 最小的误报数:调整了所集成 linter 的默认设置,大幅度减少了误报。
  • 良好的输出:输出的结果带有颜色、代码行号和 linter 标识,易于查看和定位。
  • 当前更新迭代速度很快,不断有新的 linter 被集成。

这里主要采用golangci-lint,采用其中配置文件定义可以参考文档 https://golangci-lint.run/usage/configuration/

  • .golangci.yml
  • .golangci.yaml
  • .golangci.toml
  • .golangci.json

参考文档:《Go 静态代码分析工具 golangci-lint》https://blog.csdn.net/wohu1104/article/details/113751501

在.golangci.yml中,主要分为几个部分,run/output/linters-settings/linters/issues/severity这6部分。

# Options for analysis running.
run:
    # See the dedicated "run" documentation section.
    option: value

# output configuration options
output:
    option: value

# All available settings of specific linters.
linters-settings:
    option: value

linters:
    option: value

issues:
    option: value

severity:
    option: value

2.1. run模块

# Options for analysis running.
run:
  # 默认可用并发CPU数量.
  concurrency: 4
  # 静态分析超时时间, e.g. 30s, 5m.
  # Default: 1m
  timeout: 5m
  # 至少一个错误出现时
  # 退出代码默认为1
  issues-exit-code: 2
  # 是否包含测试文件
  # 默认是true
  tests: false
  # 所有linters采用的构建标签
  # 默认空列表 [].
  build-tags:
    - mytag
  # 要跳过的目录,其中问题不会被报告,可以采用正则方式匹配。
  # 默认值为空列表
  # 但是缺省dirs将被跳过,而不依赖于该选项的值(参见skip-dirs-use-default)。"/"将被当前操作系统文件路径分隔符取代,以便在Windows上正常工作。
  skip-dirs:
    - src/external_libs
    - autogenerated_by_my_lib
  # Default: true
  skip-dirs-use-default: false
  # 设置不需要检查的go源码文件,支持正则匹配,这里建议包括:_test.go。
  # 默认值为空列表
  # 但是不需要包含所有自动生成的文件, "/"将被当前操作系统文件路径分隔符取代,以便在Windows上正常工作。
  skip-files:
    - ".*\\.my\\.go$"
    - lib/bad.go
   - ^.*\.(pb|y)\.go$

  # 定义Go版本限制。#从go1.18开始主要与泛型支持有关。#默认值:使用Go版本。mod文件,回退到env var ' GOVERSION ',回退到1.18
   go: '1.19'

2.2. output模块

# output configuration options
output:
  # 输出格式 彩打行号|行号|json|tab|检查格式|代码韧性
  # Default: colored-line-number
  format: json
  # Print lines of code with issue.
  print-issued-lines: false

  # 在问题报告结尾打印linter名称
  # Default: true
  print-linter-name: false
  # Make issues output unique by line.
  # Default: true
  uniq-by-line: false
  # 输出文件的路径前缀
  # Default is no prefix.
  path-prefix: ""
  # 对结果排序
  sort-results: true

2.3. linters模块

golangci-lint 的配置比较灵活,比如你可以自定义要启用哪些 linter。golangci-lint 默认启用的 linter,包括这些:

  • depguard - 依赖包的校验
  • misspell - 拼写错误检查
  • gofumpt - 更强格式化检查
  • deadcode - 死代码检查
  • errcheck - 返回错误是否使用检查
  • gosimple - 检查代码是否可以简化
  • govet - 代码可疑检查,比如格式化字符串和类型不一致
  • ineffassign - 检查是否有未使用的代码
  • staticcheck - 静态分析检查
  • structcheck - 查找未使用的结构体字段
  • typecheck - 类型检查
  • unused - 未使用代码检查
  • varcheck - 未使用的全局变量和常量检查
linters:
  disable-all: true
  enable:
    - depguard
    - typecheck
    - goimports
    - gofumpt
    - govet
    - misspell
    - ineffassign
    - gosimple
    - unused
    - errcheck

3. 静态检查运行

  1. 对当前目录及子目录下的所有 Go 文件进行静态代码检查
$ golangci-lint run  
#等效于 golangci-lint run ./...
  1. 对指定的 Go 文件或者指定目录下的 Go 文件进行静态代码检查
$ golangci-lint run dir1 dir2/... dir3/file1.go  

这里需要你注意:上述命令不会检查 dir1 下子目录的 Go 文件,如果想递归地检查一个目录,需要在目录后面追加 /... ,例如:dir2/...

  1. 根据指定配置文件,进行静态代码检查
$ golangci-lint run -c .golangci.yaml ./...
  1. 运行指定的 linter

可以传入参数 -E/--enable 来使某个 linter 可用,也可以使用 -D/--disable 参数来使某个 linter 不可用。下面的示例仅仅启用了 errcheck linter :

$ golangci-lint run --no-config --disable-all -E errcheck ./...

默认情况下,golangci-lint 会从当前目录一层层往上寻找配置文件名 .golangci.yaml、.golangci.toml、.golangci.json 直到根(/ )目录。如果找到,就以找到的配置文件作为本次运行的配置文件,所以为了防止读取到未知的配置文件,可以用 --no-config 参数使 golangci-lint 不读取任何配置文件。

禁止运行指定的 liner:
如果我们想禁用某些 linter,可以使用 -D 选项。

$ golangci-lint run --no-config -D godot,errcheck

4. golangci-lint模板实例

//.golangci.yaml

# 官方golangci-lint配置 https://json.schemastore.org/golangci-lint.json
# 使用范例: golangci-lint run -c .golangci.yaml ./...

#service:
  # use the fixed version to not introduce new linters unexpectedly
  # golangci-lint-version: 1.46.2

run:
  # timeout for analysis, e.g. 30s, 5m, default is 1m
  timeout: 10m
    # include test files or not, default is true
  tests: false
  # 允许跳过目录开关  default is true. Enables skipping of directories:
  #   vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
  skip-dirs-use-default: true
  skip-dirs: # 设置要忽略的目录
    - util
  skip-files: # 设置不需要检查的go源码文件,支持正则匹配,这里建议包括:_test.go
    # Skip autogenerated files.
    - ^.*\.(pb|y)\.go$

output:
  #对结果排序
  sort-results: true
  # # 输出格式 彩打行号|行号|json|tab|检查格式|代码韧性
  # # Default: colored-line-number
  # format: json
  # # Print lines of code with issue.
  # print-issued-lines: false

linters:
  disable-all: true
  enable:
    # TODO by project
    - depguard
    - typecheck
    - gofumpt
    - govet
    - misspell
    - ineffassign
    - gosimple
    - unused
    - errcheck

    # - depguard - 依赖包的校验
    # - typecheck - 类型检查
    # - misspell - 拼写错误检查
    # - gofumpt - 更强格式化检查
    # - deadcode - 死代码检查
    # - errcheck - 返回错误是否使用检查
    # - gosimple - 检查代码是否可以简化
    # - govet - 代码可疑检查,比如格式化字符串和类型不一致
    # - ineffassign - 检查是否有未使用的代码
    # - staticcheck - 静态分析检查
    # - unused - 未使用代码检查
    # - varcheck - 未使用的全局变量和常量检查
    #- goimports

linters-settings:  
  depguard:
    list-type: blacklist
    include-go-root: true
    packages-with-error-message:
      # TODO by project
      - sync/atomic: "Use go.uber.org/atomic instead of sync/atomic"
      - io/ioutil: "Use corresponding 'os' or 'io' functions instead."
      - regexp: "Use github.com/grafana/regexp instead of regexp"
      - github.com/stretchr/testify/assert: "Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert"
  # gofumpt:
  #   extra-rules: true
  #   lang-version: "1.15" #判断go版本
  #goimports:
  #  local-prefixes: github.com/prometheus/prometheus

issues:
  # 同文件中最多问题数量限制,0关闭,默认3
  max-same-issues: 0
  exclude-rules:
    - path: _test.go
      linters:
        - errcheck

发表评论

邮箱地址不会被公开。 必填项已用*标注