├── .codecov.yaml ├── .github └── workflows │ ├── ci.yml │ ├── golden-test-comment.yml │ └── golden-test-run.yml ├── .gitignore ├── .golangci.version ├── .golangci.yaml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── accumulation └── analyzer.go ├── annotation ├── affiliation.go ├── analyzer.go ├── analyzer_test.go ├── consume_trigger.go ├── consume_trigger_test.go ├── copy_test.go ├── doc.go ├── equals_test.go ├── full_trigger.go ├── helper_test.go ├── key.go ├── key_test.go ├── map.go ├── produce_trigger.go ├── produce_trigger_test.go ├── trigger.go └── util.go ├── assertion ├── affiliation │ ├── affiliation.go │ ├── analyzer.go │ └── analyzer_test.go ├── analyzer.go ├── analyzer_test.go ├── anonymousfunc │ ├── analyzer.go │ ├── analyzer_test.go │ ├── closure.go │ └── testdata │ │ └── src │ │ └── go.uber.org │ │ └── anonymousfunc │ │ └── simple.go ├── function │ ├── analyzer.go │ ├── analyzer_test.go │ ├── assertiontree │ │ ├── assertion_node.go │ │ ├── backprop.go │ │ ├── backprop_util.go │ │ ├── fld_assertion_node.go │ │ ├── func_assertion_node.go │ │ ├── function_context.go │ │ ├── index_assertion_node.go │ │ ├── parse_expr_producer.go │ │ ├── rich_check_effect.go │ │ ├── root_assertion_node.go │ │ ├── structinit_util.go │ │ ├── util.go │ │ └── var_assertion_node.go │ ├── functioncontracts │ │ ├── analyzer.go │ │ ├── analyzer_test.go │ │ ├── contract.go │ │ ├── infer.go │ │ ├── parse.go │ │ └── testdata │ │ │ └── src │ │ │ └── go.uber.org │ │ │ ├── factexport │ │ │ ├── downstream │ │ │ │ └── downstream.go │ │ │ └── upstream │ │ │ │ └── upstream.go │ │ │ ├── infer │ │ │ ├── external.go │ │ │ └── main.go │ │ │ └── parse │ │ │ └── main.go │ ├── preprocess │ │ ├── cfg.go │ │ └── preprocessor.go │ ├── producer │ │ ├── deep_parsed_producer.go │ │ ├── parsed_producer.go │ │ └── shallow_parsed_producer.go │ └── testdata │ │ └── src │ │ └── go.uber.org │ │ ├── backprop │ │ └── fixpoint.go │ │ └── pkg │ │ └── main.go ├── global │ ├── analyzer.go │ ├── analyzer_test.go │ └── globalvarinit.go └── structfield │ ├── analyzer.go │ ├── analyzer_test.go │ └── field_context.go ├── cmd ├── gclplugin │ ├── gclplugin.go │ └── gclplugin_test.go └── nilaway │ └── main.go ├── config ├── config.go └── const.go ├── diagnostic ├── conflict.go ├── engine.go ├── nilflow.go └── nolint.go ├── go.mod ├── go.sum ├── hook ├── assume_return.go ├── hook.go ├── no_return_call.go ├── replace_conditional.go └── split_blocks_on.go ├── inference ├── engine.go ├── explained_bool.go ├── inferred_map.go ├── inferred_map_test.go ├── inferred_value.go ├── mode.go └── primitive.go ├── nilaway.go ├── nilaway_test.go ├── nilawaytest └── testhelper.go ├── testdata ├── integration │ ├── .custom-gcl.template.yaml │ ├── .golangci.yaml │ ├── README.md │ ├── contracts │ │ ├── downstream │ │ │ └── downstream.go │ │ └── upstream │ │ │ └── upstream.go │ ├── go.mod │ └── simple │ │ ├── downstream │ │ ├── downstream.go │ │ └── nolint.go │ │ └── upstream │ │ ├── nolint.go │ │ └── upstream.go └── src │ ├── go.uber.org │ ├── abnormalflow │ │ └── abnormalflow.go │ ├── annotationparse │ │ └── annotationparse.go │ ├── anonymousfunction │ │ ├── anonymous_functions_assignment.go │ │ ├── argument_passing_explicitly.go │ │ ├── argument_passing_implicitly.go │ │ ├── rich_check_effect.go │ │ └── simple.go │ ├── arrays │ │ └── arrays.go │ ├── channels │ │ └── channels.go │ ├── consts │ │ ├── consts.go │ │ └── lib │ │ │ └── const.go │ ├── contracts │ │ ├── contracts.go │ │ ├── inference │ │ │ └── userdefinedfunctions-with-inference.go │ │ ├── namedtypes │ │ │ └── namedtypes.go │ │ └── userdefinedfunctions.go │ ├── deepnil │ │ ├── deepnil.go │ │ └── inference │ │ │ └── deepnil-with-inference.go │ ├── errorreturn │ │ ├── errorreturn.go │ │ ├── errors │ │ │ └── errors.go │ │ ├── fmt │ │ │ └── fmt.go │ │ └── inference │ │ │ ├── downstream.go │ │ │ ├── errorreturn-with-inference.go │ │ │ ├── otherPkg │ │ │ └── otherFile.go │ │ │ └── upstream │ │ │ └── upstream.go │ ├── functioncontracts │ │ ├── functioncontracts.go │ │ └── inference │ │ │ ├── functioncontracts.go │ │ │ └── written_contracts.go │ ├── generics │ │ └── generics.go │ ├── globalvars │ │ ├── globalvarinit.go │ │ ├── globalvars.go │ │ └── upstream │ │ │ └── upstream.go │ ├── goquirks │ │ └── goquirks.go │ ├── helloworld │ │ └── helloworld.go │ ├── ignoregenerated │ │ ├── ignoregenerated1.go │ │ ├── ignoregenerated2.go │ │ └── ignoregenerated3.go │ ├── inference │ │ └── inference.go │ ├── loopflow │ │ └── loopflow.go │ ├── looprange │ │ └── looprange.go │ ├── maps │ │ └── maps.go │ ├── methodimplementation │ │ ├── chainedDependencies │ │ │ ├── mainfile.go │ │ │ ├── packageA │ │ │ │ └── file1.go │ │ │ ├── packageB │ │ │ │ └── file2.go │ │ │ └── packageC │ │ │ │ └── file3.go │ │ ├── embedding │ │ │ └── embedding.go │ │ ├── mergedDependencies │ │ │ ├── mainfile.go │ │ │ ├── packageA │ │ │ │ └── file1.go │ │ │ └── packageB │ │ │ │ └── file2.go │ │ ├── methodimplementation1.go │ │ ├── methodimplementation10.go │ │ ├── methodimplementation11.go │ │ ├── methodimplementation12.go │ │ ├── methodimplementation13.go │ │ ├── methodimplementation2.go │ │ ├── methodimplementation3.go │ │ ├── methodimplementation4.go │ │ ├── methodimplementation5.go │ │ ├── methodimplementation6.go │ │ ├── methodimplementation7.go │ │ ├── methodimplementation8.go │ │ ├── methodimplementation9.go │ │ └── multipackage │ │ │ ├── mainfile.go │ │ │ ├── packageA │ │ │ └── interfacefile.go │ │ │ ├── packageB │ │ │ └── structfile.go │ │ │ └── packageC │ │ │ └── instantiationfile.go │ ├── multifilepackage │ │ ├── firstpackage │ │ │ ├── firstfile.go │ │ │ └── secondfile.go │ │ ├── rootfile.go │ │ └── secondpackage │ │ │ └── firstfile.go │ ├── multipleassignment │ │ └── multipleassignment.go │ ├── namedreturn │ │ └── namedreturn.go │ ├── nilabletypes │ │ └── nilabletypes.go │ ├── nilcheck │ │ ├── conditionalflow.go │ │ └── nonconditionalflow.go │ ├── nolint │ │ ├── file_level.go │ │ └── nolint.go │ ├── receivers │ │ ├── inference │ │ │ └── receivers-with-inference.go │ │ └── receivers.go │ ├── simpleflow │ │ └── simpleflow.go │ ├── slices │ │ ├── inference │ │ │ └── slices-with-inference.go │ │ └── slices.go │ ├── structinit │ │ ├── defaultfield │ │ │ └── defaultfield.go │ │ ├── funcreturnfields │ │ │ ├── funcreturnbasic.go │ │ │ ├── funcreturnwitherrors.go │ │ │ ├── functionreturntransitive.go │ │ │ └── methodreturnbasic.go │ │ ├── global │ │ │ └── global.go │ │ ├── local │ │ │ └── local.go │ │ ├── multipackage │ │ │ ├── multipackage.go │ │ │ └── packageone │ │ │ │ └── packageone.go │ │ ├── paramfield │ │ │ ├── paramfield.go │ │ │ └── receiverfield.go │ │ └── paramsideeffect │ │ │ ├── paramsideeffect.go │ │ │ └── receiversideeffect.go │ └── trustedfunc │ │ └── trustedfuncs.go │ ├── grouping │ ├── disabled │ │ └── main.go │ ├── enabled │ │ └── main.go │ └── errormessage │ │ ├── errormessage-assignment-flow.go │ │ └── inference │ │ └── errormessage-with-inference.go │ ├── ignoredpkg1 │ └── main.go │ ├── ignoredpkg2 │ └── main.go │ ├── prettyprint │ └── main.go │ └── stubs │ ├── github.com │ └── stretchr │ │ └── testify │ │ ├── assert │ │ └── assert.go │ │ ├── require │ │ └── require.go │ │ └── suite │ │ └── suite.go │ └── go.uber.org │ └── zap │ └── zap.go ├── tools ├── cmd │ ├── golden-test │ │ ├── main.go │ │ └── main_test.go │ └── integration-test │ │ ├── golangci_lint.go │ │ ├── main.go │ │ ├── main_test.go │ │ └── standalone.go ├── go.mod └── go.sum └── util ├── analysishelper ├── analyzer.go └── analyzer_test.go ├── asthelper ├── asthelper.go └── asthelper_test.go ├── guard_nonce.go ├── orderedmap ├── orderedmap.go └── orderedmap_test.go ├── tokenhelper ├── tokenhelper.go └── tokenhelper_test.go ├── typeshelper ├── typeshelper.go └── typeshelper_test.go └── util.go /.codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 80..100 3 | round: down 4 | precision: 2 5 | 6 | status: 7 | project: # measuring the overall project coverage 8 | default: # context, you can create multiple ones with custom titles 9 | enabled: yes # must be yes|true to enable this status 10 | target: auto # specify the target coverage for each commit status 11 | # option: "auto" (compare against parent commit or pull request base) 12 | # option: "X%" a static target percentage to hit 13 | threshold: 1% # allow the coverage drop by 1% before marking as failure (to allow some flakiness) 14 | if_not_found: success # if parent is not found report status as success, error, or failure 15 | if_ci_failed: error # if ci fails report status as success, error, or failure 16 | patch: 17 | default: 18 | enabled: no 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | name: Check out repository 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: 1.24.x 20 | cache: false 21 | 22 | - name: Read golangci-lint version from .golangci.version 23 | id: golangci-version 24 | run: echo "GOLANGCI_VERSION=$(cat .golangci.version)" > $GITHUB_OUTPUT 25 | 26 | # We do not really run golangci-lint here (hence we use the -h flag). We just use the 27 | # golangci-lint action to install it and reuses its internal caching infrastructure. 28 | # The actual execution is done in the Lint job (together with other custom linting tasks). 29 | - uses: golangci/golangci-lint-action@v7 30 | name: Install golangci-lint 31 | with: 32 | version: v${{ steps.golangci-version.outputs.GOLANGCI_VERSION }} 33 | args: -h 34 | 35 | - name: Symlink installed golangci-lint to bin directory 36 | run: mkdir -p bin && ln -s $(which golangci-lint) bin/golangci-lint 37 | 38 | - run: make lint 39 | name: Lint 40 | 41 | test: 42 | name: Test 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | go: [ "1.23.x", "1.24.x" ] 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Set up Go 51 | uses: actions/setup-go@v4 52 | with: 53 | go-version: ${{ matrix.go }} 54 | cache: true 55 | 56 | - name: Load cached dependencies 57 | uses: actions/cache@v4 58 | with: 59 | path: ~/go/pkg/mod 60 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 61 | 62 | - name: Download dependencies 63 | run: go mod download 64 | 65 | - name: Build 66 | run: make build 67 | 68 | - name: Test and generate coverage report 69 | run: make cover 70 | 71 | - name: Upload coverage to codecov.io 72 | uses: codecov/codecov-action@v4 73 | with: 74 | token: ${{ secrets.CODECOV_TOKEN }} 75 | 76 | integration-test: 77 | name: Integration Test 78 | runs-on: ubuntu-latest 79 | strategy: 80 | matrix: 81 | go: [ "1.23.x", "1.24.x" ] 82 | steps: 83 | - uses: actions/checkout@v4 84 | 85 | - name: Set up Go 86 | uses: actions/setup-go@v4 87 | with: 88 | go-version: ${{ matrix.go }} 89 | cache: true 90 | 91 | - name: Load cached dependencies 92 | uses: actions/cache@v4 93 | with: 94 | path: ~/go/pkg/mod 95 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 96 | 97 | - name: Download dependencies 98 | run: go mod download 99 | 100 | - name: Run integration tests 101 | run: make integration-test 102 | -------------------------------------------------------------------------------- /.github/workflows/golden-test-run.yml: -------------------------------------------------------------------------------- 1 | name: Golden Test 2 | 3 | # NilAway output may change due to introduction of new feature or bug fixes. Since NilAway is still 4 | # at early stage of development, constantly updating / maintaining the golden test output will be 5 | # a burden. Therefore, we run this as a separate CI job and post the differences as a PR comment 6 | # for manual reviews. 7 | # 8 | # Note that this workflow is triggered on `pull_request` event, where if the PR is created from 9 | # forked repository, the GITHUB_TOKEN will not have necessary write permission to post the comments. 10 | # To work around this (and to provide proper isolation), we follow the recommended approach [1] of 11 | # separating job into two parts: (1) build and upload results as artifacts in untrusted environment 12 | # (here), and then (2) trigger a follow-up job that downloads the artifacts and posts the comment in 13 | # trusted environment (see .github/workflows/golden-test-comment.yml). 14 | # 15 | # [1]: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ 16 | on: 17 | pull_request: 18 | 19 | jobs: 20 | golden-test: 21 | name: Run 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | name: Check out repository 26 | 27 | - name: Fetch base branch (${{ github.event.pull_request.base.ref }}) locally 28 | run: git fetch origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }} 29 | 30 | - name: Set up Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: 1.24.x 34 | cache: false 35 | 36 | - name: Golden Test 37 | id: golden_test 38 | # Run golden test by comparing HEAD and the base branch (the target branch of the PR). 39 | # GitHub Actions terminates the job if it hits the resource limits. Here we limit the 40 | # memory usage to 8GiB to avoid that. 41 | run: | 42 | make golden-test GOMEMLIMIT=8192MiB ARGS="-base-branch ${{ github.event.pull_request.base.ref }} -result-file ${{ runner.temp }}/golden-test-comment.md" 43 | 44 | - uses: actions/upload-artifact@v4 45 | with: 46 | name: golden-test-comment.md 47 | path: ${{ runner.temp }}/golden-test-comment.md 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Go workspace file 18 | go.work 19 | 20 | # IntelliJ files 21 | .idea/ 22 | 23 | # Coverage files 24 | cover.out 25 | cover.html 26 | 27 | # Local binary folder 28 | bin/ 29 | 30 | # Local build folder for storing build artifacts 31 | build/ 32 | 33 | # Mac OS files 34 | **/.DS_Store 35 | -------------------------------------------------------------------------------- /.golangci.version: -------------------------------------------------------------------------------- 1 | 2.1.2 -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - goheader 5 | - nolintlint 6 | - paralleltest 7 | - revive 8 | settings: 9 | staticcheck: 10 | checks: 11 | - all 12 | # disable the rule QF1001: "could apply De Morgan's law" since it does not lead to clarity 13 | # in most cases. 14 | - '-QF1001' 15 | goheader: 16 | values: 17 | regexp: 18 | any-year: \d{4} 19 | template: |- 20 | Copyright (c) {{ ANY-YEAR }} Uber Technologies, Inc. 21 | 22 | Licensed under the Apache License, Version 2.0 (the "License"); 23 | you may not use this file except in compliance with the License. 24 | You may obtain a copy of the License at 25 | 26 | http://www.apache.org/licenses/LICENSE-2.0 27 | 28 | Unless required by applicable law or agreed to in writing, software 29 | distributed under the License is distributed on an "AS IS" BASIS, 30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | See the License for the specific language governing permissions and 32 | limitations under the License. 33 | 34 | issues: 35 | max-issues-per-linter: 0 36 | max-same-issues: 0 37 | 38 | formatters: 39 | enable: 40 | - gci 41 | - gofmt 42 | - goimports 43 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to NilAway 2 | ======================= 3 | 4 | Uber welcomes contributions of all kinds and sizes. This includes everything from simple bug reports to large features. 5 | 6 | Workflow 7 | -------- 8 | 9 | We love GitHub issues! 10 | 11 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice. 12 | 13 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking. 14 | 15 | Small pull requests for things like typos, bug fixes, etc. are always welcome. 16 | 17 | DOs and DON'Ts 18 | -------------- 19 | 20 | * DO format your code using `go fmt`. 21 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. 22 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. 23 | 24 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue, and we'll be happy to discuss it. 25 | 26 | Guiding Principles 27 | ------------------ 28 | 29 | * We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them. 30 | * Always be respectful of one another. Assume the best in others and act with empathy at all times. 31 | * Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision-making process. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Set up GOBIN so that our binaries are installed to ./bin instead of $GOPATH/bin. 2 | PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 3 | export GOBIN = $(PROJECT_ROOT)/bin 4 | 5 | GOLANGCI_LINT_VERSION := $(shell $(GOBIN)/golangci-lint version --short 2>/dev/null) 6 | REQUIRED_GOLANGCI_LINT_VERSION := $(shell cat .golangci.version) 7 | 8 | # Directories containing independent Go modules. 9 | MODULE_DIRS = . ./tools 10 | 11 | .PHONY: all 12 | all: build lint test integration-test 13 | 14 | .PHONY: clean 15 | clean: 16 | @rm -rf $(GOBIN) 17 | 18 | .PHONY: build 19 | build: 20 | go install go.uber.org/nilaway/cmd/nilaway 21 | 22 | .PHONY: test 23 | test: 24 | @$(foreach mod,$(MODULE_DIRS),(cd $(mod) && go test -race ./...) &&) true 25 | 26 | .PHONY: cover 27 | cover: 28 | @$(foreach mod,$(MODULE_DIRS), ( \ 29 | cd $(mod) && \ 30 | go test -race -coverprofile=cover.out -coverpkg=./... ./... \ 31 | && go tool cover -html=cover.out -o cover.html) &&) true 32 | 33 | .PHONY: golden-test 34 | golden-test: 35 | @cd tools && go install go.uber.org/nilaway/tools/cmd/golden-test 36 | @$(GOBIN)/golden-test $(ARGS) 37 | 38 | .PHONY: integration-test 39 | integration-test: 40 | @cd tools && go install go.uber.org/nilaway/tools/cmd/integration-test 41 | @$(GOBIN)/integration-test 42 | 43 | .PHONY: lint 44 | lint: golangci-lint nilaway-lint tidy-lint 45 | 46 | # Install golangci-lint with the required version in GOBIN if it is not already installed. 47 | .PHONY: install-golangci-lint 48 | install-golangci-lint: 49 | ifneq ($(GOLANGCI_LINT_VERSION),$(REQUIRED_GOLANGCI_LINT_VERSION)) 50 | @echo "[lint] installing golangci-lint v$(REQUIRED_GOLANGCI_LINT_VERSION) since current version is \"$(GOLANGCI_LINT_VERSION)\"" 51 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v$(REQUIRED_GOLANGCI_LINT_VERSION) 52 | endif 53 | 54 | .PHONY: golangci-lint 55 | golangci-lint: install-golangci-lint 56 | @echo "[lint] $(shell $(GOBIN)/golangci-lint version)" 57 | @$(foreach mod,$(MODULE_DIRS), \ 58 | (cd $(mod) && \ 59 | echo "[lint] golangci-lint: $(mod)" && \ 60 | $(GOBIN)/golangci-lint run --path-prefix $(mod)) &&) true 61 | 62 | .PHONY: tidy-lint 63 | tidy-lint: 64 | @$(foreach mod,$(MODULE_DIRS), \ 65 | (cd $(mod) && \ 66 | echo "[lint] mod tidy: $(mod)" && \ 67 | go mod tidy && \ 68 | git diff --exit-code -- go.mod go.sum) &&) true 69 | 70 | .PHONY: nilaway-lint 71 | nilaway-lint: build 72 | @$(foreach mod,$(MODULE_DIRS), \ 73 | (cd $(mod) && \ 74 | echo "[lint] nilaway linting itself: $(mod)" && \ 75 | $(GOBIN)/nilaway -include-pkgs="go.uber.org/nilaway" ./...) &&) true 76 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Uber Technologies, Inc. 2 | -------------------------------------------------------------------------------- /annotation/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package annotation 16 | 17 | import ( 18 | "reflect" 19 | 20 | "go.uber.org/nilaway/config" 21 | "go.uber.org/nilaway/util/analysishelper" 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | const _doc = "Read the annotations for each struct, interface, and function in this package, returning" + 26 | " the results so that they may be matched against assertions by an accumulator" 27 | 28 | // Analyzer here is the analyzer than reads annotations and passes them onto the accumulator to 29 | // be matched against assertions. It returns the map generated from reading the annotations in the 30 | // source code 31 | var Analyzer = &analysis.Analyzer{ 32 | Name: "nilaway_annotation_analyzer", 33 | Doc: _doc, 34 | Run: analysishelper.WrapRun(run), 35 | ResultType: reflect.TypeOf((*analysishelper.Result[*ObservedMap])(nil)), 36 | Requires: []*analysis.Analyzer{config.Analyzer}, 37 | } 38 | 39 | func run(pass *analysis.Pass) (*ObservedMap, error) { 40 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 41 | 42 | if !conf.IsPkgInScope(pass.Pkg) { 43 | return new(ObservedMap), nil 44 | } 45 | 46 | return newObservedMap(pass, pass.Files), nil 47 | } 48 | -------------------------------------------------------------------------------- /annotation/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package annotation 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "go.uber.org/goleak" 22 | "go.uber.org/nilaway/util/analysishelper" 23 | ) 24 | 25 | func TestAnalyzer(t *testing.T) { 26 | t.Parallel() 27 | 28 | // Intentionally give a nil pass variable to trigger a panic, but we should recover from it 29 | // and convert it to an error via the result struct. 30 | r, err := Analyzer.Run(nil /* pass */) 31 | require.NoError(t, err) 32 | require.ErrorContains(t, r.(*analysishelper.Result[*ObservedMap]).Err, "INTERNAL PANIC") 33 | } 34 | 35 | func TestMain(m *testing.M) { 36 | goleak.VerifyTestMain(m) 37 | } 38 | -------------------------------------------------------------------------------- /annotation/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package annotation implements annotation-related structs (site, maps, triggers) and methods. It 16 | // also implements the annotation analyzer that reads the manually-provided annotations. 17 | package annotation 18 | -------------------------------------------------------------------------------- /annotation/key_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package annotation 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/suite" 21 | ) 22 | 23 | const _interfaceNameKey = "Key" 24 | 25 | // initStructsKey initializes all structs that implement the Key interface 26 | var initStructsKey = []any{ 27 | &FieldAnnotationKey{}, 28 | &CallSiteParamAnnotationKey{}, 29 | &ParamAnnotationKey{}, 30 | &CallSiteRetAnnotationKey{}, 31 | &RetAnnotationKey{}, 32 | &TypeNameAnnotationKey{}, 33 | &GlobalVarAnnotationKey{}, 34 | &RecvAnnotationKey{}, 35 | &RetFieldAnnotationKey{}, 36 | &EscapeFieldAnnotationKey{}, 37 | &ParamFieldAnnotationKey{}, 38 | &LocalVarAnnotationKey{}, 39 | } 40 | 41 | // TestKeyEqualsSuite runs the test suite for the `equals` method of all the structs that implement 42 | // the `Key` interface. 43 | type KeyEqualsTestSuite struct { 44 | EqualsTestSuite 45 | } 46 | 47 | func (s *KeyEqualsTestSuite) SetupTest() { 48 | s.interfaceName = _interfaceNameKey 49 | s.initStructs = initStructsKey 50 | } 51 | 52 | func TestKeyEqualsSuite(t *testing.T) { 53 | t.Parallel() 54 | suite.Run(t, new(KeyEqualsTestSuite)) 55 | } 56 | 57 | // TestKeyCopySuite runs the test suite for the `copy` method of all the structs that implement the `Key` interface. 58 | type KeyCopyTestSuite struct { 59 | CopyTestSuite 60 | } 61 | 62 | func (s *KeyCopyTestSuite) SetupTest() { 63 | s.interfaceName = _interfaceNameKey 64 | s.initStructs = initStructsKey 65 | } 66 | 67 | func TestKeyCopySuite(t *testing.T) { 68 | t.Parallel() 69 | suite.Run(t, new(KeyCopyTestSuite)) 70 | } 71 | -------------------------------------------------------------------------------- /annotation/trigger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package annotation 16 | 17 | // TriggerKind indicates the kind of the producer / consume trigger, e.g., a trigger will always 18 | // fire, or a trigger will only fire if the underlying site is nilable etc. 19 | type TriggerKind uint8 20 | 21 | const ( 22 | // Always indicates a trigger will always fire. 23 | Always TriggerKind = iota + 1 24 | // Conditional indicates a trigger will only fire depending on the nilability of the 25 | // underlying site. 26 | Conditional 27 | // DeepConditional indicates a trigger will only fire depending on the deep nilability of the 28 | // underlying site. 29 | DeepConditional 30 | // Never indicates a trigger will never fire. 31 | Never 32 | ) 33 | -------------------------------------------------------------------------------- /assertion/affiliation/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package affiliation 16 | 17 | import ( 18 | "reflect" 19 | 20 | "go.uber.org/nilaway/annotation" 21 | "go.uber.org/nilaway/config" 22 | "go.uber.org/nilaway/util/analysishelper" 23 | "golang.org/x/tools/go/analysis" 24 | ) 25 | 26 | const _doc = "Track interface declarations and their implementations by identifying sites of type casts of interface " + 27 | "into a concrete type (e.g., var i I = &A{}, where I is an interface and A is a struct implementing I). Generate " + 28 | "potential triggers for flagging covariance and contravariance errors for return types and parameter types, respectively." 29 | 30 | // Analyzer here is the analyzer that tracks interface implementations and analyzes for nilability 31 | // variance, and passes them onto the accumulator to be added to existing assertions to be matched 32 | // against annotations. 33 | var Analyzer = &analysis.Analyzer{ 34 | Name: "nilaway_affiliation_analyzer", 35 | Doc: _doc, 36 | Run: analysishelper.WrapRun(run), 37 | FactTypes: []analysis.Fact{new(AffliliationCache)}, 38 | ResultType: reflect.TypeOf((*analysishelper.Result[[]annotation.FullTrigger])(nil)), 39 | Requires: []*analysis.Analyzer{config.Analyzer}, 40 | } 41 | 42 | func run(pass *analysis.Pass) ([]annotation.FullTrigger, error) { 43 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 44 | 45 | if !conf.IsPkgInScope(pass.Pkg) { 46 | return nil, nil 47 | } 48 | 49 | a := &Affiliation{conf: conf} 50 | a.extractAffiliations(pass) 51 | return a.triggers, nil 52 | } 53 | -------------------------------------------------------------------------------- /assertion/affiliation/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package affiliation 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "go.uber.org/goleak" 22 | "go.uber.org/nilaway/annotation" 23 | "go.uber.org/nilaway/util/analysishelper" 24 | ) 25 | 26 | func TestAnalyzer(t *testing.T) { 27 | t.Parallel() 28 | 29 | // Intentionally give a nil pass variable to trigger a panic, but we should recover from it 30 | // and convert it to an error via the result struct. 31 | r, err := Analyzer.Run(nil /* pass */) 32 | require.NoError(t, err) 33 | require.ErrorContains(t, r.(*analysishelper.Result[[]annotation.FullTrigger]).Err, "INTERNAL PANIC") 34 | } 35 | 36 | func TestMain(m *testing.M) { 37 | goleak.VerifyTestMain(m) 38 | } 39 | -------------------------------------------------------------------------------- /assertion/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package assertion implements a sub-analyzer that collects full triggers from the sub-analyzers 16 | // and combine them into a list of full triggers for the entire package. 17 | package assertion 18 | 19 | import ( 20 | "errors" 21 | "reflect" 22 | 23 | "go.uber.org/nilaway/annotation" 24 | "go.uber.org/nilaway/assertion/affiliation" 25 | "go.uber.org/nilaway/assertion/function" 26 | "go.uber.org/nilaway/assertion/global" 27 | "go.uber.org/nilaway/config" 28 | "go.uber.org/nilaway/util/analysishelper" 29 | "golang.org/x/tools/go/analysis" 30 | ) 31 | 32 | const _doc = "Build the trees of assertions for each function in this package, propagating them to " + 33 | "entry and then matching them with possible sources of production to create a list of triggers " + 34 | "that can then be matched against a set of annotations to generate nil flow errors" 35 | 36 | // Analyzer here is the analyzer than generates assertions and passes them onto the accumulator to 37 | // be matched against annotations 38 | var Analyzer = &analysis.Analyzer{ 39 | Name: "nilaway_assertion_analyzer", 40 | Doc: _doc, 41 | Run: analysishelper.WrapRun(run), 42 | ResultType: reflect.TypeOf((*analysishelper.Result[[]annotation.FullTrigger])(nil)), 43 | Requires: []*analysis.Analyzer{config.Analyzer, function.Analyzer, affiliation.Analyzer, global.Analyzer}, 44 | } 45 | 46 | func run(pass *analysis.Pass) ([]annotation.FullTrigger, error) { 47 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 48 | 49 | if !conf.IsPkgInScope(pass.Pkg) { 50 | return nil, nil 51 | } 52 | 53 | // Collect and merge the results from sub-analyzers. 54 | r1 := pass.ResultOf[function.Analyzer].(*analysishelper.Result[[]annotation.FullTrigger]) 55 | r2 := pass.ResultOf[affiliation.Analyzer].(*analysishelper.Result[[]annotation.FullTrigger]) 56 | r3 := pass.ResultOf[global.Analyzer].(*analysishelper.Result[[]annotation.FullTrigger]) 57 | if err := errors.Join(r1.Err, r2.Err, r3.Err); err != nil { 58 | return nil, err 59 | } 60 | 61 | // Merge full triggers. 62 | triggers := make([]annotation.FullTrigger, 0, len(r1.Res)+len(r2.Res)+len(r3.Res)) 63 | for _, t := range [...][]annotation.FullTrigger{r1.Res, r2.Res, r3.Res} { 64 | triggers = append(triggers, t...) 65 | } 66 | 67 | return triggers, nil 68 | } 69 | -------------------------------------------------------------------------------- /assertion/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assertion 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "go.uber.org/goleak" 22 | "go.uber.org/nilaway/annotation" 23 | "go.uber.org/nilaway/util/analysishelper" 24 | ) 25 | 26 | func TestAnalyzer(t *testing.T) { 27 | t.Parallel() 28 | 29 | // Intentionally give a nil pass variable to trigger a panic, but we should recover from it 30 | // and convert it to an error via the result struct. 31 | r, err := Analyzer.Run(nil /* pass */) 32 | require.NoError(t, err) 33 | require.ErrorContains(t, r.(*analysishelper.Result[[]annotation.FullTrigger]).Err, "INTERNAL PANIC") 34 | } 35 | 36 | func TestMain(m *testing.M) { 37 | goleak.VerifyTestMain(m) 38 | } 39 | -------------------------------------------------------------------------------- /assertion/function/assertiontree/fld_assertion_node.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assertiontree 16 | 17 | import ( 18 | "fmt" 19 | "go/ast" 20 | "go/types" 21 | 22 | "go.uber.org/nilaway/annotation" 23 | ) 24 | 25 | type fldAssertionNode struct { 26 | assertionNodeCommon 27 | 28 | // declaring identifier for this field 29 | decl *types.Var 30 | 31 | functionContext FunctionContext 32 | } 33 | 34 | func (f *fldAssertionNode) MinimalString() string { 35 | return fmt.Sprintf("fld<%s>", f.decl.Name()) 36 | } 37 | 38 | // GetAncestorVarAssertionNode returns the varAssertionNode node that is ancestor of the fldAssertionNode i.e. it is the 39 | // varAssertionNode that lies on the path from root node to fldAssertionNode. Thus, if the fldAssertionNode represents the 40 | // expression `o.f.g.h` then we return the varAssertion node corresponding to `o` 41 | // Returns nil otherwise if there is no ancestor varAssertion node 42 | func (f *fldAssertionNode) GetAncestorVarAssertionNode() *varAssertionNode { 43 | var curNode AssertionNode = f 44 | for curNode != nil { 45 | curNode = curNode.Parent() 46 | 47 | if res, ok := curNode.(*varAssertionNode); ok { 48 | return res 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | // DefaultTrigger for a field node is that field's annotation 55 | func (f *fldAssertionNode) DefaultTrigger() annotation.ProducingAnnotationTrigger { 56 | if f.functionContext.functionConfig.EnableStructInitCheck { 57 | varNode := f.GetAncestorVarAssertionNode() 58 | // If the field is not produced by a variable we default to the FieldAnnotationKey 59 | // Similarly, for a global variable we default to the FieldAnnotationKey 60 | if varNode != nil && !annotation.VarIsGlobal(varNode.decl) { 61 | return &annotation.FldRead{ 62 | TriggerIfNilable: &annotation.TriggerIfNilable{ 63 | Ann: &annotation.EscapeFieldAnnotationKey{ 64 | FieldDecl: f.decl, 65 | }}} 66 | } 67 | } 68 | return &annotation.FldRead{ 69 | TriggerIfNilable: &annotation.TriggerIfNilable{ 70 | Ann: &annotation.FieldAnnotationKey{ 71 | FieldDecl: f.decl, 72 | }}} 73 | } 74 | 75 | // BuildExpr for a field node adds that field access to the expression `expr` 76 | func (f *fldAssertionNode) BuildExpr(expr ast.Expr) ast.Expr { 77 | if f.Root() == nil { 78 | panic("f.BuildExpr should only be called on nodes present in a valid assertion tree") 79 | } 80 | return &ast.SelectorExpr{ 81 | X: expr, 82 | Sel: f.Root().GetDeclaringIdent(f.decl), 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /assertion/function/assertiontree/func_assertion_node.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assertiontree 16 | 17 | import ( 18 | "fmt" 19 | "go/ast" 20 | "go/types" 21 | 22 | "go.uber.org/nilaway/annotation" 23 | "go.uber.org/nilaway/util" 24 | ) 25 | 26 | type funcAssertionNode struct { 27 | assertionNodeCommon 28 | 29 | // declaring identifier for this function 30 | decl *types.Func 31 | args []ast.Expr 32 | } 33 | 34 | func (f *funcAssertionNode) MinimalString() string { 35 | return fmt.Sprintf("func<%s>", f.decl.Name()) 36 | } 37 | 38 | // DefaultTrigger for a function node is that function's return annotation 39 | func (f *funcAssertionNode) DefaultTrigger() annotation.ProducingAnnotationTrigger { 40 | if util.FuncNumResults(f.decl) != 1 { 41 | panic("only functions with singular result should be entered into the assertion tree") 42 | } 43 | 44 | if f.decl.Type().(*types.Signature).Recv() != nil { 45 | return &annotation.MethodReturn{ 46 | TriggerIfNilable: &annotation.TriggerIfNilable{ 47 | Ann: annotation.RetKeyFromRetNum(f.decl, 0)}} 48 | } 49 | return &annotation.FuncReturn{ 50 | TriggerIfNilable: &annotation.TriggerIfNilable{ 51 | Ann: annotation.RetKeyFromRetNum(f.decl, 0)}} 52 | } 53 | 54 | // BuildExpr for a function node adds that function to `expr` as a method call 55 | func (f *funcAssertionNode) BuildExpr(expr ast.Expr) ast.Expr { 56 | if f.Root() == nil { 57 | panic("f.BuildExpr should only be called on nodes present in a valid assertion tree") 58 | } 59 | genFunc := func() ast.Expr { 60 | if expr == nil { 61 | return f.Root().GetDeclaringIdent(f.decl) 62 | } 63 | return &ast.SelectorExpr{ 64 | X: expr, 65 | Sel: f.Root().GetDeclaringIdent(f.decl), 66 | } 67 | } 68 | return &ast.CallExpr{ 69 | Fun: genFunc(), 70 | Lparen: 0, 71 | Args: f.args, 72 | Ellipsis: 0, 73 | Rparen: 0, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /assertion/function/assertiontree/index_assertion_node.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package assertiontree 16 | 17 | import ( 18 | "go/ast" 19 | "go/types" 20 | 21 | "go.uber.org/nilaway/annotation" 22 | ) 23 | 24 | type indexAssertionNode struct { 25 | assertionNodeCommon 26 | index ast.Expr 27 | 28 | // we need to remember the type of the values of this index because there is no other way 29 | // to look it up - unlike fields and functions there is no sufficient identifier to store 30 | valType types.Type 31 | 32 | // here we store the type of the reciever to this indexAssertionNode - 33 | // specifically to determine if it is a map 34 | recvType types.Type 35 | } 36 | 37 | func (i *indexAssertionNode) MinimalString() string { 38 | return "index" 39 | } 40 | 41 | // DefaultTrigger for an index node is the deep nilability annotation of its parent type 42 | func (i *indexAssertionNode) DefaultTrigger() annotation.ProducingAnnotationTrigger { 43 | return deepNilabilityTriggerOf(i.Parent()) 44 | } 45 | 46 | // BuildExpr for an index node adds that index to `expr` 47 | func (i *indexAssertionNode) BuildExpr(expr ast.Expr) ast.Expr { 48 | return &ast.IndexExpr{ 49 | X: expr, 50 | Lbrack: 0, 51 | Index: i.index, 52 | Rbrack: 0, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/contract.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package functioncontracts 16 | 17 | // ContractVal represents the possible value appearing in a function contract. 18 | type ContractVal string 19 | 20 | const ( 21 | // NonNil has keyword "nonnil". 22 | NonNil ContractVal = "nonnil" 23 | // False has keyword "false". 24 | False ContractVal = "false" 25 | // True has keyword "true". 26 | True ContractVal = "true" 27 | // Any has keyword "_". 28 | Any ContractVal = "_" 29 | ) 30 | 31 | // newContractVal converts a keyword string into the corresponding function ContractVal. 32 | func newContractVal(keyword string) ContractVal { 33 | switch keyword { 34 | case "nonnil": 35 | return NonNil 36 | case "false": 37 | return False 38 | case "true": 39 | return True 40 | case "_": 41 | return Any 42 | default: 43 | // TODO: The ideal way to handle this is to keep track of this contract parsing error and 44 | // move on to the other contracts. But this may also require some refactoring of other 45 | // parts (we do not currently handle partial recoveries anyways) 46 | panic("Unexpected keyword for ContractVal: " + keyword) 47 | } 48 | } 49 | 50 | // Contract represents a function contract. 51 | type Contract struct { 52 | // Ins is the list of input contract values, where the index is the index of the parameter. 53 | Ins []ContractVal 54 | // Outs is the list of output contract values, where the index is the index of the return. 55 | Outs []ContractVal 56 | } 57 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package functioncontracts 16 | 17 | import ( 18 | "fmt" 19 | "go/ast" 20 | "regexp" 21 | "strings" 22 | ) 23 | 24 | const _sep = "," 25 | const _contractKeyword = "contract" 26 | const _contractValKeyword = NonNil + "|" + False + "|" + True + "|" + Any 27 | 28 | // _contractRE matches multiple function contracts in the same line. Each contract looks like 29 | // `contract(VALUE(,VALUE)+ -> VALUE(,VALUE)+)`. The RE also captures two lists of VALUEs, 30 | // i.e., the part before and after `->` for each contract. 31 | // Note that we match start and end of the line here and add a non-capturing group for the entire 32 | // contract, and we disallow anything else except whitespace to appear between contracts, so we 33 | // acknowledge only the contracts written in their own line. 34 | var _contractRE = regexp.MustCompile( 35 | fmt.Sprintf("^\\s*//\\s*(?:\\s*%s\\s*\\(\\s*((?:%s)(?:\\s*,\\s*(?:%s))*)\\s*->\\s*((?:%s)(?:\\s*,\\s*(?:%s))*)\\s*\\)\\s*)+$", 36 | _contractKeyword, _contractValKeyword, _contractValKeyword, _contractValKeyword, _contractValKeyword)) 37 | 38 | // parseContracts parses a slice of function contracts from a singe comment group. If no contract 39 | // is found from the comment group, an empty slice is returned. 40 | func parseContracts(doc *ast.CommentGroup) Contracts { 41 | if doc == nil { 42 | return nil 43 | } 44 | 45 | var contracts Contracts 46 | for _, lineComment := range doc.List { 47 | for _, matching := range _contractRE.FindAllStringSubmatch(lineComment.Text, -1) { 48 | // matching is a slice of three elements; the first is the whole matched string and the 49 | // next two are the captured groups of contract values before and after `->`. 50 | ins := parseListOfContractValues(matching[1]) 51 | outs := parseListOfContractValues(matching[2]) 52 | contracts = append(contracts, Contract{ 53 | Ins: ins, 54 | Outs: outs, 55 | }) 56 | } 57 | } 58 | return contracts 59 | } 60 | 61 | // parseListOfContractValues splits a string of comma separated contract value keywords and returns 62 | // a slice of ContractVal. 63 | func parseListOfContractValues(wholeStr string) []ContractVal { 64 | valKeywords := strings.Split(wholeStr, _sep) 65 | contractVals := make([]ContractVal, len(valKeywords)) 66 | for i, v := range valKeywords { 67 | contractVals[i] = newContractVal(strings.TrimSpace(v)) 68 | } 69 | return contractVals 70 | } 71 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/testdata/src/go.uber.org/factexport/downstream/downstream.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package downstream 16 | 17 | import ( 18 | "go.uber.org/factexport/upstream" 19 | ) 20 | 21 | func foo() { 22 | // The contract sub-analyzer does not really report potential nil panics, the following 23 | // calls are just to ensure we add the upstream dependency and the sub-analyzer is able to 24 | // import facts about it. 25 | upstream.ExportedManual(nil) 26 | upstream.ExportedInferred(nil) 27 | } 28 | 29 | // This is a local function that has a contract that should be combined with the imported facts. 30 | // contract(nonnil -> nonnil) 31 | func localManual(p *int) *int { 32 | if p != nil { 33 | a := 1 34 | return &a 35 | } 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/testdata/src/go.uber.org/factexport/upstream/upstream.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package upstream 16 | 17 | // This tests the export of contracts from the upstream package. 18 | 19 | //contract(nonnil -> nonnil) 20 | func ExportedManual(p *int) *int { //want ExportedManual:"&\\[{\\[nonnil\\] \\[nonnil\\]}\\]" 21 | if p != nil { 22 | a := 1 23 | return &a 24 | } 25 | return nil 26 | } 27 | 28 | func ExportedInferred(p *int) *int { //want ExportedInferred:"&\\[{\\[nonnil\\] \\[nonnil\\]}\\]" 29 | if p != nil { 30 | a := 1 31 | return &a 32 | } 33 | return nil 34 | } 35 | 36 | //contract(nonnil -> nonnil) 37 | func unexportedManual(p *int) *int { // Notice here we do not want to export the contracts for it. 38 | if p != nil { 39 | a := 1 40 | return &a 41 | } 42 | return nil 43 | } 44 | 45 | func unexportedInferred(p *int) *int { // Notice here we do not want to export the contracts for it. 46 | if p != nil { 47 | a := 1 48 | return &a 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/testdata/src/go.uber.org/infer/external.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package infer 16 | 17 | // external is a function declaration whose body is defined outside of Go (e.g., assembly). The 18 | // SSA builder will not generate any blocks for this function, and we should handle this gracefully. 19 | func external(v *int) *int 20 | -------------------------------------------------------------------------------- /assertion/function/functioncontracts/testdata/src/go.uber.org/parse/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package parse 16 | 17 | // contract(nonnil -> nonnil) 18 | func f1(x *int) *int { 19 | if x == nil { 20 | return x 21 | } 22 | return new(int) 23 | } 24 | 25 | // contract(nonnil -> true) 26 | func f2(x *int) bool { 27 | if x == nil { 28 | return false 29 | } 30 | return true 31 | } 32 | 33 | // contract(nonnil -> false) 34 | func f3(x *int) bool { 35 | if x == nil { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | // contract(_, nonnil -> nonnil, true) 42 | func multipleValues(key string, deft *int) (*int, bool) { 43 | m := map[string]*int{} 44 | x, _ := m[key] 45 | if x != nil { 46 | return x, true 47 | } 48 | if deft != nil { 49 | return deft, true 50 | } 51 | return nil, false 52 | } 53 | 54 | // contract(_, nonnil -> nonnil, true) 55 | // contract(nonnil, _ -> nonnil, true) 56 | func multipleContracts(x *int, y *int) (*int, bool) { 57 | if x == nil && y == nil { 58 | return nil, false 59 | } 60 | return new(int), true 61 | } 62 | 63 | // This contract `// contract(nonnil -> nonnil)` does not hold for the function because the 64 | // function has no param or return. Only a contract in its own line should be parsed, not even `// 65 | // contract(nonnil -> nonnil)`. 66 | func contractCommentInOtherLine() {} 67 | -------------------------------------------------------------------------------- /assertion/function/preprocess/preprocessor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package preprocess hosts preprocessing logic for the input (e.g., CFGs etc.) to make it more 16 | // amenable to analysis. 17 | package preprocess 18 | 19 | import "golang.org/x/tools/go/analysis" 20 | 21 | // Preprocessor handles different preprocessing logic for different types of input. 22 | type Preprocessor struct { 23 | pass *analysis.Pass 24 | } 25 | 26 | // New returns a new Preprocessor. 27 | func New(pass *analysis.Pass) *Preprocessor { 28 | return &Preprocessor{pass: pass} 29 | } 30 | -------------------------------------------------------------------------------- /assertion/function/producer/deep_parsed_producer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package producer 16 | 17 | import "go.uber.org/nilaway/annotation" 18 | 19 | // DeepParsedProducer is a ParsedProducer that contains information about deeply produced values. 20 | // This information will only be read if the produced value turns out to be one of the following cases. 21 | // 1) DeepProducer for map or slice 22 | // 2) FieldProducers (struct or pointer to a struct): It holds the producers for each field of the struct. FieldProducers is either nil or 23 | // it has fixed size equal to the number of fields in the struct. Since, we only add producers for the field that can have 24 | // nil value (pointers, interfaces, slices, etc.), many of field producers will have nil value. 25 | // NOTE: If the array for FieldProducers results in increased memory usage of Nilaway, we can replace it with more compact 26 | // data structure in the future. 27 | type DeepParsedProducer struct { 28 | ShallowProducer *annotation.ProduceTrigger 29 | DeepProducer *annotation.ProduceTrigger 30 | FieldProducers []*annotation.ProduceTrigger 31 | } 32 | 33 | // GetShallow for a DeepParsedProducer returns the ProduceTrigger producing the value itself 34 | func (dp DeepParsedProducer) GetShallow() *annotation.ProduceTrigger { 35 | return dp.ShallowProducer 36 | } 37 | 38 | // GetDeep for a DeepParsedProducer returns the ProduceTrigger producing indices of the value 39 | func (dp DeepParsedProducer) GetDeep() *annotation.ProduceTrigger { 40 | return dp.DeepProducer 41 | } 42 | 43 | // GetFieldProducers returns field producers 44 | func (dp DeepParsedProducer) GetFieldProducers() []*annotation.ProduceTrigger { 45 | return dp.FieldProducers 46 | } 47 | 48 | // IsDeep for a DeepParsedProducer returns true 49 | func (dp DeepParsedProducer) IsDeep() bool { return true } 50 | 51 | // GetDeepSlice for a DeepParsedProducer returns a singular slice containing the deep ProduceTrigger 52 | func (dp DeepParsedProducer) GetDeepSlice() []*annotation.ProduceTrigger { 53 | return []*annotation.ProduceTrigger{dp.DeepProducer} 54 | } 55 | -------------------------------------------------------------------------------- /assertion/function/producer/parsed_producer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package producer contains definitions for parsed producers, which are the result of ParseExprAsProducer. 16 | package producer 17 | 18 | import "go.uber.org/nilaway/annotation" 19 | 20 | // ParsedProducer is one of the output objects of ParseExprAsProducer - it represents a production 21 | // of a value, interfaced to abstract away the potential to also include deep production of that 22 | // value, i.e. production of indices of that value 23 | type ParsedProducer interface { 24 | GetShallow() *annotation.ProduceTrigger 25 | GetDeep() *annotation.ProduceTrigger 26 | GetFieldProducers() []*annotation.ProduceTrigger 27 | IsDeep() bool 28 | 29 | // GetDeepSlice returns a 0 or 1 length slice; sometimes this is a more convenient representation 30 | GetDeepSlice() []*annotation.ProduceTrigger 31 | } 32 | -------------------------------------------------------------------------------- /assertion/function/producer/shallow_parsed_producer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package producer 16 | 17 | import "go.uber.org/nilaway/annotation" 18 | 19 | // ShallowParsedProducer is a ParsedProducer that does not contain information about deeply 20 | // produced values 21 | type ShallowParsedProducer struct { 22 | Producer *annotation.ProduceTrigger 23 | } 24 | 25 | // GetShallow for a ShallowParsedProducer contains the singular ProduceTrigger of this object 26 | func (sp ShallowParsedProducer) GetShallow() *annotation.ProduceTrigger { 27 | return sp.Producer 28 | } 29 | 30 | // GetDeep for a ShallowParsedProducer returns nil 31 | // nilable(result 0) 32 | func (sp ShallowParsedProducer) GetDeep() *annotation.ProduceTrigger { return nil } 33 | 34 | // GetFieldProducers returns nil as field producers 35 | func (sp ShallowParsedProducer) GetFieldProducers() []*annotation.ProduceTrigger { 36 | return nil 37 | } 38 | 39 | // IsDeep for a ShallowParsedProducer returns false 40 | func (sp ShallowParsedProducer) IsDeep() bool { return false } 41 | 42 | // GetDeepSlice for a ShallowParsedProducer returns an empty slice 43 | // nilable(result 0) 44 | func (sp ShallowParsedProducer) GetDeepSlice() []*annotation.ProduceTrigger { return nil } 45 | -------------------------------------------------------------------------------- /assertion/function/testdata/src/go.uber.org/pkg/main.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func Foo() {} 4 | -------------------------------------------------------------------------------- /assertion/global/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package global implements a sub-analyzer to create full triggers for global variables. 16 | package global 17 | 18 | import ( 19 | "go/ast" 20 | "go/token" 21 | "reflect" 22 | 23 | "go.uber.org/nilaway/annotation" 24 | "go.uber.org/nilaway/config" 25 | "go.uber.org/nilaway/util/analysishelper" 26 | "golang.org/x/tools/go/analysis" 27 | ) 28 | 29 | const _doc = "Nonnil global variables should be forced to provide a nonnil instantiation value at their declaration." 30 | 31 | // Analyzer checks if the nonnill global variables are initialized. 32 | var Analyzer = &analysis.Analyzer{ 33 | Name: "nilaway_global_var_analyzer", 34 | Doc: _doc, 35 | Run: analysishelper.WrapRun(run), 36 | ResultType: reflect.TypeOf((*analysishelper.Result[[]annotation.FullTrigger])(nil)), 37 | Requires: []*analysis.Analyzer{config.Analyzer}, 38 | } 39 | 40 | func run(pass *analysis.Pass) ([]annotation.FullTrigger, error) { 41 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 42 | 43 | if !conf.IsPkgInScope(pass.Pkg) { 44 | return nil, nil 45 | } 46 | 47 | var fullTriggers []annotation.FullTrigger 48 | for _, file := range pass.Files { 49 | if !conf.IsFileInScope(file) { 50 | continue 51 | } 52 | 53 | for _, decl := range file.Decls { 54 | genDecl, ok := decl.(*ast.GenDecl) 55 | if !ok || genDecl.Tok != token.VAR { 56 | continue 57 | } 58 | for _, spec := range genDecl.Specs { 59 | fullTriggers = append(fullTriggers, analyzeValueSpec(pass, spec.(*ast.ValueSpec))...) 60 | } 61 | } 62 | } 63 | 64 | return fullTriggers, nil 65 | } 66 | -------------------------------------------------------------------------------- /assertion/global/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package global 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "go.uber.org/goleak" 22 | "go.uber.org/nilaway/annotation" 23 | "go.uber.org/nilaway/util/analysishelper" 24 | ) 25 | 26 | func TestAnalyzer(t *testing.T) { 27 | t.Parallel() 28 | 29 | // Intentionally give a nil pass variable to trigger a panic, but we should recover from it 30 | // and convert it to an error via the result struct. 31 | r, err := Analyzer.Run(nil /* pass */) 32 | require.NoError(t, err) 33 | require.ErrorContains(t, r.(*analysishelper.Result[[]annotation.FullTrigger]).Err, "INTERNAL PANIC") 34 | } 35 | 36 | func TestMain(m *testing.M) { 37 | goleak.VerifyTestMain(m) 38 | } 39 | -------------------------------------------------------------------------------- /assertion/structfield/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package structfield implements a sub-analyzer that collects struct fields accessed within a 16 | // function to aid the analysis of the main function analyzer. 17 | package structfield 18 | 19 | import ( 20 | "go/ast" 21 | "reflect" 22 | 23 | "go.uber.org/nilaway/config" 24 | "go.uber.org/nilaway/util/analysishelper" 25 | "golang.org/x/tools/go/analysis" 26 | ) 27 | 28 | const _doc = "Collect relevant struct fields being assigned and/or accessed from within each function to later allow creation of triggers applicable to only those fields" 29 | 30 | // Analyzer collects struct fields accessed (e.g., assignments) from within a function. 31 | var Analyzer = &analysis.Analyzer{ 32 | Name: "nilaway_struct_field_analyzer", 33 | Doc: _doc, 34 | Run: analysishelper.WrapRun(run), 35 | ResultType: reflect.TypeOf((*analysishelper.Result[*FieldContext])(nil)), 36 | Requires: []*analysis.Analyzer{config.Analyzer}, 37 | } 38 | 39 | func run(pass *analysis.Pass) (*FieldContext, error) { 40 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 41 | 42 | fieldContext := &FieldContext{fieldMap: make(relevantFieldsMap)} 43 | 44 | if !conf.IsPkgInScope(pass.Pkg) { 45 | return fieldContext, nil 46 | } 47 | 48 | for _, file := range pass.Files { 49 | if !conf.IsFileInScope(file) { 50 | continue 51 | } 52 | 53 | for _, decl := range file.Decls { 54 | if funcDecl, ok := decl.(*ast.FuncDecl); ok { 55 | fieldContext.processFunc(funcDecl, pass) 56 | } 57 | } 58 | } 59 | 60 | return fieldContext, nil 61 | } 62 | -------------------------------------------------------------------------------- /assertion/structfield/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package structfield 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "go.uber.org/goleak" 22 | "go.uber.org/nilaway/util/analysishelper" 23 | ) 24 | 25 | func TestAnalyzer(t *testing.T) { 26 | t.Parallel() 27 | 28 | // Intentionally give a nil pass variable to trigger a panic, but we should recover from it 29 | // and convert it to an error via the result struct. 30 | r, err := Analyzer.Run(nil /* pass */) 31 | require.NoError(t, err) 32 | require.ErrorContains(t, r.(*analysishelper.Result[*FieldContext]).Err, "INTERNAL PANIC") 33 | } 34 | 35 | func TestMain(m *testing.M) { 36 | goleak.VerifyTestMain(m) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/gclplugin/gclplugin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package gclplugin implements the golangci-lint's module plugin interface for NilAway to be used 16 | // as a private linter in golangci-lint. See more details at 17 | // https://golangci-lint.run/plugins/module-plugins/. 18 | package gclplugin 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/golangci/plugin-module-register/register" 24 | "go.uber.org/nilaway" 25 | "go.uber.org/nilaway/config" 26 | "golang.org/x/tools/go/analysis" 27 | ) 28 | 29 | func init() { 30 | register.Plugin("nilaway", New) 31 | } 32 | 33 | // New returns the golangci-lint plugin that wraps the NilAway analyzer. 34 | func New(settings any) (register.LinterPlugin, error) { 35 | // Parse the settings to the correct type (map[string]string) similar to command line flags. 36 | s, ok := settings.(map[string]any) 37 | if !ok { 38 | return nil, fmt.Errorf("expect NilAway's configurations to a map from string to "+ 39 | "string (similar to command line flags), got %T", settings) 40 | } 41 | conf := make(map[string]string, len(s)) 42 | for k, v := range s { 43 | vStr, ok := v.(string) 44 | if !ok { 45 | return nil, fmt.Errorf("expect NilAway's configuration values for %q to be strings, got %T", k, v) 46 | } 47 | conf[k] = vStr 48 | } 49 | 50 | return &NilAwayPlugin{conf: conf}, nil 51 | } 52 | 53 | // NilAwayPlugin is the NilAway plugin wrapper for golangci-lint. 54 | type NilAwayPlugin struct { 55 | conf map[string]string 56 | } 57 | 58 | // BuildAnalyzers builds the NilAway analyzer with the configurations applied to the config analyzer. 59 | func (p *NilAwayPlugin) BuildAnalyzers() ([]*analysis.Analyzer, error) { 60 | // Apply the configurations to the config analyzer. 61 | for k, v := range p.conf { 62 | if err := config.Analyzer.Flags.Set(k, v); err != nil { 63 | return nil, fmt.Errorf("set config flag %s with %s: %w", k, v, err) 64 | } 65 | } 66 | 67 | return []*analysis.Analyzer{nilaway.Analyzer}, nil 68 | } 69 | 70 | // GetLoadMode returns the load mode of the NilAway plugin (requiring types info). 71 | func (p *NilAwayPlugin) GetLoadMode() string { return register.LoadModeTypesInfo } 72 | -------------------------------------------------------------------------------- /cmd/gclplugin/gclplugin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gclplugin 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/golangci/plugin-module-register/register" 21 | "github.com/stretchr/testify/require" 22 | "go.uber.org/nilaway" 23 | "go.uber.org/nilaway/config" 24 | ) 25 | 26 | func TestPlugin(t *testing.T) { 27 | t.Parallel() 28 | 29 | plugin, err := New(map[string]any{"pretty-print": "false"}) 30 | require.NoError(t, err) 31 | require.NotNil(t, plugin) 32 | 33 | require.Equal(t, register.LoadModeTypesInfo, plugin.GetLoadMode()) 34 | analyzers, err := plugin.BuildAnalyzers() 35 | require.NoError(t, err) 36 | require.Len(t, analyzers, 1) 37 | require.Equal(t, nilaway.Analyzer, analyzers[0]) 38 | 39 | // The config flag should be set to the value passed in the settings. 40 | require.Equal(t, "false", config.Analyzer.Flags.Lookup(config.PrettyPrintFlag).Value.String()) 41 | } 42 | 43 | func TestPlugin_IncorrectSettingsType(t *testing.T) { 44 | t.Parallel() 45 | 46 | plugin, err := New(map[string]any{"pretty-print": "false", "invalid": []string{"123", "234"}}) 47 | require.Error(t, err) 48 | require.Nil(t, plugin) 49 | } 50 | 51 | func TestPlugin_IncorrectSettings(t *testing.T) { 52 | t.Parallel() 53 | 54 | plugin, err := New(map[string]any{"invalid": "123"}) 55 | // The settings are applied when we build the analyzers, so the error should be thrown there. 56 | require.NoError(t, err) 57 | require.NotNil(t, plugin) 58 | 59 | analyzers, err := plugin.BuildAnalyzers() 60 | require.ErrorContains(t, err, "invalid") 61 | require.Empty(t, analyzers) 62 | } 63 | -------------------------------------------------------------------------------- /config/const.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | // This file hosts non-user-configurable parameters --- these are for development and testing purposes only. 18 | 19 | // StableRoundLimit is the number of rounds in backpropagation algorithm after which, if there is no change 20 | // in the collected triggers, the algorithm halts. It is possible to carefully craft known false negative for any value 21 | // of StableRoundLimit (check test loopflow.go/longRotNilLoop). Setting this value too low may result in false negatives 22 | // going undetected, while setting it too high may lead to longer analysis times without significant precision gains. 23 | // In practice, a value of StableRoundLimit >= 2 has shown to provide sound analysis, capturing most false negatives. 24 | // After experimentation, we observed that using StableRoundLimit = 5 with NilAway yields similar analysis time compared 25 | // to lower values, making it a good compromise for precise results. 26 | const StableRoundLimit = 5 27 | 28 | // NilAwayNoInferString is the string that may be inserted into the docstring for a package to prevent 29 | // NilAway from inferring the annotations for that package - this is useful for unit tests 30 | const NilAwayNoInferString = "" 31 | 32 | const uberPkgPathPrefix = "go.uber.org" 33 | 34 | // NilAwayPkgPathPrefix is the package prefix for NilAway. 35 | const NilAwayPkgPathPrefix = uberPkgPathPrefix + "/nilaway" 36 | 37 | // DirLevelsToPrintForTriggers controls the number of enclosing directories to print when referring 38 | // to the locations that triggered errors - right now it seems as if 1 is sufficient disambiguation, 39 | // but feel free to increase. 40 | const DirLevelsToPrintForTriggers = 1 41 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.uber.org/nilaway 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/golangci/plugin-module-register v0.1.1 9 | github.com/google/go-cmp v0.6.0 10 | github.com/klauspost/compress v1.18.0 11 | github.com/stretchr/testify v1.9.0 12 | go.uber.org/goleak v1.3.0 13 | golang.org/x/tools v0.31.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/kr/text v0.2.0 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/stretchr/objx v0.5.2 // indirect 21 | golang.org/x/mod v0.24.0 // indirect 22 | golang.org/x/sync v0.12.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= 5 | github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= 6 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 7 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 8 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 9 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 10 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 11 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 12 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 13 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 17 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 18 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 19 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 20 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 21 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 22 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 23 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 24 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 25 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 26 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 27 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 30 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /hook/no_return_call.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hook 16 | 17 | import ( 18 | "go/ast" 19 | "regexp" 20 | "slices" 21 | 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | // IsNoReturnCall returns whether the specific call expression terminates the program unconditionally. 26 | // It is used to model certain 3rd party or stdlib functions where the control flow construction 27 | // is not able to infer the function is no-return. For example: 28 | // 29 | // `zap.Fatal`-related: they have complex logic that eventually calls a hook that is almost always 30 | // configured to just panic (but we cannot infer that purely from code). 31 | // 32 | // `testing.TB.Fatal`-related: they are interface methods without implementations. 33 | func IsNoReturnCall(pass *analysis.Pass, call *ast.CallExpr) bool { 34 | return slices.ContainsFunc(_terminatingCalls, func(sig trustedFuncSig) bool { return sig.match(pass, call) }) 35 | } 36 | 37 | var _terminatingCalls = []trustedFuncSig{ 38 | // `zap.Logger.Fatal` 39 | { 40 | kind: _method, 41 | enclosingRegex: regexp.MustCompile(`^(stubs/)?go\.uber\.org/zap.Logger$`), 42 | funcNameRegex: regexp.MustCompile(`^Fatal$`), 43 | }, 44 | // `zap.SugaredLogger.Fatal` / `zap.SugaredLogger.Fatalf` / `zap.SugaredLogger.Fatalln` / `zap.SugaredLogger.Fatalw` 45 | { 46 | kind: _method, 47 | enclosingRegex: regexp.MustCompile(`^(stubs/)?go\.uber\.org/zap.SugaredLogger$`), 48 | funcNameRegex: regexp.MustCompile(`^Fatal(f|ln|w)?$`), 49 | }, 50 | // `testing.TB.Fatal` / `testing.TB.Fatalf` / `testing.TB.SkipNow` / `testing.TB.Skip` / `testing.TB.Skipf` 51 | // since it is an interface rather than a concrete implementation, the control flow analyzer 52 | // will not be able to infer that this is a no-return function. So, here we model it. 53 | { 54 | kind: _method, 55 | enclosingRegex: regexp.MustCompile(`^testing.TB$`), 56 | funcNameRegex: regexp.MustCompile(`^(Fatal|Fatalf|SkipNow|Skip|Skipf)$`), 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /hook/replace_conditional.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hook 16 | 17 | import ( 18 | "go/ast" 19 | "go/token" 20 | "regexp" 21 | 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | // ReplaceConditional replaces a call to a matched function with the returned expression. This is 26 | // useful for modeling stdlib and 3rd party functions that return a single boolean value, which 27 | // implies nilability of the arguments. For example, `errors.As(err, &target)` implies 28 | // `target != nil`, so it can be replaced with `target != nil`. 29 | // 30 | // If the call does not match any known function, nil is returned. 31 | func ReplaceConditional(pass *analysis.Pass, call *ast.CallExpr) ast.Expr { 32 | for sig, act := range _replaceConditionals { 33 | if sig.match(pass, call) { 34 | return act(pass, call) 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | type replaceConditionalAction func(pass *analysis.Pass, call *ast.CallExpr) ast.Expr 41 | 42 | // _errorAsAction replaces a call to `errors.As(err, &target)` with an equivalent expression 43 | // `errors.As(err, &target) && target != nil`. Keeping the `errors.As(err, &target)` is important 44 | // since `err` may contain complex expressions that may have nilness issues. 45 | // 46 | // Note that technically `target` can still be nil even if `errors.As(err, &target)` is true. For 47 | // example, if err is a typed nil (e.g., `var err *exec.ExitError`), then `errors.As` would 48 | // actually find a match, but `target` would be set to the typed nil value, resulting in a `nil` 49 | // target. However, in practice this should rarely happen such that even the official documentation 50 | // assumes the target is non-nil after such check [1]. So here we make this assumption as well. 51 | // 52 | // [1] https://pkg.go.dev/errors#As 53 | var _errorAsAction replaceConditionalAction = func(_ *analysis.Pass, call *ast.CallExpr) ast.Expr { 54 | if len(call.Args) != 2 { 55 | return nil 56 | } 57 | unaryExpr, ok := call.Args[1].(*ast.UnaryExpr) 58 | if !ok { 59 | return nil 60 | } 61 | if unaryExpr.Op != token.AND { 62 | return nil 63 | } 64 | return &ast.BinaryExpr{ 65 | X: call, 66 | Op: token.LAND, 67 | OpPos: call.Pos(), 68 | Y: newNilBinaryExpr(unaryExpr.X, token.NEQ), 69 | } 70 | } 71 | 72 | var _replaceConditionals = map[trustedFuncSig]replaceConditionalAction{ 73 | { 74 | kind: _func, 75 | enclosingRegex: regexp.MustCompile(`^errors$`), 76 | funcNameRegex: regexp.MustCompile(`^As$`), 77 | }: _errorAsAction, 78 | } 79 | -------------------------------------------------------------------------------- /inference/mode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package inference 16 | 17 | import ( 18 | "go.uber.org/nilaway/config" 19 | "go.uber.org/nilaway/util/asthelper" 20 | "golang.org/x/tools/go/analysis" 21 | ) 22 | 23 | // ModeOfInference is effectively an enum indicating the possible ways that we may conduct inference 24 | // for NilAway 25 | type ModeOfInference int 26 | 27 | const ( 28 | // NoInfer implies that all annotations sites are determined by syntactic annotations if present 29 | // and default otherwise 30 | NoInfer ModeOfInference = iota 31 | 32 | // FullInfer implies that no annotation site will be fixed before a sequence of assertions demands 33 | // it - this is the fully sound and complete version of inference: implication graphs are shared 34 | // between packages 35 | FullInfer 36 | ) 37 | 38 | // DetermineMode searches the files in this package for docstrings that indicate 39 | // inference should be entirely suppressed (returns NoInfer). By default, if no such 40 | // docstring is found, multi-package inference is used (returns FullInfer). 41 | func DetermineMode(pass *analysis.Pass) ModeOfInference { 42 | for _, file := range pass.Files { 43 | if asthelper.DocContains(file.Doc, config.NilAwayNoInferString) { 44 | return NoInfer 45 | } 46 | } 47 | return FullInfer 48 | } 49 | -------------------------------------------------------------------------------- /nilaway.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package nilaway implements the top-level analyzer that simply retrieves the diagnostics from 16 | // the accumulation analyzer and reports them. 17 | package nilaway 18 | 19 | import ( 20 | "go.uber.org/nilaway/accumulation" 21 | "go.uber.org/nilaway/config" 22 | "go.uber.org/nilaway/util" 23 | "golang.org/x/tools/go/analysis" 24 | ) 25 | 26 | const _doc = "Run NilAway on this package to report any possible flows of nil values to erroneous" + 27 | " sites that our system can detect" 28 | 29 | // Analyzer is the top-level instance of Analyzer - it coordinates the entire dataflow to report 30 | // nil flow errors in this package. It is needed here for nogo to recognize the package. 31 | var Analyzer = &analysis.Analyzer{ 32 | Name: "nilaway", 33 | Doc: _doc, 34 | Run: run, 35 | FactTypes: []analysis.Fact{}, 36 | Requires: []*analysis.Analyzer{config.Analyzer, accumulation.Analyzer}, 37 | } 38 | 39 | func run(pass *analysis.Pass) (interface{}, error) { 40 | conf := pass.ResultOf[config.Analyzer].(*config.Config) 41 | deferredErrors := pass.ResultOf[accumulation.Analyzer].([]analysis.Diagnostic) 42 | for _, e := range deferredErrors { 43 | if conf.PrettyPrint { 44 | e.Message = util.PrettyPrintErrorMessage(e.Message) 45 | } 46 | pass.Report(e) 47 | } 48 | 49 | return nil, nil 50 | } 51 | -------------------------------------------------------------------------------- /nilawaytest/testhelper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package nilawaytest implements utility functions for tests. 16 | package nilawaytest 17 | 18 | import ( 19 | "go/ast" 20 | "strings" 21 | 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | // FindExpectedValues inspects test files and gathers expected values' comment strings 26 | func FindExpectedValues(pass *analysis.Pass, expectedPrefix string) map[ast.Node][]string { 27 | results := make(map[ast.Node][]string) 28 | 29 | for _, file := range pass.Files { 30 | 31 | // Store a mapping between single comment's line number to its text. 32 | comments := make(map[int]string) 33 | for _, group := range file.Comments { 34 | if len(group.List) != 1 { 35 | continue 36 | } 37 | comment := group.List[0] 38 | comments[pass.Fset.Position(comment.Pos()).Line] = comment.Text 39 | } 40 | 41 | // Now, find all nodes of interest, such as *ast.FuncLit and *ast.FuncDecl, and find their comment. 42 | ast.Inspect(file, func(node ast.Node) bool { 43 | isExpectedNode := false 44 | switch node.(type) { 45 | case *ast.FuncLit, *ast.FuncDecl: 46 | isExpectedNode = true 47 | } 48 | if !isExpectedNode { 49 | return true 50 | } 51 | 52 | text, ok := comments[pass.Fset.Position(node.Pos()).Line] 53 | if !ok { 54 | // It is ok to not leave annotations for a node - it simply does not use 55 | // any closure variables. We still need to traverse further since there could be 56 | // comments for nested func lit nodes. 57 | return true 58 | } 59 | 60 | // Trim the trailing slashes and extra spaces and extract the set of expected values. 61 | text = strings.TrimSpace(strings.TrimPrefix(text, "//")) 62 | text = strings.TrimSpace(strings.TrimPrefix(text, expectedPrefix)) 63 | // If no expected values are written after the `expectedPrefix`, we simply ignore it. 64 | results[node] = nil 65 | if len(text) != 0 { 66 | results[node] = strings.Split(text, " ") 67 | } 68 | return true 69 | }) 70 | } 71 | 72 | return results 73 | } 74 | -------------------------------------------------------------------------------- /testdata/integration/.custom-gcl.template.yaml: -------------------------------------------------------------------------------- 1 | # This will be filled by our integration test driver as the version listed in /.golangci.version 2 | # (which is the gcl version we use for linting NilAway's codebase). This would ensure that we are 3 | # using the same version of gcl across the codebase and the integration test driver. 4 | version: {{.GCLVersion}} 5 | 6 | plugins: 7 | - module: "go.uber.org/nilaway" 8 | import: "go.uber.org/nilaway/cmd/gclplugin" 9 | path: {{.NilAwayPath}} 10 | -------------------------------------------------------------------------------- /testdata/integration/.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - nilaway 6 | settings: 7 | custom: 8 | nilaway: 9 | type: module 10 | description: Static analysis tool to detect potential nil panics in Go code. 11 | settings: 12 | pretty-print: "false" 13 | -------------------------------------------------------------------------------- /testdata/integration/README.md: -------------------------------------------------------------------------------- 1 | # NilAway Integration Test Folder 2 | 3 | To prevent functionality regressions when developing NilAway, we have leveraged `analysistest` 4 | test framework to write a extensive test suite (`testdata/src` folder). However, it has two major 5 | limitations that make it undesirable to ensure real-world behaviors: 6 | 7 | * It does not read the "want" comments in the upstream package when analyzing downstream packages. 8 | This makes it impossible to test our multi-package inference algorithm: NilAway might report an 9 | error for an upstream package while analyzing downstream packages, but we cannot write expected 10 | error strings for the framework to verify; 11 | * For bazel/nogo, the package loader and [`Fact`][fact] caching behavior is slightly different from 12 | bazel (which under the hood uses a custom nogo runner that is different from the `analysistest` 13 | runner), and there is no guarantee that they will not diverge further in the future. 14 | 15 | Therefore, this `integration` folder serves as a buildable Go project (both by standard Go 16 | toolchain and bazel) that contains multiple potential nil panics with `want` comments. We build 17 | this project with different drivers and check if the final output matches the desired output. It 18 | is meant to be built and tested with our integration test driver (`make integration-test`, where 19 | the code is at `tools/cmd/integration-test`), but it can also be built separately to see the NilAway 20 | outputs. This also serves as an example of how to use NilAway in a real-world project. 21 | 22 | Below shows how to run NilAway on this example project outside the integration test framework for 23 | illustrating purposes. The integration test framework basically follows the same steps, but with 24 | an extra step to verify if the output errors match the `want` comments. 25 | 26 | ## Using Standalone Checker 27 | 28 | Since this integration test project follows the standard Go project layout, you can simply run 29 | NilAway on it with the following command: 30 | 31 | ```shell 32 | # Build NilAway 33 | make build 34 | # Run NilAway on the integration test project 35 | cd testdata/integration 36 | ../../bin/nilaway ./... 37 | ``` 38 | 39 | [fact]: https://pkg.go.dev/golang.org/x/tools/go/analysis/internal/facts 40 | -------------------------------------------------------------------------------- /testdata/integration/contracts/downstream/downstream.go: -------------------------------------------------------------------------------- 1 | package downstream 2 | 3 | import "go.uber.org/nilaway/integration/contracts/upstream" 4 | 5 | func NonnilToNonnil(v *int) *int { 6 | if v != nil { 7 | a := 1 8 | return &a 9 | } 10 | return nil 11 | } 12 | 13 | func GiveNil() { 14 | r := NonnilToNonnil(nil) 15 | print(*r) //want "result 0 of `NonnilToNonnil\(\)` dereferenced" 16 | } 17 | 18 | func GiveNonnil() { 19 | a := 1 20 | r := NonnilToNonnil(&a) 21 | print(*r) // Safe! 22 | } 23 | 24 | func GiveUpstreamNil() { 25 | r := upstream.NonnilToNonnil(nil) 26 | print(*r) //want "result 0 of `NonnilToNonnil\(\)` dereferenced" 27 | } 28 | 29 | func GiveUpstreamNonnil() { 30 | a := 1 31 | r := upstream.NonnilToNonnil(&a) 32 | print(*r) // Safe due to the contract! 33 | } 34 | -------------------------------------------------------------------------------- /testdata/integration/contracts/upstream/upstream.go: -------------------------------------------------------------------------------- 1 | package upstream 2 | 3 | // NonnilToNonnil returns a nonnil pointer if the input is nonnil, otherwise a nil pointer is 4 | // returned. It is meant to be called in downstream packages. 5 | func NonnilToNonnil(v *int) *int { 6 | if v != nil { 7 | a := 1 8 | return &a 9 | } 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /testdata/integration/go.mod: -------------------------------------------------------------------------------- 1 | module go.uber.org/nilaway/integration 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /testdata/integration/simple/downstream/downstream.go: -------------------------------------------------------------------------------- 1 | package downstream 2 | 3 | import "go.uber.org/nilaway/integration/simple/upstream" 4 | 5 | func test() { 6 | print(*upstream.NilableValue) //want "global variable `NilableValue` dereferenced" 7 | 8 | v := upstream.NilableFunc() 9 | print(*v) //want "result 0 of `NilableFunc\(\)` dereferenced" 10 | 11 | upstream.NonnilParam(nil) 12 | 13 | var s *upstream.S 14 | s.NonnilRecv() 15 | s.NilableRecv() // safe 16 | } 17 | 18 | 19 | func GiveUpstreamDeref() { 20 | // Nil source is in the downstream package. However, the nil sink (dereference) is happening 21 | // in the upstream package. NilAway should report the violation in the upstream package _when_ 22 | // analyzing the downstream package. 23 | upstream.Deref(nil) 24 | } 25 | 26 | func GiveUpstreamDerefNoLint() { 27 | // Similar to `GiveUpstreamDeref`. However, here the upstream package has `//nolint` mark for 28 | // NilAway and therefore NilAway should not report the violation. This is to test our 29 | // cross-package nolint suppression support. 30 | upstream.DerefNoLintLine(nil) 31 | upstream.DerefNoLintFunc(nil) 32 | upstream.DerefNoLintFile(nil) 33 | } 34 | 35 | func localNoLintLint() { 36 | var p *int 37 | print(*p) //nolint:nilaway 38 | print(*p) //nolint:all 39 | print(*p) // nolint : nilaway // Explanation 40 | print(*p) //nolint 41 | print(*p) ////nolint:nilaway 42 | } 43 | 44 | //nolint:nilaway 45 | func localNoLintFunc() { 46 | var p *int 47 | print(*p) 48 | print(*p) 49 | print(*p) 50 | } 51 | -------------------------------------------------------------------------------- /testdata/integration/simple/downstream/nolint.go: -------------------------------------------------------------------------------- 1 | //nolint:nilaway 2 | package downstream 3 | 4 | func localNoLintFile() { 5 | var p *int 6 | print(*p) 7 | } 8 | -------------------------------------------------------------------------------- /testdata/integration/simple/upstream/nolint.go: -------------------------------------------------------------------------------- 1 | //nolint:nilaway 2 | package upstream 3 | 4 | func DerefNoLintFile(v *int) { 5 | print(*v) 6 | } 7 | -------------------------------------------------------------------------------- /testdata/integration/simple/upstream/upstream.go: -------------------------------------------------------------------------------- 1 | package upstream 2 | 3 | var _caseNo int 4 | 5 | var NilableValue *int = nil 6 | 7 | func NilableFunc() *int { return nil } 8 | 9 | func NonnilParam(v *int) { 10 | print(*v) //want "function parameter `v` dereferenced" 11 | } 12 | 13 | type S struct { 14 | f int 15 | } 16 | 17 | func (s *S) NilableRecv() { 18 | if s == nil { 19 | print("s is nil") 20 | } else { 21 | print(s.f) 22 | } 23 | } 24 | 25 | func (s *S) NonnilRecv() { 26 | print(s.f) //want "read by method receiver `s` accessed field `f`" 27 | } 28 | 29 | func dereference() { 30 | var v *int 31 | print(*v) //want "unassigned variable `v` dereferenced" 32 | } 33 | 34 | func DerefNoLintLine(v *int) { 35 | print(*v) //nolint:nilaway 36 | print(*v) // nolint:all 37 | print(*v) // nolint: nilaway 38 | print(*v) ////nolint:nilaway 39 | print(*v) //nolint 40 | } 41 | 42 | //nolint:nilaway 43 | func DerefNoLintFunc(v *int) { 44 | print(*v) 45 | } 46 | 47 | func Deref(v *int, ) { 48 | switch _caseNo { 49 | case 1: 50 | print(*v) //want "function parameter `v` dereferenced" 51 | case 2: 52 | print(*v) //nolint:other_linter //want "function parameter `v` dereferenced" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/anonymousfunction/anonymous_functions_assignment.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package aims to test nilability behavior for simple cases in anonymous functions. 16 | package anonymousfunction 17 | 18 | func testSimpleAssignment() { 19 | var t1 *int 20 | a := func() { 21 | print(*t1) //want "unassigned variable `t1`" 22 | } 23 | a() // at this call t1 is nil 24 | 25 | var t2 *int 26 | b := func() { 27 | print(*t1) 28 | print(*t2) //want "function parameter `t2` dereferenced" 29 | } 30 | 31 | i := 1 32 | t1 = &i 33 | t2 = nil 34 | 35 | b() // at this call t1 is not nil but t2 is nil 36 | } 37 | 38 | func testAssignOneToOneAssingments() { 39 | var t1 *int 40 | a, b := func(t *int) { 41 | print(*t) //want "unassigned variable `t1`" 42 | }, func(t2 *int) { 43 | print(*t2) //want "unassigned variable `t1`" 44 | } 45 | a(t1) 46 | b(t1) 47 | 48 | } 49 | 50 | func testNestedAnonymousFuncAssignment() { 51 | var t1 *int 52 | var t2 *int 53 | a := func() { 54 | print(*t1) //want "unassigned variable `t1`" 55 | var t2 *int 56 | print(*t2) //want "unassigned variable `t2`" 57 | b := func() { 58 | print(*t1) //want "unassigned variable `t1`" 59 | print(*t2) //want "unassigned variable `t2`" 60 | if t2 != nil { 61 | print(*t2) // this is ok 62 | } 63 | } 64 | b() // at this call t1 and t2 are both nil 65 | } 66 | i := 1 67 | t2 = &i 68 | print(*t2) 69 | a() // at this call t1 is nil 70 | } 71 | 72 | func testImplicitAndExplicitArguments() { 73 | var t1 *int 74 | var t2 *int 75 | f := func(t *int) { 76 | print(*t) //want "unassigned variable `t2`" 77 | // the following erros are for t3 and p2 78 | g := func(p *int) { 79 | print(*t) 80 | print(*p) //want "unassigned variable `t1`" 81 | print(*t2) //want "unassigned variable `t2`" 82 | } 83 | i := 1 84 | t = &i 85 | g(t1) 86 | } 87 | 88 | f(t2) 89 | } 90 | 91 | func testShadowedClosureVariable() { 92 | var a *int 93 | i := 1 94 | b := &i 95 | 96 | func() { 97 | print(*a) //want "unassigned variable `a`" 98 | func(a *int) { 99 | print(*a) // this is actually ok since `a` is shadowed and we are passing a nonnil argument at the call site. 100 | }(b) 101 | }() 102 | } 103 | 104 | // nonnil(i) 105 | func testWithNamedReturn(i *int) (r *int) { 106 | print(*i) // safe 107 | r = i 108 | k := func() { 109 | print(*r) // safe 110 | } 111 | k() 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/anonymousfunction/argument_passing_explicitly.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package aims to test nilability behavior for simple cases in anonymous functions. 16 | package anonymousfunction 17 | 18 | func testPassingArgsExplicityToAnonFuncs() { 19 | // Now we test uses of variables in the closure in anonymous functions. This is a simple test 20 | // such that all used variables are explicitly passed as params in to the anonymous functions. 21 | 22 | // Passing nilable variables as parameters. 23 | var t1 *int 24 | func(t *int) { 25 | print(*t) //want "unassigned variable `t1`" 26 | }(t1) 27 | 28 | func(t *int) { 29 | if t != nil { 30 | print(*t) 31 | } 32 | }(t1) 33 | 34 | // Pass a nonnil struct as parameter 35 | t2 := A{} 36 | func(t *A) *int { 37 | return t.a 38 | }(&t2) 39 | 40 | // Pass nilable struct as parameter. 41 | var t3 *A 42 | func(t *A) *int { 43 | return t.a //want "unassigned variable `t3`" 44 | }(t3) 45 | 46 | // test nested anonymous function 47 | 48 | var t4 *int 49 | func(t *int) { 50 | var t2 *int 51 | func(n1 *int, n2 *int) { 52 | print(*n1) //want "unassigned variable `t4`" 53 | print(*n2) //want "unassigned variable `t2`" 54 | }(t, t2) 55 | }(t4) 56 | 57 | c := 1 58 | t5 := &c // t5 is nonnil now 59 | func(t *int) { 60 | print(*t) 61 | var t2 *int 62 | // the following error is coming from n2 but not from n1 63 | func(n1 *int, n2 *int) { 64 | print(*n1) // this should be ok 65 | print(*n2) //want "unassigned variable `t2`" 66 | if n2 != nil { 67 | print(*n2) // this is ok 68 | } 69 | }(t, t2) 70 | }(t5) 71 | } 72 | 73 | type B struct{} 74 | 75 | func (b *B) testOverWriteReceiver() { 76 | b = nil 77 | func() { 78 | print(*b) //want "literal `nil`" 79 | }() 80 | } 81 | 82 | func (b *B) testPassReceiver() { 83 | func(nb *B) { 84 | print(*nb) //want "unassigned variable `b`" 85 | }(b) 86 | } 87 | 88 | func (b *B) testGuardReceiver() { 89 | if b == nil { 90 | return 91 | } 92 | func(nb *B) { 93 | print(*nb) // this is ok 94 | }(b) 95 | } 96 | 97 | func nilReceiverTestDriver() { 98 | // This calls B's methods with a nil receiver to test handling receivers in closure. 99 | var b *B 100 | b.testPassReceiver() 101 | b.testGuardReceiver() 102 | } 103 | 104 | func (a *A) testNonNilReceiver() { 105 | func(na *A) { 106 | print(*na) // this is also ok 107 | }(&A{}) 108 | } 109 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/anonymousfunction/argument_passing_implicitly.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package aims to test nilability behavior for simple cases in anonymous functions. 16 | package anonymousfunction 17 | 18 | func testNilFlowFromClosure() { 19 | 20 | var t *int 21 | func() { 22 | print(*t) //want "unassigned variable `t`" 23 | }() 24 | 25 | i := 1 26 | t = &i 27 | func() { 28 | print(*t) 29 | }() 30 | 31 | func() { 32 | print(*t) 33 | print(*t) 34 | }() 35 | 36 | t = nil 37 | 38 | func() { 39 | print(*t) //want "literal `nil`" 40 | print(*t) //want "literal `nil`" 41 | }() 42 | 43 | t = &i 44 | 45 | func() { 46 | print(*t) 47 | t = nil 48 | print(*t) //want "literal `nil`" 49 | }() 50 | 51 | // TODO we will report an error here after updating the return type of function literals to include variables from closure 52 | print(*t) 53 | 54 | // test nested anonymous functions 55 | 56 | var t1 *int 57 | func() { 58 | var t2 *int 59 | func() { 60 | print(*t1) //want "unassigned variable `t1`" 61 | print(*t2) //want "unassigned variable `t2`" 62 | }() 63 | }() 64 | 65 | c := 1 66 | t3 := &c // t5 is nonnil now 67 | func() { 68 | print(*t3) 69 | var t4 *int 70 | // the following error is coming from t4 but not from t3 71 | func() { 72 | print(*t3) // this should be ok 73 | print(*t4) //want "unassigned variable `t4`" 74 | if t4 != nil { 75 | print(*t4) // this is ok 76 | } 77 | }() 78 | }() 79 | 80 | var t5 *int 81 | func() { 82 | print(*t1) //want "unassigned variable `t1`" 83 | print(*t3) 84 | print(*t5) //want "unassigned variable `t5`" 85 | }() 86 | 87 | } 88 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/anonymousfunction/rich_check_effect.go: -------------------------------------------------------------------------------- 1 | package anonymousfunction 2 | 3 | var dummy bool 4 | 5 | type myErr2 struct{} 6 | 7 | func (myErr2) Error() string { return "myErr2 message" } 8 | 9 | var globalFunc = func() (*int, error) { 10 | if dummy { 11 | return nil, &myErr2{} 12 | } 13 | return new(int), nil 14 | } 15 | 16 | func testAnonErrReturningFunc(i int) { 17 | f1 := func() (*int, error) { 18 | if dummy { 19 | return nil, &myErr2{} 20 | } 21 | return new(int), nil 22 | } 23 | 24 | f2 := func() (*int, error) { 25 | if dummy { 26 | return new(int), &myErr2{} 27 | } 28 | return new(int), nil 29 | } 30 | 31 | f3 := func() (*int, error) { 32 | if dummy { 33 | return nil, &myErr2{} 34 | } 35 | return nil, nil 36 | } 37 | 38 | switch i { 39 | case 1: 40 | x, err := f1() 41 | if err != nil { 42 | return 43 | } 44 | _ = *x 45 | 46 | case 2: 47 | if x, err := f1(); err != nil { 48 | _ = *x // want "dereferenced" 49 | } 50 | 51 | case 3: 52 | x, err := f2() 53 | if err != nil { 54 | // safe since f2() always returns a non-nil value 55 | _ = *x 56 | } 57 | 58 | case 4: 59 | x, err := f3() 60 | if err != nil { 61 | return 62 | } 63 | // unsafe since f3() always returns a nil value 64 | _ = *x // want "dereferenced" 65 | 66 | case 5: 67 | x, err := func() (*int, error) { 68 | if dummy { 69 | return nil, &myErr2{} 70 | } 71 | return new(int), nil 72 | }() 73 | if err != nil { 74 | return 75 | } 76 | _ = *x 77 | 78 | case 6: 79 | if x2, err2 := func() (*int, error) { 80 | if dummy { 81 | return nil, &myErr2{} 82 | } 83 | return new(int), nil 84 | }(); err2 != nil { 85 | _ = *x2 // want "dereferenced" 86 | } 87 | 88 | case 7: 89 | x, err := func() (*int, error) { 90 | if dummy { 91 | return new(int), &myErr2{} 92 | } 93 | return new(int), nil 94 | }() 95 | if err != nil { 96 | // safe since f2() always returns a non-nil value 97 | _ = *x 98 | } 99 | 100 | case 8: 101 | x, err := func() (*int, error) { 102 | if dummy { 103 | return nil, &myErr2{} 104 | } 105 | return nil, nil 106 | }() 107 | if err != nil { 108 | return 109 | } 110 | // unsafe since the anonymous function always returns a nil value 111 | _ = *x // want "dereferenced" 112 | 113 | case 9: 114 | // TODO: fix this false positive case by handling global anonymous function 115 | x, err := globalFunc() 116 | if err != nil { 117 | return 118 | } 119 | _ = *x // want "dereferenced" 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/anonymousfunction/simple.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package aims to test nilability behavior for simple cases in anonymous functions. 16 | package anonymousfunction 17 | 18 | // We currently do not parse annotations for anonymous functions, and there is no simple way to 19 | // do that, so we rely on the inference engine in the tests. A few tricks are used to make 20 | // sure inference engine will correctly infer a site as nilable or nonnil. 21 | // (1) nilable: we will use an uninitialized variable, which will definitely be inferred as 22 | // nilable, to enforce nilable values. 23 | // (2) nonnil: we will use a simple dereference `print(*ptr)` to enforce nonnil values. 24 | 25 | // nilable(a, b) 26 | // nonnil(c) 27 | // nonnil(d) 28 | type A struct { 29 | a *int 30 | b *A 31 | c *A 32 | d *int 33 | } 34 | 35 | func retNilable() *int { 36 | return nil 37 | } 38 | 39 | func simple() { 40 | // Here we test nilability analysis _inside_ the anonymous functions, where no interactions 41 | // happen between the anonymous functions and the outside world. 42 | aNonnilPtr := &A{} 43 | print(*(aNonnilPtr.a)) //want "it is annotated" 44 | 45 | func() { 46 | var t *int 47 | print(*t) //want "unassigned variable `t`" 48 | t2 := 1 49 | ptr := &t2 50 | print(*ptr) 51 | t3 := retNilable() 52 | print(*t3) //want "result 0 of `retNilable.*` dereferenced" 53 | var aPtr *A 54 | print(*aPtr) //want "unassigned variable `aPtr`" 55 | aNonnilPtr := &A{} 56 | print(*(aNonnilPtr.a)) //want "it is annotated" 57 | // A.c is marked as nonnil, so it is ok to dereference. 58 | print(aNonnilPtr.c.a) 59 | }() 60 | 61 | a := func() { 62 | var t *int 63 | print(*t) //want "unassigned variable `t`" 64 | } 65 | print(a) 66 | 67 | var f func() = func() { 68 | var t *int 69 | print(*t) //want "unassigned variable `t`" 70 | } 71 | 72 | f() 73 | } 74 | 75 | // test nested anonymous functions 76 | func nestedFunc() { 77 | func() { 78 | func() { 79 | var t *int 80 | print(*t) //want "unassigned variable `t`" 81 | }() 82 | a := func() { 83 | var t *int 84 | print(*t) //want "unassigned variable `t`" 85 | } 86 | print(a) 87 | }() 88 | 89 | func() { 90 | func() { 91 | func() { 92 | var t *int 93 | print(*t) //want "unassigned variable `t`" 94 | }() 95 | }() 96 | }() 97 | } 98 | 99 | var a = func() { 100 | var t *int 101 | print(*t) //want "unassigned variable `t`" 102 | } 103 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/consts/consts.go: -------------------------------------------------------------------------------- 1 | // This packages tests the behavior of nilaway on constants. 2 | // 3 | // 4 | package consts 5 | 6 | import ( 7 | "math" 8 | 9 | "go.uber.org/consts/lib" 10 | ) 11 | 12 | var dummy bool 13 | 14 | // tests for checking index expressions as constants both built-in and user defined (declared locally and in another package) 15 | // nonnil(mp, mp[]) 16 | func testConst(mp map[string]*string, i int) string { 17 | switch i { 18 | case 0: 19 | // local const 20 | const key = "key" 21 | if mp[key] == nil || *mp[key] == "" { 22 | return "nil" 23 | } else { 24 | return *mp[key] 25 | } 26 | case 1: 27 | // local const created from a another package const 28 | const key = lib.MyStrConst 29 | if mp[key] == nil || *mp[key] == "" { 30 | return "nil" 31 | } else { 32 | return *mp[key] 33 | } 34 | case 2: 35 | // another package const 36 | if mp == nil || mp[lib.MyStrConst] == nil || *mp[lib.MyStrConst] == "" { 37 | return "nil" 38 | } else { 39 | return *mp[lib.MyStrConst] 40 | } 41 | case 3: 42 | var v = lib.MyStrConst 43 | if mp[v] == nil || *mp[v] == "" { 44 | return "nil" 45 | } 46 | case 4: 47 | // write and read from the same map 48 | if dummy { 49 | mp[lib.MyStrConst] = new(string) 50 | return *mp[lib.MyStrConst] 51 | } 52 | mp[lib.MyStrConst] = nil //want "assigned" 53 | return *mp[lib.MyStrConst] //want "dereferenced" 54 | case 5: 55 | // built-in 56 | mp2 := make(map[float64]*string) 57 | if mp2[math.Pi] == nil || *mp2[math.Pi] == "" { 58 | return "nil" 59 | } else { 60 | return *mp2[math.Pi] 61 | } 62 | } 63 | return "" 64 | } 65 | 66 | var unexportedGlobalVar string = "local" 67 | 68 | // tests for checking the behavior of indexing with a global variable. 69 | // nonnil(mp, mp[]) 70 | func testGlobalVar(mp map[string]*string, i int) string { 71 | switch i { 72 | case 0: 73 | // locally defined unexported global variable 74 | if mp[unexportedGlobalVar] == nil || *mp[unexportedGlobalVar] == "" { 75 | return "nil" 76 | } else { 77 | return *mp[unexportedGlobalVar] 78 | } 79 | case 2: 80 | // global variable defined in another package 81 | if mp == nil || mp[lib.MyGlobalVar] == nil || *mp[lib.MyGlobalVar] == "" { 82 | return "nil" 83 | } else { 84 | return *mp[lib.MyGlobalVar] 85 | } 86 | } 87 | return "" 88 | } 89 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/consts/lib/const.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | const MyStrConst = "xyz" 4 | 5 | var MyGlobalVar = "abc" 6 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/contracts/namedtypes/namedtypes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package namedtypes: This package tests that named types are correctly handled for contract types (e.g., error 16 | // returning functions and ok-form for functions) 17 | package namedtypes 18 | 19 | // the below test uses the built-in name `bool` for creating a user-defined named type. However, the logic for determining 20 | // an ok-form function should not depend on the name `bool`, but the underlying type. This test ensures that the logic. 21 | type bool int 22 | 23 | func retPtrBoolNamed() (*int, bool) { 24 | return nil, 0 25 | } 26 | 27 | func testNamedBool() { 28 | if v, ok := retPtrBoolNamed(); ok == 0 { 29 | _ = *v // want "dereferenced" 30 | } 31 | } 32 | 33 | // Similar to the above test, but with the built-in name `error` 34 | type error int 35 | 36 | func retPtrErrorNamed() (*int, error) { 37 | return nil, 0 38 | } 39 | 40 | func testNamedError() { 41 | if v, ok := retPtrErrorNamed(); ok == 0 { 42 | _ = *v // want "dereferenced" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/errorreturn/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package errors 17 | 18 | type any interface{} 19 | 20 | // these stubs simulate the real `errors` package because we can't import it in tests 21 | 22 | type err struct{} 23 | 24 | func (err) Error() string { return "err message" } 25 | 26 | // nonnil(result 0) 27 | func New(text string) error { return err{} } 28 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/errorreturn/fmt/fmt.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package fmt 17 | 18 | type any interface{} 19 | 20 | // these stubs simulate the real `fmt` package because we can't import it in tests 21 | 22 | type err struct{} 23 | 24 | func (err) Error() string { return "err message" } 25 | 26 | // nonnil(result 0) 27 | func Errorf(format string, a ...any) error { return err{} } 28 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/errorreturn/inference/downstream.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package inference 16 | 17 | import "go.uber.org/errorreturn/inference/upstream" 18 | 19 | type dummyCallback struct{} 20 | 21 | func (dcb dummyCallback) GetErrorObj() error { 22 | return nil 23 | } 24 | 25 | func foo() { 26 | // False negative case! The reason why the error here does not get reported here is because when analyzing package `upstream`, 27 | // the non-error trigger for function `problem` is removed by `FilterTriggersForErrorReturn`. Why? Because the annotation site 28 | // `Result 0 of GetErrorObj` is found to be `Undetermined`, really unknown in this case, since `upstream` does not have the 29 | // implementation of `GetErrorObj`. The compute function passed to `FilterTriggersForErrorReturn` in`inferred_annotation_map.go` 30 | // treats all undetermined sites to be non-nil. This is actually a fair assumption based on the understanding that the 31 | // inference algorithm does not propagate non-nil forward, meaning that the sites are interpreted as "nonnil-hence-left-undetermined". 32 | // Now when analyzing `downstream.go`, NilAway will indeed realize that `Callback.GetErrorObj()` should be nilable, but by that point, 33 | // the function `problem` will never be analyzed again, and the error goes unreported. 34 | // 35 | // We are leaving this case as a known limitation in NilAway, since we believe this coding pattern to be rare in practice. 36 | // (This issue is tracked in 37 | 38 | _ = *upstream.EntryPoint(&dummyCallback{}) // error should be reported here 39 | } 40 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/errorreturn/inference/otherPkg/otherFile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package otherPkg 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | ) 21 | 22 | func RetErr() error { 23 | return errors.New("some exported error message") 24 | } 25 | 26 | var GlobalErrorFromOtherPkg = fmt.Errorf("some other exported error message") 27 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/errorreturn/inference/upstream/upstream.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Upstream package 16 | package upstream 17 | 18 | type Callback interface { 19 | GetErrorObj() error 20 | } 21 | 22 | // note: Unexported 23 | func problem(cb Callback) (*int, error) { 24 | err := cb.GetErrorObj() 25 | return nil, err 26 | } 27 | 28 | func EntryPoint(cb Callback) *int { 29 | v, err := problem(cb) 30 | if err != nil { 31 | i := 0 32 | return &i 33 | } 34 | return v 35 | } 36 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/functioncontracts/inference/written_contracts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package inference 16 | 17 | // This file tests if NilAway's capability to respect the written contracts in full inference mode. 18 | 19 | import "math/rand" 20 | 21 | // We set an incorrect manual contract here, but NilAway should still respect it. 22 | // TODO: NilAway should validate and warn users of incorrect manual contract annotations. 23 | // contract(nonnil -> nonnil) 24 | func incorrectContract(x *int) *int { 25 | if x != nil { 26 | // Returns nil if the input is nonnil, violating the contract. 27 | return nil 28 | } 29 | // Return nonnil or nil randomly 30 | if rand.Float64() > 0.5 { 31 | return new(int) 32 | } else { 33 | return nil 34 | } 35 | } 36 | 37 | func useIncorrectContract1() { 38 | n := 1 39 | a1 := &n 40 | b1 := incorrectContract(a1) 41 | print(*b1) // No error here due to the contract. 42 | } 43 | 44 | func useIncorrectContract2() { 45 | var a2 *int 46 | b2 := incorrectContract(a2) 47 | // TODO: NilAway is reporting the same error below twice. This will be grouped into one error 48 | // in real world due to our diagnostic engine. However, this is still a bug in contract 49 | // handling. We should investigate and fix this. 50 | print(*b2) // want "result 0 of `incorrectContract.*` .* dereferenced" "result 0 of `incorrectContract.*` .* dereferenced" 51 | } 52 | 53 | // Contract below isn't useful, since return is always nonnil and argument is ignored, but added to 54 | // check we don't crash on unnamed parameters. 55 | // contract(nonnil -> nonnil) 56 | func fooUnnamedParam(_ *int) *int { 57 | return new(int) 58 | } 59 | 60 | func barUnnamedParam1() { 61 | var a1 *int 62 | b1 := fooUnnamedParam(a1) 63 | print(*b1) // No error here. 64 | } 65 | 66 | func barUnnamedParam2() { 67 | var a2 *int 68 | b2 := fooUnnamedParam(a2) 69 | print(*b2) // No error here. 70 | } 71 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/globalvars/globalvars.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | These tests aim to establish proper handling of the nilness of global variables 17 | 18 | 19 | */ 20 | package globalvars 21 | 22 | // nilable(nilable) 23 | var nilable *int 24 | var nonnil = new(int) 25 | 26 | func readFromGlobals() *int { 27 | if true { 28 | return nilable //want "returned" 29 | } else { 30 | return nonnil 31 | } 32 | } 33 | 34 | // nilable(a) 35 | func writeToGlobals(a, b *int) { 36 | switch 0 { 37 | case 1: 38 | nilable = a 39 | case 2: 40 | nilable = b 41 | case 3: 42 | nonnil = a //want "assigned" 43 | default: 44 | nonnil = b 45 | } 46 | } 47 | 48 | // nilable(deepnilable[]), nonnil(deepnilable) 49 | var deepnilable []*int //want "assigned into global variable" 50 | 51 | // nonnil(deepnonnil) 52 | var deepnonnil []*int //want "assigned into global variable" 53 | 54 | func readDeepFromGlobals() *int { 55 | if true { 56 | return deepnilable[0] //want "returned" 57 | } else { 58 | return deepnonnil[0] 59 | } 60 | } 61 | 62 | // nilable(a) 63 | func writeDeepToGlobals(a, b *int) { 64 | switch 0 { 65 | case 1: 66 | deepnilable[0] = a 67 | case 2: 68 | deepnilable[0] = b 69 | case 3: 70 | deepnonnil[0] = a //want "assigned" 71 | default: 72 | deepnonnil[0] = b 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/globalvars/upstream/upstream.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package upstream 16 | 17 | // ErrorNo is an uint32 type that implements error interface. 18 | type ErrorNo uint32 19 | 20 | func (e ErrorNo) Error() string { return "error!" } 21 | 22 | // ErrorNoFailure is a constant marking a failure. 23 | const ErrorNoFailure = ErrorNo(42) 24 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/goquirks/goquirks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This set of tests aims to capture a grab bag of strange go features 17 | 18 | 19 | */ 20 | package goquirks 21 | 22 | type A int 23 | type B = *int 24 | 25 | // nilable(b1, b3) 26 | func (a A) add(b1, b2, b3 B) {} 27 | 28 | // this tests that both forms of method call are parsed for non-pointer receivers 29 | // nilable(b1) 30 | func foo(a A, b1, b2 B) { 31 | a.add(b1, b2, b1) 32 | A.add(a, b1, b2, b1) 33 | a.add(b2, b1, b2) //want "passed" 34 | A.add(a, b2, b1, b2) //want "passed" 35 | } 36 | 37 | // nilable(b1, b3) 38 | func (a *A) add2(b1, b2, b3 B) {} 39 | 40 | // this tests that both forms of method call are parsed for pointer receivers 41 | // nilable(b1) 42 | func foo2(a *A, b1, b2 B) { 43 | a.add2(b1, b2, b1) 44 | (*A).add2(a, b1, b2, b1) 45 | a.add2(b2, b1, b2) //want "passed" 46 | (*A).add2(a, b2, b1, b2) //want "passed" 47 | } 48 | 49 | // this tests the common paradigm in go of a nilable return of error type 50 | // we want to assume these are nilable 51 | func fooThatErrs() error { 52 | return nil 53 | } 54 | 55 | func fooThatErrs2() (*int, error, *int) { 56 | i := 0 57 | return &i, nil, &i 58 | } 59 | 60 | func fooThatConsumesErrs() interface{} { 61 | a := fooThatErrs() 62 | b, c, d := fooThatErrs2() 63 | switch 0 { 64 | case 1: 65 | return a //want "returned" 66 | case 2: 67 | return b 68 | case 3: 69 | return c //want "returned" 70 | default: 71 | return d 72 | } 73 | } 74 | 75 | // external is only a function declaration whose body is defined outside Go (e.g., assembly). 76 | func external(v *int) *int -------------------------------------------------------------------------------- /testdata/src/go.uber.org/helloworld/helloworld.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | The point of this test is to make sure that the package fmt can be imported, which runs the 17 | analyzer on a larger variety of library packages 18 | 19 | 20 | */ 21 | package helloworld 22 | 23 | import "fmt" 24 | 25 | func main() { 26 | fmt.Println("Hello World") 27 | } 28 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/ignoregenerated/ignoregenerated1.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //comment 16 | //@generated 17 | 18 | //comment 19 | //comment 20 | 21 | // comment 22 | // comment 23 | package ignoregenerated 24 | 25 | func foo1() int { 26 | var x *int 27 | return *x 28 | } 29 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/ignoregenerated/ignoregenerated2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //comment 16 | //comment 17 | 18 | //@generated 19 | //comment 20 | 21 | // comment 22 | // comment 23 | package ignoregenerated 24 | 25 | func foo2() int { 26 | var x *int 27 | return *x 28 | } 29 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/ignoregenerated/ignoregenerated3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //comment 16 | //comment 17 | 18 | //comment 19 | //comment 20 | 21 | // @generated 22 | // comment 23 | package ignoregenerated 24 | 25 | func foo3() int { 26 | var x *int 27 | return *x 28 | } 29 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/inference/inference.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package tests _single_ package inference. Due to limitations of `analysistest` framework, 16 | // multi-package inference is tested by our integration test suites. Please see 17 | // `testdata/README.md` for more details. 18 | 19 | package inference 20 | 21 | var dummyBool bool 22 | var dummyInt int 23 | 24 | func retsNilable1() *int { 25 | return nil 26 | } 27 | 28 | func retsNilable2() *int { 29 | if dummyBool { 30 | return &dummyInt 31 | } 32 | return nil 33 | } 34 | 35 | func retsNilable3() *int { 36 | switch dummyInt { 37 | case dummyInt: 38 | return retsNilable1() 39 | case dummyInt: 40 | return retsNilable2() 41 | case dummyInt: 42 | return retsNilable3() 43 | } 44 | return &dummyInt 45 | } 46 | 47 | func retsNonnil1() *int { 48 | return &dummyInt 49 | } 50 | 51 | func retsNonnil2() *int { 52 | if dummyBool { 53 | return &dummyInt 54 | } 55 | return &dummyInt 56 | } 57 | 58 | func retsNonnil3() *int { 59 | switch dummyInt { 60 | case dummyInt: 61 | return retsNonnil1() 62 | case dummyInt: 63 | return retsNonnil2() 64 | case dummyInt: 65 | return retsNonnil3() 66 | } 67 | return &dummyInt 68 | } 69 | 70 | func retsNilable4() *int { 71 | if dummyBool { 72 | return retsNilable3() 73 | } 74 | return retsNilable3() 75 | } 76 | 77 | func takesNonnil(x *int) int { 78 | return *x 79 | } 80 | 81 | func takesNilable(x *int) int { 82 | if x == nil { 83 | return 0 84 | } 85 | return *x 86 | } 87 | 88 | func retsAndTakes() { 89 | switch dummyInt { 90 | case dummyInt: 91 | takesNonnil(retsNonnil1()) 92 | takesNonnil(retsNonnil2()) 93 | takesNonnil(retsNonnil3()) 94 | 95 | takesNilable(retsNonnil1()) 96 | takesNilable(retsNonnil2()) 97 | takesNilable(retsNonnil3()) 98 | 99 | takesNilable(retsNilable1()) 100 | takesNilable(retsNilable2()) 101 | takesNilable(retsNilable3()) 102 | takesNilable(retsNilable4()) 103 | } 104 | } 105 | 106 | // Below test checks the working of inference in the presence of annotations 107 | // nonnil(x) nilable(result 0) 108 | func foo(x *int) *int { //want "NONNIL because it is annotated as so" 109 | print(*x) 110 | return nil 111 | } 112 | 113 | func callFoo() { 114 | ptr := foo(nil) 115 | print(*ptr) //want "NILABLE because it is annotated as so" 116 | } 117 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/chainedDependencies/mainfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking support for chained dependencies of witnessed affiliations through caching 17 | 18 | 19 | */ 20 | package chainedDependencies 21 | 22 | import ( 23 | "go.uber.org/methodimplementation/chainedDependencies/packageC" 24 | ) 25 | 26 | func main() { 27 | packageC.Chain() 28 | } 29 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/chainedDependencies/packageA/file1.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageA 17 | 18 | type I1 interface { 19 | // nilable(n) 20 | Foo(n *int) bool 21 | } 22 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/chainedDependencies/packageB/file2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageB 17 | 18 | import ( 19 | "go.uber.org/methodimplementation/chainedDependencies/packageA" 20 | ) 21 | 22 | type S1 struct{} 23 | 24 | func (*S1) Foo(n *int) bool { //want "passed as param" 25 | v := &n 26 | return v != nil 27 | } 28 | 29 | func m() { 30 | var i1 packageA.I1 = new(S1) 31 | i1.Foo(nil) 32 | } 33 | 34 | type I2 interface { 35 | Bar() *string 36 | } 37 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/chainedDependencies/packageC/file3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageC 17 | 18 | import ( 19 | "go.uber.org/methodimplementation/chainedDependencies/packageB" 20 | ) 21 | 22 | type S2 struct{} 23 | 24 | func (S2) Bar() *string { 25 | s := "hello" 26 | return &s 27 | } 28 | 29 | func Chain() { 30 | var i2 packageB.I2 31 | s2 := &S2{} 32 | i2 = s2 33 | i2.Bar() 34 | } 35 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/mergedDependencies/mainfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking support for caching functionality. Affiliations witnessed through upstream packages (A and B) should 17 | not be re-analyzed by downstream ones (mergedDependencies/mainFile.go) 18 | TODO: the caching behavior can only be observed by inspecting the logs. Make it as part of a unit test in the future perhaps by adding mocking of the log 19 | 20 | 21 | */ 22 | package mergedDependencies 23 | 24 | import ( 25 | "go.uber.org/methodimplementation/mergedDependencies/packageA" 26 | "go.uber.org/methodimplementation/mergedDependencies/packageB" 27 | ) 28 | 29 | func main() { 30 | var i1 packageA.I1 = &packageA.S1{} 31 | i1.Foo2(i1.Foo1()) 32 | 33 | var i2 packageB.I2 34 | s2 := &packageB.S2{} 35 | i2 = s2 36 | i2.Bar() 37 | } 38 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/mergedDependencies/packageA/file1.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageA 17 | 18 | type I1 interface { 19 | Foo1() *int //want "returned as result" 20 | 21 | // nilable(n) 22 | Foo2(n *int) bool 23 | } 24 | 25 | type S1 struct{} 26 | 27 | // nilable(result 0) 28 | func (*S1) Foo1() *int { 29 | var v *int 30 | return v 31 | } 32 | 33 | func (*S1) Foo2(n *int) bool { //want "passed as param" 34 | v := &n 35 | return v != nil 36 | } 37 | 38 | func main() { 39 | var i1 I1 = new(S1) 40 | i1.Foo2(i1.Foo1()) 41 | } 42 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/mergedDependencies/packageB/file2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageB 17 | 18 | type I2 interface { 19 | Bar() *string //want "returned as result" 20 | } 21 | 22 | type S2 struct{} 23 | 24 | // nilable(result 0) 25 | func (S2) Bar() *string { 26 | s := "hello" 27 | return &s 28 | } 29 | 30 | func main() { 31 | var i2 I2 = new(S2) 32 | i2.Bar() 33 | } 34 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation1.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param nil, implementing method param non-nil --> contravariance violation 20 | 21 | 22 | */ 23 | package methodimplementation 24 | 25 | type I interface { 26 | // nilable(x) 27 | foo(x *A) (*A, string) //want "returned as result" 28 | } 29 | 30 | type J interface { 31 | // nilable(x, result 0) 32 | bar(x *A, y *B) *string 33 | } 34 | 35 | type A struct { 36 | s string 37 | } 38 | 39 | type B struct { 40 | i int 41 | } 42 | 43 | // nilable(result 0) 44 | func (A) foo(x *A) (*A, string) { //want "passed as param" 45 | var b *A 46 | return b, x.s 47 | } 48 | 49 | // nilable(x) 50 | func (a A) bar(x *A, y *B) *string { 51 | if x != nil { 52 | return &x.s 53 | } 54 | return &a.s 55 | } 56 | 57 | func (b B) foo(x *A) (*A, string) { 58 | return x, x.s // this is safe because struct of type B is never used as the interface type I 59 | } 60 | 61 | // nilable(y, result 0) 62 | func (b *B) bar(x *A, y *B) *string { //want "passed as param" 63 | if b.i+y.i > 5 { //want "accessed field `i`" 64 | return nil 65 | } 66 | return &x.s 67 | } 68 | 69 | func m() { 70 | // site 1: assignment of a concrete implementation to an interface type 71 | var v1 I 72 | v1 = &A{} 73 | v1.foo(new(A)) 74 | 75 | var v2 J 76 | v2 = &B{} 77 | v2.bar(new(A), new(B)) 78 | } 79 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation10.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking N-to-1 assignment form (e.g., i1, i2 = foo(), where foo return s1, s2) 17 | */ 18 | 19 | package methodimplementation 20 | 21 | type i1 interface { 22 | foo() (x *int) 23 | } 24 | 25 | type i2 interface { 26 | foo() (x *int) 27 | } 28 | 29 | type i3 interface { 30 | foo() (x *int) 31 | } 32 | 33 | type i4 interface { 34 | foo() (x *int) //want "returned as result" 35 | } 36 | 37 | type i5 interface { 38 | foo() (x *int) //want "returned as result" 39 | } 40 | 41 | type i6 interface { 42 | foo() (x *int) 43 | } 44 | 45 | type i7 interface { 46 | foo() (x *int) //want "returned as result" 47 | } 48 | 49 | type i8 interface { 50 | foo() (x *int) //want "returned as result" 51 | } 52 | 53 | type s1 struct{} 54 | 55 | func (*s1) foo() (x *int) { 56 | i := 0 57 | return &i 58 | } 59 | 60 | type s2 struct{} 61 | 62 | // nilable(x) 63 | func (*s2) foo() (x *int) { return nil } 64 | 65 | func rets11() (*s1, *s1) { 66 | return &s1{}, &s1{} 67 | } 68 | 69 | func rets12() (*s1, *s2) { 70 | return &s1{}, &s2{} 71 | } 72 | 73 | func rets21() (*s2, *s1) { 74 | return &s2{}, &s1{} 75 | } 76 | 77 | func rets22() (*s2, *s2) { 78 | return &s2{}, &s2{} 79 | } 80 | 81 | func mainbody() { 82 | var x1 i1 83 | var x2 i2 84 | var x3 i3 85 | var x4 i4 86 | var x5 i5 87 | var x6 i6 88 | var x7 i7 89 | var x8 i8 90 | 91 | x1 = &s1{} 92 | 93 | x1, x2 = rets11() 94 | x3, x4 = rets12() 95 | x5, x6 = rets21() 96 | x7, x8 = rets22() 97 | func(...any) {}(x1, x2, x3, x4, x5, x6, x7, x8) 98 | } 99 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation11.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking that NilAway only analyzes the affiliations within scope. For example, ast is an external 17 | library, affiliations processing of which should be ignored 18 | */ 19 | 20 | package methodimplementation 21 | 22 | import ( 23 | "go/ast" 24 | "go/token" 25 | ) 26 | 27 | type myNode struct{} 28 | 29 | // nilable(result 0) 30 | func (myNode) Pos() token.Pos { 31 | return 1 32 | } 33 | 34 | func (myNode) End() token.Pos { 35 | return 2 36 | } 37 | 38 | func m11() { 39 | var node ast.Node = &ast.FuncDecl{} 40 | node.Pos() 41 | 42 | var mynode ast.Node = &myNode{} 43 | mynode.Pos() 44 | } 45 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation13.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This test file tests a corner case for anonymous functions, where we should not affiliate the 16 | // return statements inside the anonymous functions with the outermost function return type. 17 | // 18 | 19 | package methodimplementation 20 | 21 | // IA13 and IB13 are two different interfaces coincidentally both having a Get method, but their 22 | // arguments are different. 23 | type IA13 interface { 24 | // nilable(x) 25 | Get(x *int) int 26 | } 27 | 28 | type IB13 interface { 29 | // nilable(x, y) 30 | Get(x *int, y *int) int 31 | } 32 | 33 | // A13 and B13 are two structs that implement IA13 and IB13 interfaces, respectively. 34 | type A13 struct{} 35 | 36 | // nilable(x) 37 | func (a *A13) Get(x *int) int { return 0 } 38 | 39 | type B13 struct{} 40 | 41 | // nilable(x, y) 42 | func (b *B13) Get(x *int, y *int) int { return 0 } 43 | 44 | func f1() IA13 { 45 | // We should not affiliate "return B13" with the function return interface type IA13. 46 | getter := func() IB13 { return &B13{} } 47 | getter() 48 | return &A13{} 49 | } 50 | 51 | // Now, we test a case where the two interfaces have a same-name method with same arguments (i.e., 52 | // they are functionally the same interfaces), but the niliability requirements are different. 53 | // No errors should be reported if our assignments follow the nilability requirements. 54 | 55 | // IC13 and C13 have a Get method with a nilable argument, meanwhile ID13 and D13 have a Get method 56 | // with a nonnil argument. If we only assign C13 -> IC13, D13 -> ID13, C13 -> ID13, no errors 57 | // should be reported. 58 | 59 | type IC13 interface { 60 | // niable(x) 61 | Get(x *bool) int 62 | } 63 | 64 | type ID13 interface { 65 | // nonnil(x) 66 | Get(x *bool) int 67 | } 68 | 69 | // C13 and D13 are two structs that implement IC13 and ID13 interfaces, respectively. 70 | type C13 struct{} 71 | 72 | // nilable(x) 73 | func (c *C13) Get(x *bool) int { return 0 } 74 | 75 | type D13 struct{} 76 | 77 | // nonnil(x) 78 | func (d *D13) Get(x *bool) int { return 0 } 79 | 80 | func f2() ID13 { 81 | getter := func() ID13 { return &D13{} } 82 | getter() 83 | return &C13{} 84 | } 85 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I2 interface { 25 | // nilable(x) 26 | foo(x *A2) (*A2, string) //want "returned as result" 27 | } 28 | 29 | type A2 struct { 30 | s string 31 | } 32 | 33 | // nilable(result 0) 34 | func (A2) foo(x *A2) (*A2, string) { //want "passed as param" 35 | var b *A2 36 | return b, x.s 37 | } 38 | 39 | func m2() { 40 | var nilV *A2 41 | 42 | // site 2: interface variable declaration and initialization using concrete type 43 | var v2 I2 = &A2{} 44 | v2.foo(nilV) 45 | } 46 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I3 interface { 25 | // nilable(x) 26 | foo(x *A3) (*A3, string) //want "returned as result" 27 | } 28 | 29 | type A3 struct { 30 | s string 31 | } 32 | 33 | // nilable(result 0) 34 | func (A3) foo(x *A3) (*A3, string) { //want "passed as param" 35 | var b *A3 36 | return b, x.s 37 | } 38 | 39 | func ret3() *A3 { 40 | return &A3{} 41 | } 42 | 43 | func m3() { 44 | var nilV *A3 45 | 46 | // site 3: assignment of return of a function to a variable of interface type 47 | var v3 I3 = ret3() 48 | v3.foo(nilV) 49 | } 50 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation4.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I4 interface { 25 | // nilable(x) 26 | foo(x *A4) (*A4, string) //want "returned as result" 27 | } 28 | 29 | type A4 struct { 30 | s string 31 | } 32 | 33 | // nilable(result 0) 34 | func (A4) foo(x *A4) (*A4, string) { //want "passed as param" 35 | var b *A4 36 | return b, x.s 37 | } 38 | 39 | func param3(i I4) { 40 | i.foo(&A4{}) 41 | } 42 | 43 | func m4() { 44 | // site 4: passing a variable of concrete type as a param2 to a function expecting interface type 45 | v4 := &A4{} 46 | param3(v4) 47 | } 48 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation5.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I5 interface { 25 | // nilable(x) 26 | foo(x *A5) (*A5, string) //want "returned as result" 27 | } 28 | 29 | type A5 struct { 30 | s string 31 | } 32 | 33 | // nilable(result 0) 34 | func (A5) foo(x *A5) (*A5, string) { //want "passed as param" 35 | var b *A5 36 | return b, x.s 37 | } 38 | 39 | func ret5() *A5 { 40 | return &A5{} 41 | } 42 | 43 | func param5(i I5) { 44 | i.foo(&A5{}) 45 | } 46 | 47 | func m5() { 48 | // site 5: function return flowing into a function param2 49 | param5(ret5()) 50 | } 51 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation6.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I6 interface { 25 | // nilable(x) 26 | foo(x *A6) (*A6, string) //want "returned as result" 27 | } 28 | 29 | type A6 struct { 30 | s string 31 | } 32 | 33 | // nilable(result 0) 34 | func (A6) foo(x *A6) (*A6, string) { //want "passed as param" 35 | var b *A6 36 | return b, x.s 37 | } 38 | 39 | func m6() { 40 | // site 6: casting interface into a concrete type 41 | var i I6 42 | if v, ok := i.(*A6); ok { 43 | v.foo(&A6{}) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation7.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I7 interface { 25 | // nilable(x) 26 | foo(x *A7) (*A7, string) //want "returned as result" 27 | } 28 | 29 | type A7 struct { 30 | s string 31 | } 32 | 33 | type FuncType func(x *A7) (*A7, string) 34 | 35 | // nilable(result 0) 36 | func (f FuncType) foo(x *A7) (*A7, string) { //want "passed as param" 37 | var b *A7 38 | return b, x.s 39 | } 40 | 41 | func m7() { 42 | // site 7: function implementing an interface 43 | f := new(FuncType) 44 | var i I7 = f 45 | i.foo(&A7{"abc"}) 46 | } 47 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation8.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I8 interface { 25 | // nilable(x) 26 | foo(x *A8) (*A8, string) //want "returned as result" 27 | } 28 | 29 | type A8 struct { 30 | s string 31 | } 32 | 33 | type NamedType int 34 | 35 | // nilable(result 0) 36 | func (NamedType) foo(x *A8) (*A8, string) { //want "passed as param" 37 | var b *A8 38 | return b, x.s 39 | } 40 | 41 | func m8() { 42 | // site 7: a named type (non-struct) implementing an interface 43 | var i I8 = new(NamedType) 44 | i.foo(&A8{}) 45 | } 46 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/methodimplementation9.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking the *true negative* case for nilability-type variance. Covariance for return types and contravariance for parameter 17 | types of methods implementing an interface. 18 | interface method returns non-nil, implementing method returns nil --> covariance violation 19 | interface method param2 nil, implementing method param2 non-nil --> contravariance violation 20 | */ 21 | 22 | package methodimplementation 23 | 24 | type I9 interface { 25 | // nilable(result 0) 26 | foo(x *A9) *string 27 | } 28 | 29 | type A9 struct { 30 | s string 31 | } 32 | 33 | // nilable(x) 34 | func (A9) foo(x *A9) *string { 35 | if x != nil { 36 | return &x.s 37 | } 38 | s := "" 39 | return &s 40 | } 41 | 42 | func m9() { 43 | var i I9 = &A9{} 44 | i.foo(&A9{}) 45 | } 46 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/multipackage/mainfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a test for checking support for downstream/upstream dependencies. For example, interface and struct defined in 17 | different packages and their instantiation(s) witnessed in yet another package(s). Also, this test checks the use of 18 | cache passed across packages to avoid re-analysis of the affiliations. 19 | 20 | 21 | */ 22 | package multipackage 23 | 24 | import ( 25 | "go/ast" 26 | 27 | "go.uber.org/methodimplementation/multipackage/packageA" 28 | "go.uber.org/methodimplementation/multipackage/packageB" 29 | "go.uber.org/methodimplementation/multipackage/packageC" 30 | ) 31 | 32 | func m10() packageA.I9 { 33 | param2(&packageB.A9{}) 34 | 35 | packageC.M9() 36 | 37 | var v packageA.I9 38 | v = &packageB.A9{"xyz"} 39 | return v 40 | } 41 | 42 | func param2(i packageA.I9) { 43 | // do something ... 44 | } 45 | 46 | func m11() { 47 | var node ast.Node = &ast.FuncDecl{} 48 | node.Pos() 49 | } 50 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/multipackage/packageA/interfacefile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageA 17 | 18 | import ( 19 | "go.uber.org/methodimplementation/multipackage/packageB" 20 | ) 21 | 22 | type I9 interface { 23 | // nilable(x) 24 | Foo(x *packageB.A9) (*packageB.A9, string) //want "returned as result" 25 | } 26 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/multipackage/packageB/structfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageB 17 | 18 | type A9 struct { 19 | S string 20 | } 21 | 22 | // nilable(result 0) 23 | func (a *A9) Foo(x *A9) (*A9, string) { //want "passed as param" 24 | var b *A9 25 | return b, x.S 26 | } 27 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/methodimplementation/multipackage/packageC/instantiationfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package packageC 17 | 18 | import ( 19 | "go.uber.org/methodimplementation/multipackage/packageA" 20 | "go.uber.org/methodimplementation/multipackage/packageB" 21 | ) 22 | 23 | func M9() packageA.I9 { 24 | var v packageA.I9 = &packageB.A9{"abc"} 25 | return v 26 | } 27 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/multifilepackage/firstpackage/firstfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package firstpackage 17 | 18 | type A struct { 19 | myB *B 20 | } 21 | 22 | func (a *A) GetMyB() *B { 23 | return a.myB 24 | } 25 | 26 | func (a *A) CheckReflect() bool { 27 | return a == a.GetMyB().GetMyA() 28 | } 29 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/multifilepackage/firstpackage/secondfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package firstpackage 16 | 17 | type B struct { 18 | myA *A 19 | } 20 | 21 | func (b *B) GetMyA() *A { 22 | return b.myA 23 | } 24 | 25 | func (b *B) CheckReflect() bool { 26 | return b == b.GetMyA().GetMyB() 27 | } 28 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/multifilepackage/rootfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This test functions as a sanity check for multi-file handling, just making sure that in the 17 | case of linked files no errors are thrown 18 | 19 | 20 | */ 21 | package multifilepackage 22 | 23 | import ( 24 | "go.uber.org/multifilepackage/firstpackage" 25 | "go.uber.org/multifilepackage/secondpackage" 26 | ) 27 | 28 | func main() { 29 | a := &firstpackage.A{} 30 | b := &firstpackage.B{} 31 | c := secondpackage.C{true} 32 | c.Branch(a, b).CheckReflect() 33 | } 34 | 35 | func safeBoxManipulations() { 36 | c := secondpackage.CBox{} 37 | c.Box(nil) 38 | c.Ptr = nil 39 | } 40 | 41 | func unsafeBoxManipulations() *secondpackage.C { 42 | c := secondpackage.CBox{} 43 | if true { 44 | return c.Unbox() //want "returned" 45 | } else { 46 | return c.Ptr //want "returned" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/multifilepackage/secondpackage/firstfile.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package secondpackage 17 | 18 | import "go.uber.org/multifilepackage/firstpackage" 19 | 20 | type C struct { 21 | B bool 22 | } 23 | 24 | // nilable(Ptr) 25 | type CBox struct { 26 | Ptr *C 27 | } 28 | 29 | // nilable(ptr) 30 | func (c CBox) Unbox() (ptr *C) { 31 | return c.Ptr 32 | } 33 | 34 | // nilable(ptr) 35 | func (c CBox) Box(ptr *C) { 36 | c.Ptr = ptr 37 | } 38 | 39 | func (c C) Branch(a *firstpackage.A, b *firstpackage.B) *firstpackage.B { 40 | if c.B { 41 | return a.GetMyB() 42 | } else { 43 | return b 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/nolint/file_level.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nolint 16 | 17 | func localNoLintLint() { 18 | var p *int 19 | print(*p) //nolint:nilaway 20 | print(*p) //nolint:all 21 | print(*p) // nolint : nilaway // Explanation 22 | print(*p) //nolint 23 | print(*p) ////nolint:nilaway 24 | } 25 | 26 | //nolint:nilaway 27 | func localNoLintFunc() { 28 | var p *int 29 | print(*p) 30 | print(*p) 31 | print(*p) 32 | } 33 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/nolint/nolint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //nolint:nilaway 16 | package nolint 17 | 18 | func localNoLintFile() { 19 | var p *int 20 | print(*p) 21 | } 22 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/simpleflow/simpleflow.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | This is a very simple test that just checks direct return of nilable and non-nilable values 17 | to ensure that correct diagnostics are thrown in some common cases 18 | 19 | 20 | */ 21 | package testdata 22 | 23 | /* 24 | nilable(f) 25 | */ 26 | type A struct { 27 | f *A 28 | g *A 29 | } 30 | 31 | /* 32 | nilable(c) 33 | */ 34 | func (a *A) foo(b, c *A) *A { 35 | switch 0 { 36 | case 1: 37 | return a 38 | case 2: 39 | return a.f //want "returned from `foo.*`" 40 | case 3: 41 | return a.g 42 | case 4: 43 | return b 44 | case 5: 45 | return b.f //want "returned from `foo.*`" 46 | case 6: 47 | return b.g 48 | case 7: 49 | return c //want "returned from `foo.*`" 50 | case 8: 51 | return c.f //want "returned from `foo.*`" "accessed field `f`" 52 | case 9: 53 | return c.g //want "accessed field `g`" 54 | default: 55 | return nil //want "returned from `foo.*`" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/defaultfield/defaultfield.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package defaultfield tests default nilability of the fields. Since struct initialization checking currently tracks 16 | // nilability of only depth one fields, we resort to default nilability of the field when the field read is not tracked. 17 | // For instance, a call to x.y when x is a parameter is tracked but x.y.z is not tracked. In these untracked cases, 18 | // the default nilability of the field is chosen based on escaping initialization analysis. The exact assumptions in the 19 | // analysis are documented at the FldEscape consume trigger. In short, it tracks the uninitialized fields that escape the 20 | // function. 21 | package defaultfield 22 | 23 | // This gives an error since field aptr escapes 24 | 25 | type A10 struct { 26 | aptr *A10 27 | ptr *int 28 | } 29 | 30 | func m() *A10 { 31 | // field aptr escapes uninitialized here 32 | return &A10{} 33 | } 34 | 35 | func m3(a *A10) { 36 | // relies on default annotation of field aptr since we don't track field at depth >=2 37 | print(a.aptr.aptr.ptr) //want "accessed field `ptr`" 38 | } 39 | 40 | // This should give an error since aptr escapes 41 | 42 | type A11 struct { 43 | ptr *int 44 | aptr *A11 45 | } 46 | 47 | func m11(c *A11) { 48 | print(c.aptr.aptr.ptr) //want "field `aptr` escaped" 49 | } 50 | 51 | func callEscape() { 52 | // field aptr escapes uninitialized here 53 | escape11(&A11{}) 54 | } 55 | 56 | // TODO: We should only call param fields escaping if the callee function is not analyzed by NilAway 57 | func escape11(a *A11) { 58 | // no-op 59 | } 60 | 61 | // This does not give an error since A12 never escapes 62 | 63 | type A12 struct { 64 | ptr *int 65 | aptr *A12 66 | } 67 | 68 | func m12(c *A12) { 69 | print(c.aptr.aptr.ptr) 70 | } 71 | 72 | // This gives an error since A13 can escape 73 | 74 | type A13 struct { 75 | ptr *int 76 | aptr *A13 77 | } 78 | 79 | func m13(c *A13) { 80 | print(c.aptr.aptr.ptr) //want "field `aptr` escaped" 81 | } 82 | 83 | func escape13() *A13 { 84 | var a = &A13{} 85 | return a 86 | } 87 | 88 | // This is another example with accessed field at depth 2, it should error as well 89 | 90 | type A14 struct { 91 | ptr *int 92 | bptr *B14 93 | } 94 | 95 | type B14 struct { 96 | cptr *C14 97 | } 98 | 99 | type C14 struct { 100 | ptr *int 101 | } 102 | 103 | func m21(c *A14) { 104 | // relies on default annotation of field cptr 105 | print(c.bptr.cptr.ptr) //want "field `cptr` escaped" 106 | } 107 | 108 | func escape14(a *B14) { 109 | // no-op 110 | } 111 | 112 | func createC14() { 113 | t := &B14{} 114 | escape14(t) 115 | } 116 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/funcreturnfields/functionreturntransitive.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package funcreturnfields Tests when nilability flows through the field of return of a function or a method 16 | package funcreturnfields 17 | 18 | // Testing with direct return of a composite literal initialization of struct 19 | 20 | func giveEmptyA() *A11 { 21 | t := &A11{} 22 | return t 23 | } 24 | 25 | func m07() *int { 26 | b := giveEmptyA() 27 | return b.aptr.ptr //want "accessed field `ptr`" 28 | } 29 | 30 | // Testing with direct return of struct as a composite literal. 31 | func giveEmptyAComposite() *A11 { 32 | return &A11{} 33 | } 34 | 35 | func m08() *int { 36 | t := giveEmptyAComposite() 37 | return t.aptr.ptr //want "accessed field `ptr`" 38 | } 39 | 40 | // Testing with transitive return of struct through a function call. 41 | func giveEmptyA11Fun() *A11 { 42 | return &A11{} 43 | } 44 | 45 | func giveEmptyACallFun() *A11 { 46 | return giveEmptyA11Fun() 47 | } 48 | 49 | func m10() *int { 50 | t := giveEmptyACallFun() 51 | return t.aptr.ptr //want "accessed field `ptr`" 52 | } 53 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/funcreturnfields/methodreturnbasic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package funcreturnfields Tests when nilability flows through the field of return of a function or a method 16 | package funcreturnfields 17 | 18 | // similar tests ad funcreturnfields.go for methods 19 | // In this test, field aptr is initialized in giveA() function and thus no error should be reported 20 | 21 | type A21 struct { 22 | ptr *int 23 | aptr *A21 24 | } 25 | 26 | type Pool struct{} 27 | 28 | func (pool *Pool) giveA() *A21 { 29 | t := &A21{} 30 | t.aptr = &A21{} 31 | return t 32 | } 33 | 34 | func m21() *int { 35 | pool := new(Pool) 36 | var b = pool.giveA() 37 | return b.aptr.ptr 38 | } 39 | 40 | // In this test, field aptr is set to nil in giveEmptyA() function and thus error should be reported 41 | 42 | type A22 struct { 43 | ptr *int 44 | aptr *A22 45 | } 46 | 47 | func (pool *Pool) giveEmptyA() *A22 { 48 | t := &A22{} 49 | return t 50 | } 51 | 52 | func m22() *int { 53 | pool := new(Pool) 54 | var b = pool.giveEmptyA() 55 | return b.aptr.ptr //want "accessed field `ptr`" 56 | } 57 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/global/global.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package global checks if the struct initialization checker works as expected on the global variables. 16 | package global 17 | 18 | // Negative test 19 | 20 | type A struct { 21 | ptr *int 22 | aptr *A 23 | } 24 | 25 | var g *A = nil 26 | 27 | func f() { 28 | g = &A{} 29 | // g.aptr is initialized here 30 | g.aptr = new(A) 31 | } 32 | 33 | func h() { 34 | if g == nil { 35 | return 36 | } 37 | 38 | print(g.aptr.ptr) 39 | } 40 | 41 | // Positive test 42 | 43 | type A2 struct { 44 | ptr *int 45 | aptr *A2 46 | } 47 | 48 | var g2 *A2 = nil 49 | 50 | func f2() { 51 | g2 = &A2{} 52 | g2.aptr = nil 53 | } 54 | 55 | func h2() { 56 | if g2 == nil { 57 | return 58 | } 59 | 60 | print(g2.aptr.ptr) //want "field `aptr` accessed field `ptr`" 61 | } 62 | 63 | var g3 = &A{} 64 | 65 | func f3() { 66 | // g3.aptr is initialized here 67 | g3.aptr = new(A) 68 | } 69 | 70 | func h3() { 71 | print(g3.aptr.ptr) 72 | } 73 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/multipackage/multipackage.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package multipackage test functions as a sanity check for multi-file handling, just making sure that in the 16 | // case of linked files no errors are thrown 17 | package multipackage 18 | 19 | import ( 20 | "go.uber.org/structinit/multipackage/packageone" 21 | ) 22 | 23 | func m() *int { 24 | var a = packageone.GiveEmptyA() 25 | return a.Aptr.Ptr 26 | } 27 | 28 | func callF12() { 29 | t := &packageone.A{Aptr: &packageone.A{}} 30 | packageone.F12(t) 31 | } 32 | 33 | type B struct { 34 | packageone.A 35 | } 36 | 37 | func m2() *B { 38 | var b = &B{ 39 | A: packageone.A{}, 40 | } 41 | return b 42 | } 43 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/multipackage/packageone/packageone.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package packageone 16 | 17 | type A struct { 18 | Ptr *int 19 | Aptr *A 20 | } 21 | 22 | var GlobalA = A{} 23 | 24 | func GiveEmptyA() *A { 25 | return &A{Aptr: &A{}} 26 | } 27 | 28 | func F12(c *A) { 29 | print(c.Aptr.Ptr) 30 | } 31 | 32 | type S []int 33 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/paramfield/receiverfield.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package paramfield Tests when nilability flows through the field of param of a function or a method 16 | package paramfield 17 | 18 | // Negative example 19 | 20 | func callM21() { 21 | t := &A{} 22 | t.aptr = &A{} 23 | t.m21() 24 | } 25 | 26 | func (c *A) m21() { 27 | print(c.aptr.ptr) 28 | } 29 | 30 | // Positive example 31 | 32 | func callM22() { 33 | t := &A{} 34 | t.m22() 35 | } 36 | 37 | func (c *A) m22() { 38 | print(c.aptr.ptr) //want "field `aptr` of method receiver `c`" 39 | } 40 | 41 | // Checking if Nilaway does not crash on unnamed receivers 42 | func (*A) m23() {} 43 | 44 | // Positive example with direct composite as parameter 45 | 46 | func callF24() { 47 | (A{}).f24() 48 | } 49 | 50 | func (c A) f24() { 51 | print(c.aptr.ptr) //want "field `aptr` of method receiver `c`" 52 | } 53 | 54 | // Positive example with direct composite as parameter 55 | func giveA25() *A { 56 | return &A{} 57 | } 58 | 59 | func callF25() { 60 | giveA25().f25() 61 | } 62 | 63 | func (c *A) f25() { 64 | print(c.aptr.ptr) //want "field `aptr` of method receiver `c`" 65 | } 66 | 67 | // Negative example with direct composite as parameter 68 | 69 | func callF26() { 70 | (&A{aptr: &A{}}).f26() 71 | } 72 | 73 | func (c *A) f26() { 74 | print(c.aptr.ptr) 75 | } 76 | 77 | // Negative example with direct composite as parameter 78 | func giveA27() *A { 79 | return &A{aptr: new(A)} 80 | } 81 | 82 | func callF27() { 83 | giveA27().f27() 84 | } 85 | 86 | func (c *A) f27() { 87 | print(c.aptr.ptr) 88 | } 89 | 90 | // No Nilaway error in this case. 91 | // But it is a special case and Nilaway should not crash on it 92 | 93 | type someError struct { 94 | error 95 | } 96 | 97 | func (n someError) IsSomeError() bool { 98 | return true 99 | } 100 | -------------------------------------------------------------------------------- /testdata/src/go.uber.org/structinit/paramsideeffect/receiversideeffect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package paramsideeffect Tests when nilability flows through the field of param on a call to a function or a method 16 | package paramsideeffect 17 | 18 | // Tests populating Receiver of a method 19 | 20 | func (x *A) populateMethod() { 21 | x.newPtr = &A{} 22 | } 23 | 24 | func m21() *int { 25 | b := &A{} 26 | b.aptr = &A{} 27 | b.populateMethod() 28 | print(b.newPtr.ptr) 29 | return b.aptr.ptr 30 | } 31 | 32 | // Positive test 33 | 34 | func (x *A) populateMethod2() { 35 | x.newPtr = nil 36 | } 37 | 38 | func m22() *int { 39 | b := &A{} 40 | b.aptr = &A{} 41 | b.populateMethod2() 42 | print(b.newPtr.ptr) //want "field `newPtr` of method receiver `x`" 43 | return b.aptr.ptr 44 | } 45 | -------------------------------------------------------------------------------- /testdata/src/grouping/disabled/main.go: -------------------------------------------------------------------------------- 1 | // Package disabled is meant to check if our group-error-messages flag has effect. 2 | package disabled 3 | 4 | // When the group-error-messages flag is set to false, the two dereference error messages should be reported separately. 5 | func test() { 6 | var x *int 7 | _ = *x //want "dereferenced" 8 | _ = *x //want "dereferenced" 9 | } 10 | -------------------------------------------------------------------------------- /testdata/src/grouping/enabled/main.go: -------------------------------------------------------------------------------- 1 | // Package grouping is meant to check if our group-error-messages flag has effect. 2 | package enabled 3 | 4 | // When the group-error-messages flag is set to true, the two dereference error messages should be grouped together. 5 | func test() { 6 | var x *int 7 | _ = *x //want "Same nil source could also cause potential nil panic" 8 | _ = *x 9 | } 10 | -------------------------------------------------------------------------------- /testdata/src/grouping/errormessage/inference/errormessage-with-inference.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This package tests error messages for the inference mode. 16 | 17 | package inference 18 | 19 | // Below test checks error messages for single assertion conflicts when the producer expression is not authentic, i.e., 20 | // it is built from assertion nodes and hence not found in the original AST. 21 | 22 | type S struct { 23 | f map[int]*S 24 | g string 25 | } 26 | 27 | type T struct { 28 | m map[string]S 29 | } 30 | 31 | // Here, although the two error messages are similar for test1 and test2, they should not be grouped together as they are 32 | // from different functions. 33 | func (t *T) test1(str string) { 34 | p := t.m[str] 35 | _ = *p.f[0] //want "dereferenced" 36 | } 37 | 38 | func (t *T) test2(str string) { 39 | p := t.m[str] 40 | _ = *p.f[0] //want "dereferenced" 41 | } 42 | 43 | // Here, the error messages for the two dereferences in test3 are similar and should be grouped together. 44 | func (t *T) test3(str string) { 45 | p := t.m[str] 46 | _ = *p.f[0] //want "Same nil source could also cause potential nil panic" 47 | _ = *p.f[1] 48 | } 49 | 50 | // Here, the two error messages in test4 are similar, and ideally they should be grouped together. 51 | // However, since producer position is unavailable, NilAway uses a heuristic combined of the producer and consumer 52 | // error messages to group them, and in this case the consumer messages are different: "dereferenced" and "accessed field". 53 | // Hence, they are not grouped together. 54 | func (t *T) test4(str string) { 55 | p := t.m[str] 56 | _ = *p.f[0] //want "dereferenced" 57 | _ = p.f[1].f //want "accessed field" 58 | } 59 | 60 | // Similar to test4, here the error messages are not grouped since the accessed fields in consumer messages are different. 61 | func (t *T) test5(str string) { 62 | p := t.m[str] 63 | _ = p.f[0].f //want "accessed field `f`" 64 | _ = p.f[1].g //want "accessed field `g`" 65 | } 66 | 67 | // Here, although the two error messages are similar for the pairs test8-test9 and test10-test11, 68 | // they should not be grouped together as they are from different functions. 69 | 70 | func test8(mp map[int]*int) { 71 | _ = *mp[0] //want "dereferenced" 72 | } 73 | 74 | func test9(mp map[int]*int) { 75 | _ = *mp[0] //want "dereferenced" 76 | } 77 | 78 | func test10() { 79 | mp := make(map[int]*int) 80 | _ = *mp[0] //want "dereferenced" 81 | } 82 | 83 | func test11() { 84 | mp := make(map[int]*int) 85 | _ = *mp[0] //want "dereferenced" 86 | } 87 | -------------------------------------------------------------------------------- /testdata/src/ignoredpkg1/main.go: -------------------------------------------------------------------------------- 1 | // Package ignoredpkg1 tests NilAway's ability to ignore packages that are configured to be ignored. 2 | package ignoredpkg1 3 | 4 | var GlobalVar *int 5 | 6 | func main() { 7 | // Directly de-referencing a nil pointer, but it is OK since this package is ignored. 8 | print(*GlobalVar) 9 | } 10 | -------------------------------------------------------------------------------- /testdata/src/ignoredpkg2/main.go: -------------------------------------------------------------------------------- 1 | // Package ignoredpkg2 tests NilAway's ability to ignore packages that are configured to be ignored. 2 | package ignoredpkg2 3 | 4 | var GlobalVar *int 5 | 6 | func main() { 7 | // Directly de-referencing a nil pointer, but it is OK since this package is ignored. 8 | print(*GlobalVar) 9 | } 10 | -------------------------------------------------------------------------------- /testdata/src/prettyprint/main.go: -------------------------------------------------------------------------------- 1 | // Package prettyprint is meant to check if our pretty-print flag has effect. 2 | package prettyprint 3 | 4 | func main() { 5 | var a *int 6 | // Ensure that the ASCII escape code is in the want strings (such that the errors are pretty 7 | // printed). 8 | print(*a) //want "\u001B" 9 | } 10 | -------------------------------------------------------------------------------- /testdata/src/stubs/github.com/stretchr/testify/suite/suite.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 16 | package suite 17 | 18 | import ( 19 | "stubs/github.com/stretchr/testify/assert" 20 | "stubs/github.com/stretchr/testify/require" 21 | ) 22 | 23 | type any interface{} 24 | 25 | // these stubs simulate the real `github.com/stretchr/testify/suite` package because we can't import it in tests 26 | 27 | type Suite struct { 28 | *assert.Assertions 29 | require *require.Assertions 30 | } 31 | 32 | func (suite *Suite) Require() *require.Assertions { 33 | return suite.require 34 | } 35 | 36 | func (suite *Suite) Assert() *assert.Assertions { 37 | return suite.Assertions 38 | } 39 | -------------------------------------------------------------------------------- /testdata/src/stubs/go.uber.org/zap/zap.go: -------------------------------------------------------------------------------- 1 | package zap 2 | 3 | // NewProduction builds a sensible production Logger. 4 | func NewProduction() (*Logger, error) { 5 | return &Logger{}, nil 6 | } 7 | 8 | 9 | // Logger is a logger interface that provides structured, leveled logging. 10 | type Logger struct{} 11 | 12 | // Field represents a key-value pair for structured logging. 13 | type Field struct{} 14 | 15 | // Fatal logs a message at fatal level and then calls os.Exit(1). 16 | func (l *Logger) Fatal(msg string, fields ...Field) {} 17 | 18 | // Sugared returns a SugaredLogger wrapping this Logger. 19 | func (l *Logger) Sugared() *SugaredLogger { 20 | return &SugaredLogger{} 21 | } 22 | 23 | // SugaredLogger wraps the base Logger to provide a more ergonomic, but slightly slower, 24 | // API. In particular, any key/value pairs passed as arguments are added to the logged 25 | // context using fmt.Sprint-style interpolation. 26 | type SugaredLogger struct{} 27 | 28 | // Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit(1). 29 | func (s *SugaredLogger) Fatal(args ...interface{}) {} 30 | 31 | // Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit(1). 32 | func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {} 33 | 34 | // Fatalln uses fmt.Sprintln to construct and log a message, then calls os.Exit(1). 35 | func (s *SugaredLogger) Fatalln(args ...interface{}) {} 36 | 37 | // Fatalw logs a message with some additional context, then calls os.Exit(1). 38 | // The variadic key-value pairs are treated as they are in With. 39 | func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {} 40 | -------------------------------------------------------------------------------- /tools/cmd/integration-test/standalone.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "os/exec" 21 | "path/filepath" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | // StandaloneDriver implements Driver for running NilAway as a standalone binary. 27 | type StandaloneDriver struct{} 28 | 29 | // Run runs NilAway as a standalone binary on the test project and returns the diagnostics. 30 | func (d *StandaloneDriver) Run(dir string) (map[Position]string, error) { 31 | // Build NilAway first. 32 | if out, err := exec.Command("make", "build").CombinedOutput(); err != nil { 33 | return nil, fmt.Errorf("build NilAway: %w: %q", err, string(out)) 34 | } 35 | 36 | // Run the NilAway binary on the integration test project, with redirects to an internal buffer. 37 | cmd := exec.Command(filepath.Join("..", "..", "bin", "nilaway"), 38 | "-json", "-pretty-print=false", 39 | // Disable group error messages to make the output accurate for comparisons. 40 | "-group-error-messages=false", 41 | "./...", 42 | ) 43 | cmd.Dir = dir 44 | out, err := cmd.CombinedOutput() 45 | if err != nil { 46 | return nil, fmt.Errorf("run nilaway: %w\n%s", err, string(out)) 47 | } 48 | 49 | // Parse the diagnostics. 50 | type diagnostic struct { 51 | Posn string `json:"posn"` 52 | Message string `json:"message"` 53 | } 54 | // pkg name -> "nilaway" -> list of diagnostics. 55 | var result map[string]map[string][]diagnostic 56 | if err := json.Unmarshal(out, &result); err != nil { 57 | return nil, fmt.Errorf("decode nilaway output: %w", err) 58 | } 59 | 60 | collected := make(map[Position]string) 61 | for _, m := range result { 62 | diagnostics, ok := m["nilaway"] 63 | if !ok { 64 | return nil, fmt.Errorf("expect \"nilaway\" key in result, got %+v", m) 65 | } 66 | for _, d := range diagnostics { 67 | parts := strings.Split(d.Posn, ":") 68 | if len(parts) != 3 { 69 | return nil, fmt.Errorf("expect 3 parts in position string, got %+v", d) 70 | } 71 | // Convert diagnostic output from NilAway to canonical form. 72 | line, err := strconv.Atoi(parts[1]) 73 | if err != nil { 74 | return nil, fmt.Errorf("convert line number: %w", err) 75 | } 76 | pos := Position{Filename: parts[0], Line: line} 77 | if current, ok := collected[pos]; ok { 78 | return nil, fmt.Errorf("multiple diagnostics on the same line not supported, current: %q, got: %q", current, d.Message) 79 | } 80 | collected[pos] = d.Message 81 | } 82 | } 83 | 84 | return collected, nil 85 | } 86 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module go.uber.org/nilaway/tools 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.4 6 | 7 | require ( 8 | github.com/fatih/color v1.18.0 9 | github.com/stretchr/testify v1.10.0 10 | go.uber.org/goleak v1.3.0 11 | golang.org/x/tools v0.31.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/text v0.2.0 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | golang.org/x/mod v0.24.0 // indirect 21 | golang.org/x/sync v0.12.0 // indirect 22 | golang.org/x/sys v0.31.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 5 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 6 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 7 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 8 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 10 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 11 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 12 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 13 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 14 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 15 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 16 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 20 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 21 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 22 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 23 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 24 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 25 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 26 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 27 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 30 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 31 | golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= 32 | golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 35 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /util/analysishelper/analyzer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package analysishelper provides helper functions for the `go/analysis` package. 16 | package analysishelper 17 | 18 | import ( 19 | "fmt" 20 | "runtime/debug" 21 | 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | // Result is the result struct for the sub-analyzers where the actual result is accompanied by 26 | // an optional error. 27 | type Result[T any] struct { 28 | // Res is the actual result from the sub-analyzer. 29 | Res T 30 | // Err is the optional error from the sub-analyzer. 31 | Err error 32 | } 33 | 34 | // WrapRun wraps the run function of an analyzer to: 35 | // (1) convert the return type to Result[T] and put the error in the Result[T].Err field in order 36 | // to _not_ stop the analysis and let upper-level analyzer to decide what to do. 37 | // (2) recover from a panic and convert it to an error with stack traces for easier debugging. 38 | // This is to ensure that NilAway _never_ panics during the analysis. 39 | // Moreover, it also wraps the error from the sub-analyzer with the name of the analyzer to make 40 | // it easier to identify the source of the error. 41 | func WrapRun[T any](f func(*analysis.Pass) (T, error)) func(*analysis.Pass) (any, error) { 42 | wrapped := func(pass *analysis.Pass) (result any, _ error) { 43 | result = &Result[T]{} 44 | analyzerName := "" 45 | if pass != nil && pass.Analyzer != nil { 46 | analyzerName = pass.Analyzer.Name 47 | } 48 | defer func() { 49 | if r := recover(); r != nil { 50 | result.(*Result[T]).Err = fmt.Errorf("INTERNAL PANIC from %q: %s\n%s", analyzerName, r, string(debug.Stack())) 51 | } 52 | }() 53 | 54 | r, err := f(pass) 55 | if err != nil { 56 | // Prefix the error with the name of the analyzer to make it easier to identify the source 57 | // of the error. 58 | err = fmt.Errorf("%s: %w", analyzerName, err) 59 | } 60 | result.(*Result[T]).Res = r 61 | result.(*Result[T]).Err = err 62 | return result, nil 63 | } 64 | 65 | return wrapped 66 | } 67 | -------------------------------------------------------------------------------- /util/analysishelper/analyzer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package analysishelper 16 | 17 | import ( 18 | "errors" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | "golang.org/x/tools/go/analysis" 23 | ) 24 | 25 | func TestWrapPanic_Panic(t *testing.T) { 26 | t.Parallel() 27 | 28 | // Test that WrapRun recovers from a panic and returns the panic as an error. 29 | panickingFunc := func(*analysis.Pass) (int, error) { panic("panic") } 30 | wrapped := WrapRun(panickingFunc) 31 | r, err := wrapped(nil /* pass */) 32 | // No error should be returned, since the converted error (from the recovered panic) is 33 | // returned via the result struct. 34 | require.NoError(t, err) 35 | 36 | require.IsType(t, &Result[int]{}, r) 37 | require.Empty(t, r.(*Result[int]).Res) 38 | require.ErrorContains(t, r.(*Result[int]).Err, "INTERNAL PANIC") 39 | } 40 | 41 | func TestWrapPanic_Error(t *testing.T) { 42 | t.Parallel() 43 | 44 | // Test that WrapRun does not recover from a panic and returns the panic as an error. 45 | errFunc := func(*analysis.Pass) (int, error) { return 0, errors.New("my error") } 46 | wrapped := WrapRun(errFunc) 47 | r, err := wrapped(nil /* pass */) 48 | require.NoError(t, err) 49 | 50 | require.IsType(t, &Result[int]{}, r) 51 | require.Empty(t, r.(*Result[int]).Res) 52 | require.ErrorContains(t, r.(*Result[int]).Err, "my error") 53 | } 54 | 55 | func TestWrapPanic_NoPanic(t *testing.T) { 56 | t.Parallel() 57 | 58 | // Test that WrapRun does not recover from a panic and returns the panic as an error. 59 | nonPanickingFunc := func(*analysis.Pass) (int, error) { return 42, nil } 60 | wrapped := WrapRun(nonPanickingFunc) 61 | r, err := wrapped(nil) 62 | // No error should be returned, since the converted error (from the recovered panic) is 63 | // returned via the result struct. 64 | require.NoError(t, err) 65 | 66 | require.IsType(t, &Result[int]{}, r) 67 | require.NoError(t, r.(*Result[int]).Err) 68 | require.Equal(t, 42, r.(*Result[int]).Res) 69 | } 70 | -------------------------------------------------------------------------------- /util/asthelper/asthelper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package asthelper 16 | 17 | import ( 18 | "go/ast" 19 | "go/token" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestDocContains(t *testing.T) { 26 | t.Parallel() 27 | 28 | testcases := []struct { 29 | description string 30 | s string 31 | node *ast.CommentGroup 32 | want bool 33 | }{ 34 | { 35 | description: "contains the text", 36 | s: "42", 37 | node: &ast.CommentGroup{List: []*ast.Comment{ 38 | {Slash: token.Pos(1), Text: "my comment 42"}, 39 | }}, 40 | want: true, 41 | }, 42 | { 43 | description: "contains the text in separate comment inside the group", 44 | s: "42", 45 | node: &ast.CommentGroup{List: []*ast.Comment{ 46 | {Slash: token.Pos(1), Text: "my comment"}, 47 | {Slash: token.Pos(10), Text: "some 42 some other text"}, 48 | }}, 49 | want: true, 50 | }, 51 | { 52 | description: "does not contain the text in nil group", 53 | s: "42", 54 | node: nil, 55 | want: false, 56 | }, 57 | { 58 | description: "does not contain the text", 59 | s: "42", 60 | node: &ast.CommentGroup{List: []*ast.Comment{ 61 | {Slash: token.Pos(1), Text: "my comment"}, 62 | {Slash: token.Pos(10), Text: "some some other text"}, 63 | }}, 64 | want: false, 65 | }, 66 | } 67 | 68 | for _, tc := range testcases { 69 | tc := tc 70 | t.Run(tc.description, func(t *testing.T) { 71 | t.Parallel() 72 | require.Equal(t, tc.want, DocContains(tc.node, tc.s)) 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /util/tokenhelper/tokenhelper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package tokenhelper hosts helper functions that enhance the `token` package (e.g., around 16 | // position and file path formatting etc.). 17 | package tokenhelper 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | var _cwd, _cwdErr = os.Getwd() 25 | 26 | // RelToCwd returns the relative path of the given filename with respect to the current 27 | // working directory (retrieved during initialization). If the filename is not a child of 28 | // the current working directory, it returns the filename itself. 29 | func RelToCwd(filename string) string { 30 | if _cwdErr != nil { 31 | panic("failed to get current working directory: " + _cwdErr.Error()) 32 | } 33 | rel, err := filepath.Rel(_cwd, filename) 34 | if err == nil { 35 | return rel 36 | } 37 | return filename 38 | } 39 | -------------------------------------------------------------------------------- /util/tokenhelper/tokenhelper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tokenhelper 16 | 17 | import ( 18 | "os" 19 | "path/filepath" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestRelToCwd(t *testing.T) { 26 | t.Parallel() 27 | 28 | cwd, err := os.Getwd() 29 | require.NoError(t, err) 30 | 31 | testcases := []struct { 32 | give string 33 | want string 34 | }{ 35 | {give: filepath.Join(cwd, "testdata", "foo.go"), want: filepath.Join("testdata", "foo.go")}, 36 | {give: filepath.Join("testdata", "foo.go"), want: filepath.Join("testdata", "foo.go")}, 37 | } 38 | for _, tc := range testcases { 39 | t.Run(tc.give, func(t *testing.T) { 40 | t.Parallel() 41 | 42 | require.Equal(t, tc.want, RelToCwd(tc.give)) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /util/typeshelper/typeshelper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package typeshelper implements utility functions for the go/types package. 16 | package typeshelper 17 | 18 | import ( 19 | "go/types" 20 | ) 21 | 22 | // IsIterType returns true if the underlying type is an iterator func: 23 | // 24 | // func(func() bool) 25 | // func(func(K) bool) 26 | // func(func(K, V) bool) 27 | // 28 | // See more at https://tip.golang.org/doc/go1.23. 29 | func IsIterType(t types.Type) bool { 30 | // Ensure it is a function signature. 31 | sig, ok := t.Underlying().(*types.Signature) 32 | if !ok { 33 | return false 34 | } 35 | 36 | // Ensure it has exactly one parameter (the yield func). 37 | params := sig.Params() 38 | if params.Len() != 1 { 39 | return false 40 | } 41 | 42 | // Ensure the single parameter is a function type (the yield func). 43 | paramType, ok := params.At(0).Type().Underlying().(*types.Signature) 44 | if !ok { 45 | return false 46 | } 47 | 48 | // Ensure the yield func takes fewer than 2 arguments and returns exactly one boolean value. 49 | res := paramType.Results() 50 | if paramType.Params().Len() > 2 || res.Len() != 1 { 51 | return false 52 | } 53 | 54 | // Final check: ensure the return type of the yield func is a boolean. 55 | basic, ok := res.At(0).Type().Underlying().(*types.Basic) 56 | return ok && basic.Kind() == types.Bool 57 | } 58 | -------------------------------------------------------------------------------- /util/typeshelper/typeshelper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Uber Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package typeshelper 16 | 17 | import ( 18 | "go/token" 19 | "go/types" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestIsIterType(t *testing.T) { 26 | t.Parallel() 27 | 28 | tests := []struct { 29 | name string 30 | typeStr string 31 | want bool 32 | }{ 33 | {"ValidIterator0", "func(func() bool)", true}, 34 | {"ValidIterator1", "func(func(int) bool)", true}, 35 | {"ValidIterator2", "func(func(int, string) bool)", true}, 36 | {"InvalidNonFunc", "int", false}, 37 | {"InvalidFuncWrongReturn", "func(func(int) int)", false}, 38 | {"InvalidFuncNoBool", "func(func(int, string))", false}, 39 | {"InvalidFuncTooManyArgs", "func(func() bool, string)", false}, 40 | {"InvalidFuncNotFuncType", "func(bool)", false}, 41 | } 42 | 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | t.Parallel() 46 | 47 | pkg := types.NewPackage("testpkg", "testpkg") 48 | typeInfo, err := types.Eval(token.NewFileSet(), pkg, 0, tt.typeStr) 49 | if err != nil { 50 | t.Fatalf("failed to evaluate type: %v", err) 51 | } 52 | 53 | got := IsIterType(typeInfo.Type) 54 | require.Equal(t, tt.want, got, "IsIterType(%s) = %v, want %v", tt.typeStr, got, tt.want) 55 | }) 56 | } 57 | } 58 | --------------------------------------------------------------------------------