Repository: dominikh/go-tools
Branch: master
Commit: 31e1ee5e554a
Files: 1106
Total size: 2.0 MB
Directory structure:
gitextract_okzdp02o/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── 1_false_positive.md
│ │ ├── 2_false_negative.md
│ │ ├── 3_bug.md
│ │ ├── 4_other.md
│ │ └── config.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── LICENSE-THIRD-PARTY
├── README.md
├── _benchmarks/
│ ├── bench.sh
│ └── silent-staticcheck.sh
├── add-check.go
├── analysis/
│ ├── callcheck/
│ │ └── callcheck.go
│ ├── code/
│ │ ├── code.go
│ │ ├── code_test.go
│ │ └── visit.go
│ ├── dfa/
│ │ ├── dfa.el
│ │ └── dfa.go
│ ├── edit/
│ │ └── edit.go
│ ├── facts/
│ │ ├── deprecated/
│ │ │ ├── deprecated.go
│ │ │ ├── deprecated_test.go
│ │ │ └── testdata/
│ │ │ └── src/
│ │ │ └── example.com/
│ │ │ └── Deprecated/
│ │ │ └── Deprecated.go
│ │ ├── directives/
│ │ │ └── directives.go
│ │ ├── generated/
│ │ │ └── generated.go
│ │ ├── nilness/
│ │ │ ├── nilness.go
│ │ │ ├── nilness_test.go
│ │ │ └── testdata/
│ │ │ └── src/
│ │ │ └── example.com/
│ │ │ └── Nilness/
│ │ │ ├── Nilness.go
│ │ │ ├── Nilness_go118.go
│ │ │ └── Nilness_go17.go
│ │ ├── purity/
│ │ │ ├── purity.go
│ │ │ ├── purity_test.go
│ │ │ └── testdata/
│ │ │ └── src/
│ │ │ └── example.com/
│ │ │ └── Purity/
│ │ │ └── CheckPureFunctions.go
│ │ ├── tokenfile/
│ │ │ └── token.go
│ │ └── typedness/
│ │ ├── testdata/
│ │ │ └── src/
│ │ │ └── example.com/
│ │ │ └── Typedness/
│ │ │ └── Typedness.go
│ │ ├── typedness.go
│ │ └── typedness_test.go
│ ├── lint/
│ │ ├── lint.go
│ │ └── testutil/
│ │ ├── check.go
│ │ └── util.go
│ └── report/
│ ├── report.go
│ └── report_test.go
├── cmd/
│ ├── staticcheck/
│ │ ├── README.md
│ │ └── staticcheck.go
│ ├── structlayout/
│ │ ├── README.md
│ │ └── main.go
│ ├── structlayout-optimize/
│ │ └── main.go
│ └── structlayout-pretty/
│ └── main.go
├── config/
│ ├── config.go
│ └── example.conf
├── debug/
│ └── debug.go
├── dist/
│ └── build.sh
├── doc/
│ ├── articles/
│ │ └── customizing_staticcheck.html
│ └── run.html
├── generate.go
├── go/
│ ├── ast/
│ │ └── astutil/
│ │ ├── upstream.go
│ │ └── util.go
│ ├── buildid/
│ │ ├── UPSTREAM
│ │ ├── buildid.go
│ │ └── note.go
│ ├── gcsizes/
│ │ ├── LICENSE
│ │ └── sizes.go
│ ├── ir/
│ │ ├── LICENSE
│ │ ├── UPSTREAM
│ │ ├── bench_test.go
│ │ ├── blockopt.go
│ │ ├── builder.go
│ │ ├── builder_test.go
│ │ ├── const.go
│ │ ├── create.go
│ │ ├── doc.go
│ │ ├── dom.go
│ │ ├── emit.go
│ │ ├── example_test.go
│ │ ├── func.go
│ │ ├── html.go
│ │ ├── irutil/
│ │ │ ├── load.go
│ │ │ ├── load_test.go
│ │ │ ├── loops.go
│ │ │ ├── stub.go
│ │ │ ├── switch.go
│ │ │ ├── switch_test.go
│ │ │ ├── terminates.go
│ │ │ ├── testdata/
│ │ │ │ └── switches.go
│ │ │ ├── util.go
│ │ │ └── visit.go
│ │ ├── lift.go
│ │ ├── lvalue.go
│ │ ├── methods.go
│ │ ├── mode.go
│ │ ├── print.go
│ │ ├── sanity.go
│ │ ├── source.go
│ │ ├── source_test.go
│ │ ├── ssa.go
│ │ ├── stdlib_test.go
│ │ ├── testdata/
│ │ │ ├── objlookup.go
│ │ │ └── valueforexpr.go
│ │ ├── util.go
│ │ ├── wrappers.go
│ │ └── write.go
│ ├── loader/
│ │ ├── hash.go
│ │ └── loader.go
│ └── types/
│ └── typeutil/
│ ├── ext.go
│ ├── typeparams.go
│ ├── typeparams_test.go
│ ├── upstream.go
│ └── util.go
├── go.mod
├── go.sum
├── internal/
│ ├── analysisinternal/
│ │ └── typeindex/
│ │ └── typeindex.go
│ ├── cmd/
│ │ ├── ast-to-pattern/
│ │ │ ├── main.go
│ │ │ └── parse.go
│ │ ├── gogrep/
│ │ │ └── gogrep.go
│ │ ├── irdump/
│ │ │ └── main.go
│ │ └── unused/
│ │ └── unused.go
│ ├── diff/
│ │ └── myers/
│ │ └── diff.go
│ ├── passes/
│ │ └── buildir/
│ │ ├── buildir.go
│ │ ├── buildir_test.go
│ │ └── testdata/
│ │ └── src/
│ │ └── a/
│ │ └── a.go
│ ├── renameio/
│ │ ├── UPSTREAM
│ │ ├── renameio.go
│ │ ├── renameio_test.go
│ │ └── umask_test.go
│ ├── robustio/
│ │ ├── UPSTREAM
│ │ ├── robustio.go
│ │ ├── robustio_darwin.go
│ │ ├── robustio_flaky.go
│ │ ├── robustio_other.go
│ │ └── robustio_windows.go
│ ├── sharedcheck/
│ │ └── lint.go
│ ├── sync/
│ │ └── sync.go
│ ├── testenv/
│ │ ├── UPSTREAM
│ │ ├── testenv.go
│ │ └── testenv_112.go
│ └── typesinternal/
│ └── typeindex/
│ └── typeindex.go
├── knowledge/
│ ├── arg.go
│ ├── deprecated.go
│ ├── doc.go
│ ├── signatures.go
│ └── targets.go
├── lintcmd/
│ ├── cache/
│ │ ├── UPSTREAM
│ │ ├── cache.go
│ │ ├── cache_test.go
│ │ ├── default.go
│ │ ├── hash.go
│ │ └── hash_test.go
│ ├── cmd.go
│ ├── cmd_test.go
│ ├── config.go
│ ├── config_test.go
│ ├── directives.go
│ ├── format.go
│ ├── lint.go
│ ├── runner/
│ │ ├── runner.go
│ │ └── stats.go
│ ├── sarif.go
│ ├── stats.go
│ ├── stats_bsd.go
│ ├── stats_posix.go
│ └── version/
│ ├── buildinfo.go
│ └── version.go
├── pattern/
│ ├── convert.go
│ ├── doc.go
│ ├── lexer.go
│ ├── match.go
│ ├── parser.go
│ ├── parser_test.go
│ ├── pattern.go
│ └── testdata/
│ └── fuzz/
│ └── FuzzParse/
│ ├── 0001cdcefc5f03f99c21d4ef8232d8f0d8510d9c48e8105c927bc70ac02034a9
│ ├── 00ec3673b415e2f6fc4a3f0d31413096921fbd1faa1cbabdd3637480af027a72
│ ├── 02f183192c9bcfbb22db5afa08e5a9a84babfca022726d0121f42c68d3feecee
│ ├── 04fca5bfcc4a67c0d97de75fd6dc13a4a3e5c2dc68e5061f7bcb7e19852efe56
│ ├── 05eea82b6791ec62e197e6128c608c67f5393ff98e94a9c1ba1311e763778749
│ ├── 06b3cbf8b7806ca08ce1ca466e83488ca32abb5db6b0ca4b07c54aa7be47adf3
│ ├── 09c3a6a518c0e44fe60591523655ba4d7dcf62cb477f7e316a51e089adea74c2
│ ├── 0a21c29e926184ebb3c293c9cea3465ef5e1fc5c1b81be7d0770d5d69ee838a3
│ ├── 0ce7ffb3713ec9373531b2903b8f8751e280cdae2b625dcf35dc1fcd88c592bf
│ ├── 170704499ec0c05bf39fb37f6c5604e13624c4fb531e41305b2439308e370f35
│ ├── 1a3c741fba42577fac3c5035a3d44e5a78bcefa11f9ccc3bb2919376d984e4a2
│ ├── 1eb6c2e8b8e0be47a019f0345b68ebfdba5f05804204e810166d1fe7c12e8556
│ ├── 27e5f99d63fed488c4e9c3ac4a1e364f809ad894cb109aacc9bd6a85c015fdb7
│ ├── 2bac99d4a450641e3ae239588965c64323b1ee9eb2351cc53019d430d3a59efa
│ ├── 2c72a4a6b571446d5374dc5174fa44767bdcc8197e38c54738e50f8b58903230
│ ├── 2f1cdb43e9c62bdb5f8777bc2cb4eee3e8fe173c4361f54833c48d06833ec8fe
│ ├── 312c49b9d41ad52e7beaa65ab01f5416e4f4d1db78b4e0001260ac888256b609
│ ├── 3148b044a5e00e508bfd9ac4d139e032503a590c36bd458a8291b77502d13561
│ ├── 31ac2ece486bde345a4ac42fb989efa8835e72e82e357d5d82a313d6ba03eca2
│ ├── 359bf5d248c22a3fc8d67de10279802663a767d4bf2d11dad3209bee13953ee0
│ ├── 3895395d667f576d7f3891a63e4cc0157b2ec73dbe55745c1cba65f31e8cc5db
│ ├── 3a3ef35129ccc131fc582363751397ad5723fb8ae891c31eaa5ad86ba402a27e
│ ├── 3d78313ab191ebe8647428cd6d896208cb6dcfdd19eb87ae388315548176445a
│ ├── 3e0e018aca3103af7824d729c88c028b8e0d60d3de223c786f46acac3e910cdb
│ ├── 3f66b015db9a62175f277eab5f76a62397c681b7e4ed6564f452e6159d4cb454
│ ├── 4115b01752356dd12fd6499da369daec6031f62d315aecc4afad56c97f61b904
│ ├── 465963a68302ca54f21c75fc3f680d6a5e1065682fb05a1350ed105883436a82
│ ├── 47857edd56b46ac9c16e788e9295d1dafb910c345899aafd618ddaa12793f4f9
│ ├── 4dec90e6083b5e195501df63d8e1ed6813b623bef60ad8d9e0a1df1f251a58f3
│ ├── 51390f40de42348adb99c613cd8367db404851ce3ea1a4e02ea316b5b7e915b7
│ ├── 53da8fdd88cd66de33bbdbbf564e2b14b69d02f32102d8a96171ed4b05dbc92e
│ ├── 56a234ae7b32577f770d5b997f037de709344d7be6fd9ca6e1f44fc8c4367f5b
│ ├── 5baab7b6c2c18988c27aefc55f5d48c6ac583f790fb6763cda34375f9b07da40
│ ├── 5cc91809f9225a218b9cfb3a31d5baed3c5a44b5da3a74184fa97abe3bbf178f
│ ├── 5d9a745f26174c61e5ab0966e4821f75b71de80345be52d4b81aa1515158b735
│ ├── 5e8383a425cf9bc34f43d60f7586184ae7a544e3ad10405ef7aca57246c2ab66
│ ├── 60c36a14214281c0c2c31599563afec69016f469a0f25222a9500e307b159d11
│ ├── 614ea1474d223cb45716d531aa8afac2dfd52938aeb38c64b70a351f0cf509b2
│ ├── 6490b1471fef1f39
│ ├── 6a4d6ea339df8f59816483834329cc4310816de0223bd3607b2af6c91367a59b
│ ├── 6aa9975401e9a24c46284ea6ea1740740fc58950a021c56e1376c2e108ee3b90
│ ├── 6ac1f5e27fbe6d979efae1abf9b2439a824b83f4b2a27508dbeb5dc95b4f9960
│ ├── 6fa1e1e283fd220866a9e5878510db574b761fbd5a0e863e66f40fd4acbbaf07
│ ├── 71e2fa0db72c309e630267beac45c90d37e4b8f9d2d2ed52100d1abca7b72965
│ ├── 76d91998f39bf2e25bd361453a73968274ffe16677cf02d872222d4c799552f8
│ ├── 78eaf491672242d08770ab22b67853f639c767f65346de39c6f3e677b1cd879d
│ ├── 7ba6359207886a1c2c7bbe254835555e87a037ecd3af0301a11a43ec2287c487
│ ├── 7ec87621ab148929b69125a04edd13ff104007ca0d8dff12f281753ea93ffb80
│ ├── 8203d4ee0690ca0d0c4b907e1f1c8d6c1724c4771ec3a685b56b440f52b4282a
│ ├── 8324b925e52410ab88b6265538881346436b67d95ad808b8f9220a84b0772ab7
│ ├── 84e67732ffe4ba2d8fdb8cfc8690804579623dbc9c56a378ca483f088348296a
│ ├── 87839b3497143dd5ea14963b78c011edceb40d13fe1d8cd9b894a81b5dae2200
│ ├── 87f42498d6f57dc40c9972487f0e35d9820acbbce6cf61f3b90dabaa9cb8a8fc
│ ├── 90a846c5b88ccf4fe765113a3580ecc90a5cf083a97f0bc4b3bb53a1f00e3fd8
│ ├── 9437b751fb0f1f07f5dcb8c8a10d0f3d4470a77b7ec77df6be872a109184bd1b
│ ├── 94b7dc35d595dd794b4f65cd35f94ae8fe7c7214e6da8caa69f0b841e9a099af
│ ├── 9d603847ed1c030c81f2289ee576971cd63564cc811afb5c18d5a51db7aefa76
│ ├── a3b8af4d027db37d44e58995ed2ab3cd9f2cb415669287e9e7ce7186534b4b1f
│ ├── aa520290f4868dc3c01f15d2769941654a404b87327f5dde790c99fc2c63d875
│ ├── ac1b69c690b399207dd7fe32f03a12d2731fa2d1704f6b15cfdc7f772b0f3187
│ ├── ad86b3632aca0a27fef3d6d79de5c2bcf1c21f7a6caa1260aab964edc21f3f65
│ ├── af10598249def731ec19ebffa3cbc464892d0e445dbefab9ccf578eae136236a
│ ├── afe5949d38d9171e39ad413d31abfab6bf45d066b700b4e84a232a6b3aa53085
│ ├── b44bceab2d84f09950aa80d8541c18e31a3d5dde6e874fd0bfe2e4ce54606db0
│ ├── b553c9e015253a9e3d4e202fdb2d90764151e24219f26f7510a433d30323666e
│ ├── b6a22c4a4f5e0cf4a291f2d6f03860631075934e4069959665d1f8097c69d0d0
│ ├── b95053e6ea7644faad4e0f2e5f308ca66d6a05c47bf36d0fde268fc12e09ca63
│ ├── ba95d1477ea1b35a949c6b469077d908b1cbcaf7fbf3ce9ef544bfeb24f877fb
│ ├── bb62ca358e19867f7d31400cb2a65aac1e918308212c43d10cca21feeb9c99d2
│ ├── c1a2c8141751527604100e865db8d0e711ce25fc5c291b7702752496ac4b2546
│ ├── c30ca6d4801d71144c641960df6919115149d2b6fae5f7d9b2bac2b8cd6b8d25
│ ├── c5f48734d853b82016955671d916daaf72da20a5f8335dddf7640fab1f5a3acb
│ ├── c6d06d254dee12276b9b46ef9be863a1eefc4d0673946a706ec7a164625595f0
│ ├── c7abb7fc60634bb8d57b5b7c225a6accf0d2eb56c88bfe5e44cdd3e0c3e29666
│ ├── ca92b01f6dbdcb91e335219081aa48c16893c217bf6edc020fcb78b3ebabcd1f
│ ├── e027a03ee012e289def51d770ead1e8a136b60989d3d1fb9388a394da2f595da
│ ├── e1e59b9e6718f5089e98c955c391d38c7e243495ece9598826492ab734e5171f
│ ├── e43aa6da655e6c326cfb1f8c9970b603411caf262af4a50980c5a5987ee696f3
│ ├── e48990bfca21324ab7a29098b9a4b40fbd22bd5adcfa316b4b8af460a232b638
│ ├── eb0ecf0066fdafbe218a736d3fc071a52408311637cc527db239f110418e8616
│ ├── ed6769b59df864327fba2b109f0cb965e5b8a6e5f1085e36f5635f1d65003a00
│ ├── f640eee2b04d1b52793ba88998a86702893e23d2563d017be9be90efc04a43c6
│ ├── f855d335a52bd8b6ed4472abb33c0eb8f67a63d84f1c27398c23689fb2720645
│ ├── fac160433f2d82b3c15a8c6ad3938fd85825a4f248108538938a57914e80f114
│ ├── fb2c5ef5801f44e5bee94b82dbb1bc787cc4b7fbdb17e5cfcc4283f2c726a99f
│ ├── fe6c6578776a5ce92474e943ac14979a308d4151d779fd4cfd782f7fb970165e
│ └── ff2017b5c630d7225812cfa8b29b6ebad665505492db847722ba79da5d2c89eb
├── printf/
│ ├── fuzz.go
│ ├── printf.go
│ └── printf_test.go
├── quickfix/
│ ├── analysis.go
│ ├── doc.go
│ ├── qf1001/
│ │ ├── qf1001.go
│ │ ├── qf1001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDeMorgan/
│ │ ├── CheckDeMorgan.go
│ │ ├── CheckDeMorgan.go.golden
│ │ ├── kvexpr.go
│ │ └── kvexpr.go.golden
│ ├── qf1002/
│ │ ├── qf1002.go
│ │ ├── qf1002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTaglessSwitch/
│ │ ├── CheckTaglessSwitch.go
│ │ └── CheckTaglessSwitch.go.golden
│ ├── qf1003/
│ │ ├── qf1003.go
│ │ ├── qf1003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIfElseToSwitch/
│ │ ├── CheckIfElseToSwitch.go
│ │ └── CheckIfElseToSwitch.go.golden
│ ├── qf1004/
│ │ ├── qf1004.go
│ │ ├── qf1004_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckStringsReplaceAll/
│ │ ├── CheckStringsReplaceAll.go
│ │ └── CheckStringsReplaceAll.go.golden
│ ├── qf1005/
│ │ ├── qf1005.go
│ │ ├── qf1005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckMathPow/
│ │ ├── CheckMathPow.go
│ │ └── CheckMathPow.go.golden
│ ├── qf1006/
│ │ ├── qf1006.go
│ │ ├── qf1006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckForLoopIfBreak/
│ │ ├── CheckForLoopIfBreak.go
│ │ └── CheckForLoopIfBreak.go.golden
│ ├── qf1007/
│ │ ├── qf1007.go
│ │ ├── qf1007_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckConditionalAssignment/
│ │ ├── CheckConditionalAssignment.go
│ │ └── CheckConditionalAssignment.go.golden
│ ├── qf1008/
│ │ ├── qf1008.go
│ │ ├── qf1008_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ ├── CheckExplicitEmbeddedSelector/
│ │ │ ├── CheckExplicitEmbeddedSelector-anon.go
│ │ │ ├── CheckExplicitEmbeddedSelector-anon.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-basic.go
│ │ │ ├── CheckExplicitEmbeddedSelector-basic.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-call.go
│ │ │ ├── CheckExplicitEmbeddedSelector-call.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-depth.go
│ │ │ ├── CheckExplicitEmbeddedSelector-depth.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-multi.go
│ │ │ ├── CheckExplicitEmbeddedSelector-multi.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-multi2.go
│ │ │ ├── CheckExplicitEmbeddedSelector-multi2.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-partial-multi.go
│ │ │ ├── CheckExplicitEmbeddedSelector-partial-multi.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-qualified.go
│ │ │ ├── CheckExplicitEmbeddedSelector-qualified.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-recursive.go
│ │ │ ├── CheckExplicitEmbeddedSelector-recursive.go.golden
│ │ │ ├── CheckExplicitEmbeddedSelector-shadowing.go
│ │ │ ├── CheckExplicitEmbeddedSelector-unexported.go
│ │ │ └── CheckExplicitEmbeddedSelector-unexported.go.golden
│ │ └── CheckExplicitEmbeddedSelectorassist/
│ │ └── assist.go
│ ├── qf1009/
│ │ ├── qf1009.go
│ │ ├── qf1009_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimeEquality/
│ │ ├── CheckTimeEquality.go
│ │ └── CheckTimeEquality.go.golden
│ ├── qf1010/
│ │ ├── qf1010.go
│ │ ├── qf1010_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckByteSlicePrinting/
│ │ │ ├── CheckByteSlicePrinting.go
│ │ │ └── CheckByteSlicePrinting.go.golden
│ │ └── go1.9/
│ │ └── CheckByteSlicePrinting/
│ │ ├── CheckByteSlicePrinting.go
│ │ └── CheckByteSlicePrinting.go.golden
│ ├── qf1011/
│ │ ├── qf1011.go
│ │ ├── qf1011_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckRedundantTypeInDeclaration/
│ │ │ ├── CheckRedundantTypeInDeclaration.go
│ │ │ └── CheckRedundantTypeInDeclaration.go.golden
│ │ └── go1.9/
│ │ └── CheckRedundantTypeInDeclaration/
│ │ ├── README
│ │ ├── cgo.go
│ │ └── cgo.golden
│ └── qf1012/
│ ├── qf1012.go
│ ├── qf1012_test.go
│ └── testdata/
│ └── go1.0/
│ └── CheckWriteBytesSprintf/
│ ├── CheckWriteBytesSprintf.go
│ └── CheckWriteBytesSprintf.go.golden
├── sarif/
│ └── sarif.go
├── simple/
│ ├── analysis.go
│ ├── doc.go
│ ├── s1000/
│ │ ├── s1000.go
│ │ ├── s1000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSingleCaseSelect/
│ │ └── single-case-select.go
│ ├── s1001/
│ │ ├── s1001.go
│ │ ├── s1001_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckLoopCopy/
│ │ │ ├── copy.go
│ │ │ └── copy.go.golden
│ │ └── go1.18/
│ │ └── CheckLoopCopy/
│ │ ├── copy_generics.go
│ │ └── copy_generics.go.golden
│ ├── s1002/
│ │ ├── s1002.go
│ │ ├── s1002_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckIfBoolCmp/
│ │ │ ├── bool-cmp.go
│ │ │ ├── bool-cmp.go.golden
│ │ │ └── bool-cmp_test.go
│ │ └── go1.18/
│ │ └── CheckIfBoolCmp/
│ │ ├── bool-cmp_generics.go
│ │ └── bool-cmp_generics.go.golden
│ ├── s1003/
│ │ ├── s1003.go
│ │ ├── s1003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckStringsContains/
│ │ ├── contains.go
│ │ └── contains.go.golden
│ ├── s1004/
│ │ ├── s1004.go
│ │ ├── s1004_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckBytesCompare/
│ │ ├── compare.go
│ │ └── compare.go.golden
│ ├── s1005/
│ │ ├── s1005.go
│ │ ├── s1005_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckUnnecessaryBlank/
│ │ │ ├── LintBlankOK.go
│ │ │ ├── LintBlankOK.go.golden
│ │ │ ├── receive-blank.go
│ │ │ └── receive-blank.go.golden
│ │ ├── go1.3/
│ │ │ └── CheckUnnecessaryBlank/
│ │ │ └── range.go
│ │ └── go1.4/
│ │ └── CheckUnnecessaryBlank/
│ │ ├── range.go
│ │ └── range.go.golden
│ ├── s1006/
│ │ ├── s1006.go
│ │ ├── s1006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckForTrue/
│ │ ├── for-true.go
│ │ ├── generated.go
│ │ └── input.go
│ ├── s1007/
│ │ ├── s1007.go
│ │ ├── s1007_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckRegexpRaw/
│ │ └── regexp-raw.go
│ ├── s1008/
│ │ ├── s1008.go
│ │ ├── s1008_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIfReturn/
│ │ ├── comment.go
│ │ └── if-return.go
│ ├── s1009/
│ │ ├── s1009.go
│ │ ├── s1009_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckRedundantNilCheckWithLen/
│ │ │ └── nil-len.go
│ │ └── go1.18/
│ │ └── CheckRedundantNilCheckWithLen/
│ │ └── nil-len_generics.go
│ ├── s1010/
│ │ ├── s1010.go
│ │ ├── s1010_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSlicing/
│ │ ├── slicing.go
│ │ └── slicing.go.golden
│ ├── s1011/
│ │ ├── s1011.go
│ │ ├── s1011_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckLoopAppend/
│ │ ├── loop-append.go
│ │ └── loop-append.go.golden
│ ├── s1012/
│ │ ├── s1012.go
│ │ ├── s1012_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimeSince/
│ │ ├── time-since.go
│ │ └── time-since.go.golden
│ ├── s1016/
│ │ ├── s1016.go
│ │ ├── s1016_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckSimplerStructConversion/
│ │ │ ├── convert.go
│ │ │ └── convert.go.golden
│ │ ├── go1.18/
│ │ │ └── CheckSimplerStructConversion/
│ │ │ ├── convert_generics.go
│ │ │ └── convert_generics.go.golden
│ │ ├── go1.7/
│ │ │ └── CheckSimplerStructConversion/
│ │ │ ├── convert.go
│ │ │ └── convert.go.golden
│ │ ├── go1.8/
│ │ │ └── CheckSimplerStructConversion/
│ │ │ ├── convert.go
│ │ │ └── convert.go.golden
│ │ └── go1.9/
│ │ └── CheckSimplerStructConversion/
│ │ ├── convert_alias.go
│ │ └── convert_alias.go.golden
│ ├── s1017/
│ │ ├── s1017.go
│ │ ├── s1017_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTrim/
│ │ └── trim.go
│ ├── s1018/
│ │ ├── s1018.go
│ │ ├── s1018_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckLoopSlide/
│ │ │ ├── LintLoopSlide.go
│ │ │ └── LintLoopSlide.go.golden
│ │ └── go1.18/
│ │ └── CheckLoopSlide/
│ │ ├── generics.go
│ │ └── generics.go.golden
│ ├── s1019/
│ │ ├── s1019.go
│ │ ├── s1019_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckMakeLenCap/
│ │ │ └── LintMakeLenCap.go
│ │ └── go1.18/
│ │ └── CheckMakeLenCap/
│ │ └── CheckMakeLenCap.go
│ ├── s1020/
│ │ ├── s1020.go
│ │ ├── s1020_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckAssertNotNil/
│ │ └── LintAssertNotNil.go
│ ├── s1021/
│ │ ├── s1021.go
│ │ ├── s1021_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDeclareAssign/
│ │ ├── LintDeclareAssign.go
│ │ └── LintDeclareAssign.go.golden
│ ├── s1023/
│ │ ├── s1023.go
│ │ ├── s1023_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ ├── CheckRedundantBreak/
│ │ │ └── LintRedundantBreak.go
│ │ └── CheckRedundantReturn/
│ │ └── LintRedundantReturn.go
│ ├── s1024/
│ │ ├── s1024.go
│ │ ├── s1024_test.go
│ │ └── testdata/
│ │ ├── go1.7/
│ │ │ └── CheckTimeUntil/
│ │ │ └── LimeTimeUntil.go
│ │ └── go1.8/
│ │ └── CheckTimeUntil/
│ │ ├── LimeTimeUntil.go
│ │ └── LimeTimeUntil.go.golden
│ ├── s1025/
│ │ ├── s1025.go
│ │ ├── s1025_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckRedundantSprintf/
│ │ │ ├── LintRedundantSprintf.go
│ │ │ └── LintRedundantSprintf.go.golden
│ │ ├── go1.17/
│ │ │ └── CheckRedundantSprintf/
│ │ │ ├── LintRedundantSprintf.go
│ │ │ └── LintRedundantSprintf.go.golden
│ │ ├── go1.18/
│ │ │ └── CheckRedundantSprintf/
│ │ │ ├── LintRedundantSprintf.go
│ │ │ └── LintRedundantSprintf.go.golden
│ │ └── go1.9/
│ │ └── CheckRedundantSprintf/
│ │ ├── LintRedundantSprintf.go
│ │ └── LintRedundantSprintf.go.golden
│ ├── s1028/
│ │ ├── s1028.go
│ │ ├── s1028_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckErrorsNewSprintf/
│ │ ├── LintErrorsNewSprintf.go
│ │ └── LintErrorsNewSprintf.go.golden
│ ├── s1029/
│ │ ├── s1029.go
│ │ ├── s1029_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckRangeStringRunes/
│ │ │ └── LintRangeStringRunes.go
│ │ └── go1.18/
│ │ └── CheckRangeStringRunes/
│ │ └── generics.go
│ ├── s1030/
│ │ ├── s1030.go
│ │ ├── s1030_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckBytesBufferConversions/
│ │ │ ├── LintBytesBufferConversions.go
│ │ │ └── LintBytesBufferConversions.go.golden
│ │ └── go1.9/
│ │ └── CheckBytesBufferConversions/
│ │ ├── LintBytesBufferConversions.go
│ │ └── LintBytesBufferConversions.go.golden
│ ├── s1031/
│ │ ├── s1031.go
│ │ ├── s1031_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckNilCheckAroundRange/
│ │ │ └── LintNilCheckAroundRange.go
│ │ └── go1.18/
│ │ └── CheckNilCheckAroundRange/
│ │ └── CheckNilCheckAroundRange.go
│ ├── s1032/
│ │ ├── s1032.go
│ │ ├── s1032_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSortHelpers/
│ │ └── LintSortHelpers.go
│ ├── s1033/
│ │ ├── s1033.go
│ │ ├── s1033_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckGuardedDelete/
│ │ ├── LintGuardedDelete.go
│ │ └── LintGuardedDelete.go.golden
│ ├── s1034/
│ │ ├── s1034.go
│ │ ├── s1034_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSimplifyTypeSwitch/
│ │ ├── LintSimplifyTypeSwitch.go
│ │ └── LintSimplifyTypeSwitch.go.golden
│ ├── s1035/
│ │ ├── s1035.go
│ │ ├── s1035_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckRedundantCanonicalHeaderKey/
│ │ ├── LintRedundantCanonicalHeaderKey.go
│ │ └── LintRedundantCanonicalHeaderKey.go.golden
│ ├── s1036/
│ │ ├── s1036.go
│ │ ├── s1036_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUnnecessaryGuard/
│ │ ├── LintUnnecessaryGuard.go
│ │ └── LintUnnecessaryGuard.go.golden
│ ├── s1037/
│ │ ├── s1037.go
│ │ ├── s1037_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckElaborateSleep/
│ │ ├── LintElaborateSleep.go
│ │ └── LintElaborateSleep.go.golden
│ ├── s1038/
│ │ ├── s1038.go
│ │ ├── s1038_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckPrintSprintf/
│ │ └── CheckPrintSprintf.go
│ ├── s1039/
│ │ ├── s1039.go
│ │ ├── s1039_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSprintLiteral/
│ │ ├── CheckSprintLiteral.go
│ │ └── CheckSprintLiteral.go.golden
│ └── s1040/
│ ├── s1040.go
│ ├── s1040_test.go
│ └── testdata/
│ └── go1.0/
│ └── CheckSameTypeTypeAssertion/
│ └── CheckSameTypeTypeAssertion.go
├── staticcheck/
│ ├── analysis.go
│ ├── doc.go
│ ├── fakejson/
│ │ └── encode.go
│ ├── fakereflect/
│ │ └── fakereflect.go
│ ├── fakexml/
│ │ ├── marshal.go
│ │ ├── typeinfo.go
│ │ └── xml.go
│ ├── sa1000/
│ │ ├── sa1000.go
│ │ ├── sa1000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckRegexps/
│ │ └── CheckRegexps.go
│ ├── sa1001/
│ │ ├── sa1001.go
│ │ ├── sa1001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTemplate/
│ │ └── CheckTemplate.go
│ ├── sa1002/
│ │ ├── sa1002.go
│ │ ├── sa1002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimeParse/
│ │ └── CheckTimeParse.go
│ ├── sa1003/
│ │ ├── sa1003.go
│ │ ├── sa1003_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckEncodingBinary/
│ │ │ └── CheckEncodingBinary.go
│ │ ├── go1.7/
│ │ │ └── CheckEncodingBinary/
│ │ │ └── CheckEncodingBinary.go
│ │ └── go1.8/
│ │ └── CheckEncodingBinary/
│ │ └── CheckEncodingBinary.go
│ ├── sa1004/
│ │ ├── sa1004.go
│ │ ├── sa1004_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimeSleepConstant/
│ │ ├── CheckTimeSleepConstant.go
│ │ └── CheckTimeSleepConstant.go.golden
│ ├── sa1005/
│ │ ├── sa1005.go
│ │ ├── sa1005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckExec/
│ │ └── CheckExec.go
│ ├── sa1006/
│ │ ├── sa1006.go
│ │ ├── sa1006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUnsafePrintf/
│ │ ├── CheckUnsafePrintf.go
│ │ └── CheckUnsafePrintf.go.golden
│ ├── sa1007/
│ │ ├── sa1007.go
│ │ ├── sa1007_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckURLs/
│ │ └── CheckURLs.go
│ ├── sa1008/
│ │ ├── sa1008.go
│ │ ├── sa1008_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckCanonicalHeaderKey/
│ │ ├── CheckCanonicalHeaderKey.go
│ │ └── CheckCanonicalHeaderKey.go.golden
│ ├── sa1010/
│ │ ├── sa1010.go
│ │ ├── sa1010_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── checkStdlibUsageRegexpFindAll/
│ │ └── checkStdlibUsageRegexpFindAll.go
│ ├── sa1011/
│ │ ├── sa1011.go
│ │ ├── sa1011_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── checkStdlibUsageUTF8Cutset/
│ │ └── checkStdlibUsageUTF8Cutset.go
│ ├── sa1012/
│ │ ├── sa1012.go
│ │ ├── sa1012_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── checkStdlibUsageNilContext/
│ │ │ ├── checkStdlibUsageNilContext.go
│ │ │ └── checkStdlibUsageNilContext.go.golden
│ │ └── go1.18/
│ │ └── checkStdlibUsageNilContext/
│ │ ├── checkStdlibUsageNilContext_generics.go
│ │ └── checkStdlibUsageNilContext_generics.go.golden
│ ├── sa1013/
│ │ ├── sa1013.go
│ │ ├── sa1013_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── checkStdlibUsageSeeker/
│ │ ├── checkStdlibUsageSeeker.go
│ │ └── checkStdlibUsageSeeker.go.golden
│ ├── sa1014/
│ │ ├── sa1014.go
│ │ ├── sa1014_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUnmarshalPointer/
│ │ └── CheckUnmarshalPointer.go
│ ├── sa1015/
│ │ ├── sa1015.go
│ │ ├── sa1015_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ ├── CheckLeakyTimeTick/
│ │ │ │ └── CheckLeakyTimeTick.go
│ │ │ └── CheckLeakyTimeTick-main/
│ │ │ └── CheckLeakyTimeTick-main.go
│ │ └── go1.23/
│ │ └── CheckLeakyTimeTick/
│ │ └── CheckLeakyTimeTick.go
│ ├── sa1016/
│ │ ├── sa1016.go
│ │ ├── sa1016_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUntrappableSignal/
│ │ ├── CheckUntrappableSignal.go
│ │ ├── CheckUntrappableSignal.go.golden
│ │ ├── CheckUntrappableSignal_unix.go
│ │ └── CheckUntrappableSignal_unix.go.golden
│ ├── sa1017/
│ │ ├── sa1017.go
│ │ ├── sa1017_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUnbufferedSignalChan/
│ │ └── CheckUnbufferedSignalChan.go
│ ├── sa1018/
│ │ ├── sa1018.go
│ │ ├── sa1018_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckStringsReplaceZero/
│ │ └── CheckStringsReplaceZero.go
│ ├── sa1019/
│ │ ├── sa1019.go
│ │ ├── sa1019_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ ├── AnotherCheckDeprecated.assist/
│ │ │ │ └── CheckDeprecatedassist.go
│ │ │ ├── CheckDeprecated/
│ │ │ │ ├── CheckDeprecated.go
│ │ │ │ ├── CheckDeprecated_test.go
│ │ │ │ ├── external_test.go
│ │ │ │ ├── not-protobuf.go
│ │ │ │ └── protobuf.go
│ │ │ ├── CheckDeprecated.assist/
│ │ │ │ └── CheckDeprecatedassist.go
│ │ │ ├── CheckDeprecated.assist_external/
│ │ │ │ └── CheckDeprecatedassist_external.go
│ │ │ └── vendor/
│ │ │ └── github.com/
│ │ │ └── golang/
│ │ │ └── protobuf/
│ │ │ └── proto/
│ │ │ └── pkg.go
│ │ ├── go1.18/
│ │ │ ├── CheckDeprecated/
│ │ │ │ └── CheckDeprecated_generics.go
│ │ │ └── CheckDeprecated.assist/
│ │ │ └── CheckDeprecatedassist_generics.go
│ │ ├── go1.19/
│ │ │ └── CheckDeprecated/
│ │ │ ├── CheckDeprecated.go
│ │ │ └── stub.go
│ │ ├── go1.3/
│ │ │ └── CheckDeprecated/
│ │ │ └── CheckDeprecated.go
│ │ ├── go1.4/
│ │ │ └── CheckDeprecated/
│ │ │ └── CheckDeprecated.go
│ │ └── go1.8/
│ │ └── CheckDeprecated/
│ │ └── CheckDeprecated.go
│ ├── sa1020/
│ │ ├── sa1020.go
│ │ ├── sa1020_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckListenAddress/
│ │ └── CheckListenAddress.go
│ ├── sa1021/
│ │ ├── sa1021.go
│ │ ├── sa1021_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckBytesEqualIP/
│ │ └── CheckBytesEqualIP.go
│ ├── sa1023/
│ │ ├── sa1023.go
│ │ ├── sa1023_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckWriterBufferModified/
│ │ │ └── CheckWriterBufferModified.go
│ │ └── go1.9/
│ │ └── CheckWriterBufferModified/
│ │ └── CheckWriterBufferModified.go
│ ├── sa1024/
│ │ ├── sa1024.go
│ │ ├── sa1024_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNonUniqueCutset/
│ │ └── CheckNonUniqueCutset.go
│ ├── sa1025/
│ │ ├── sa1025.go
│ │ ├── sa1025_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimerResetReturnValue/
│ │ └── CheckTimerResetReturnValue.go
│ ├── sa1026/
│ │ ├── sa1026.go
│ │ ├── sa1026_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckUnsupportedMarshal/
│ │ │ └── CheckUnsupportedMarshal.go
│ │ └── go1.18/
│ │ └── CheckUnsupportedMarshal/
│ │ └── generics.go
│ ├── sa1027/
│ │ ├── sa1027.go
│ │ ├── sa1027_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckAtomicAlignment/
│ │ ├── atomic32.go
│ │ └── atomic64.go
│ ├── sa1028/
│ │ ├── sa1028.go
│ │ ├── sa1028_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSortSlice/
│ │ └── slice.go
│ ├── sa1029/
│ │ ├── sa1029.go
│ │ ├── sa1029_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckWithValueKey/
│ │ └── CheckWithValueKey.go
│ ├── sa1030/
│ │ ├── sa1030.go
│ │ ├── sa1030_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckStrconv/
│ │ │ └── CheckStrconv.go
│ │ └── go1.15/
│ │ └── CheckStrconv/
│ │ ├── CheckStrconv.go
│ │ └── stub.go
│ ├── sa1031/
│ │ ├── sa1031.go
│ │ ├── sa1031_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ ├── CheckEncodingAscii85/
│ │ │ └── CheckEncodingAscii85.go
│ │ ├── CheckEncodingBase32/
│ │ │ └── CheckEncodingBase32.go
│ │ ├── CheckEncodingBase64/
│ │ │ └── CheckEncodingBase64.go
│ │ └── CheckEncodingHex/
│ │ └── CheckEncodingHex.go
│ ├── sa1032/
│ │ ├── sa1032.go
│ │ ├── sa1032_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── example.com/
│ │ └── ErrorsOrder/
│ │ └── ErrorsOrder.go
│ ├── sa2000/
│ │ ├── sa2000.go
│ │ ├── sa2000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckWaitgroupAdd/
│ │ └── CheckWaitgroupAdd.go
│ ├── sa2001/
│ │ ├── sa2001.go
│ │ ├── sa2001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckEmptyCriticalSection/
│ │ └── CheckEmptyCriticalSection.go
│ ├── sa2002/
│ │ ├── sa2002.go
│ │ ├── sa2002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckConcurrentTesting/
│ │ └── CheckConcurrentTesting.go
│ ├── sa2003/
│ │ ├── sa2003.go
│ │ ├── sa2003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDeferLock/
│ │ └── CheckDeferLock.go
│ ├── sa3000/
│ │ ├── sa3000.go
│ │ ├── sa3000_test.go
│ │ └── testdata/
│ │ ├── go1.15/
│ │ │ └── CheckTestMainExit-1/
│ │ │ └── CheckTestMainExit-1.go
│ │ └── go1.4/
│ │ ├── CheckTestMainExit-1/
│ │ │ └── CheckTestMainExit-1.go
│ │ ├── CheckTestMainExit-2/
│ │ │ └── CheckTestMainExit-2.go
│ │ ├── CheckTestMainExit-3/
│ │ │ └── CheckTestMainExit-3.go
│ │ ├── CheckTestMainExit-4/
│ │ │ └── CheckTestMainExit-4.go
│ │ └── CheckTestMainExit-5/
│ │ └── CheckTestMainExit-5.go
│ ├── sa3001/
│ │ ├── sa3001.go
│ │ ├── sa3001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckBenchmarkN/
│ │ └── CheckBenchmarkN.go
│ ├── sa4000/
│ │ ├── sa4000.go
│ │ ├── sa4000_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckLhsRhsIdentical/
│ │ │ ├── CheckLhsRhsIdentical.go
│ │ │ └── cgo.go
│ │ ├── go1.18/
│ │ │ └── CheckLhsRhsIdentical/
│ │ │ └── generics.go
│ │ └── go1.22/
│ │ └── CheckLhsRhsIdentical/
│ │ └── randv2.go
│ ├── sa4001/
│ │ ├── sa4001.go
│ │ ├── sa4001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIneffectiveCopy/
│ │ └── CheckIneffectiveCopy.go
│ ├── sa4003/
│ │ ├── sa4003.go
│ │ ├── sa4003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckExtremeComparison/
│ │ ├── CheckExtremeComparison.go
│ │ └── CheckExtremeComparison64.go
│ ├── sa4004/
│ │ ├── sa4004.go
│ │ ├── sa4004_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckIneffectiveLoop/
│ │ │ └── CheckIneffectiveLoop.go
│ │ └── go1.18/
│ │ └── CheckIneffectiveLoop/
│ │ └── CheckIneffectiveLoop_generics.go
│ ├── sa4005/
│ │ ├── sa4005.go
│ │ ├── sa4005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIneffectiveFieldAssignments/
│ │ ├── CheckIneffectiveFieldAssignments.go
│ │ └── issue141.go
│ ├── sa4006/
│ │ ├── sa4006.go
│ │ ├── sa4006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckUnreadVariableValues/
│ │ ├── CheckUnreadVariableValues.go
│ │ └── CheckUnreadVariableValues_test.go
│ ├── sa4008/
│ │ ├── sa4008.go
│ │ ├── sa4008_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckLoopCondition/
│ │ └── CheckLoopCondition.go
│ ├── sa4009/
│ │ ├── sa4009.go
│ │ ├── sa4009_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckArgOverwritten/
│ │ └── CheckArgOverwritten.go
│ ├── sa4010/
│ │ ├── sa4010.go
│ │ ├── sa4010_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIneffectiveAppend/
│ │ └── CheckIneffectiveAppend.go
│ ├── sa4011/
│ │ ├── sa4011.go
│ │ ├── sa4011_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckScopedBreak/
│ │ └── CheckScopedBreak.go
│ ├── sa4012/
│ │ ├── sa4012.go
│ │ ├── sa4012_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNaNComparison/
│ │ └── CheckNaNComparison.go
│ ├── sa4013/
│ │ ├── sa4013.go
│ │ ├── sa4013_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDoubleNegation/
│ │ ├── CheckDoubleNegation.go
│ │ └── CheckDoubleNegation.go.golden
│ ├── sa4014/
│ │ ├── sa4014.go
│ │ ├── sa4014_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckRepeatedIfElse/
│ │ └── CheckRepeatedIfElse.go
│ ├── sa4015/
│ │ ├── sa4015.go
│ │ ├── sa4015_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckMathInt/
│ │ │ └── CheckMathInt.go
│ │ └── go1.18/
│ │ └── CheckMathInt/
│ │ └── CheckMathInt.go
│ ├── sa4016/
│ │ ├── sa4016.go
│ │ ├── sa4016_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ ├── CheckSillyBitwiseOps/
│ │ │ │ └── CheckSillyBitwiseOps.go
│ │ │ ├── CheckSillyBitwiseOps_dotImport/
│ │ │ │ ├── foo.go
│ │ │ │ └── foo_test.go
│ │ │ └── CheckSillyBitwiseOps_shadowedIota/
│ │ │ └── shadowed.go
│ │ └── go1.18/
│ │ └── CheckSillyBitwiseOps/
│ │ └── generics.go
│ ├── sa4017/
│ │ ├── sa4017.go
│ │ ├── sa4017_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSideEffectFreeCalls/
│ │ ├── CheckSideEffectFreeCalls.go
│ │ └── CheckSideEffectFreeCalls_test.go
│ ├── sa4018/
│ │ ├── sa4018.go
│ │ ├── sa4018_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSelfAssignment/
│ │ └── CheckSelfAssignment.go
│ ├── sa4019/
│ │ ├── sa4019.go
│ │ ├── sa4019_test.go
│ │ └── testdata/
│ │ └── go1.1/
│ │ └── CheckDuplicateBuildConstraints/
│ │ └── CheckDuplicateBuildConstraints.go
│ ├── sa4020/
│ │ ├── sa4020.go
│ │ ├── sa4020_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckUnreachableTypeCases/
│ │ │ └── CheckUnreachableTypeCases.go
│ │ └── go1.18/
│ │ └── CheckUnreachableTypeCases/
│ │ └── typeparams.go
│ ├── sa4021/
│ │ ├── sa4021.go
│ │ ├── sa4021_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSingleArgAppend/
│ │ └── CheckSingleArgAppend.go
│ ├── sa4022/
│ │ ├── sa4022.go
│ │ ├── sa4022_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckAddressIsNil/
│ │ └── CheckAddressIsNil.go
│ ├── sa4023/
│ │ ├── sa4023.go
│ │ ├── sa4023_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ ├── CheckTypedNilInterface/
│ │ │ │ ├── CheckTypedNilInterface.go
│ │ │ │ └── real.go
│ │ │ ├── i26000/
│ │ │ │ └── 26000.go
│ │ │ ├── i27815/
│ │ │ │ └── 27815.go
│ │ │ ├── i28241/
│ │ │ │ └── 28241.go
│ │ │ ├── i31873/
│ │ │ │ └── 31873.go
│ │ │ ├── i33965/
│ │ │ │ └── 33965.go
│ │ │ ├── i33994/
│ │ │ │ └── 33994.go
│ │ │ └── i35217/
│ │ │ └── 35217.go
│ │ ├── go1.18/
│ │ │ └── CheckTypedNilInterface/
│ │ │ └── generics.go
│ │ └── go1.9/
│ │ └── CheckTypedNilInterface/
│ │ └── CheckTypedNilInterface.go
│ ├── sa4024/
│ │ ├── sa4024.go
│ │ ├── sa4024_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckBuiltinZeroComparison/
│ │ └── CheckBuiltinZeroComparison.go
│ ├── sa4025/
│ │ ├── sa4025.go
│ │ ├── sa4025_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIntegerDivisionEqualsZero/
│ │ └── CheckIntegerDivisionEqualsZero.go
│ ├── sa4026/
│ │ ├── sa4026.go
│ │ ├── sa4026_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNegativeZeroFloat/
│ │ ├── CheckNegativeZeroFloat.go
│ │ └── CheckNegativeZeroFloat.go.golden
│ ├── sa4027/
│ │ ├── sa4027.go
│ │ ├── sa4027_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckIneffectiveURLQueryModification/
│ │ └── CheckIneffectiveURLQueryModification.go
│ ├── sa4028/
│ │ ├── sa4028.go
│ │ ├── sa4028_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckModuloOne/
│ │ └── CheckModuloOne.go
│ ├── sa4029/
│ │ ├── sa4029.go
│ │ ├── sa4029_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckIneffectiveSort/
│ │ │ ├── CheckIneffectiveSort.go
│ │ │ └── CheckIneffectiveSort.go.golden
│ │ └── go1.9/
│ │ └── CheckIneffectiveSort/
│ │ ├── CheckIneffectiveSort.go
│ │ └── CheckIneffectiveSort.go.golden
│ ├── sa4030/
│ │ ├── sa4030.go
│ │ ├── sa4030_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckIneffectiveRandInt/
│ │ │ └── CheckIneffectiveRandInt.go
│ │ └── go1.22/
│ │ └── CheckIneffectiveRandInt/
│ │ └── CheckIneffectiveRandInt.go
│ ├── sa4031/
│ │ ├── sa4031.go
│ │ ├── sa4031_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckAllocationNilCheck/
│ │ └── CheckAllocationNilCheck.go
│ ├── sa4032/
│ │ ├── sa4032.go
│ │ ├── sa4032_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckGOOSComparison/
│ │ ├── complex.go
│ │ ├── f_linux.go
│ │ ├── f_unix.go
│ │ ├── f_windows.go
│ │ ├── other.go
│ │ ├── tlinux.go
│ │ ├── tunix.go
│ │ ├── twindows.go
│ │ └── unknown.go
│ ├── sa5000/
│ │ ├── sa5000.go
│ │ ├── sa5000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNilMaps/
│ │ └── CheckNilMaps.go
│ ├── sa5001/
│ │ ├── sa5001.go
│ │ ├── sa5001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckEarlyDefer/
│ │ └── CheckEarlyDefer.go
│ ├── sa5002/
│ │ ├── sa5002.go
│ │ ├── sa5002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckInfiniteEmptyLoop/
│ │ └── CheckInfiniteEmptyLoop.go
│ ├── sa5003/
│ │ ├── sa5003.go
│ │ ├── sa5003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDeferInInfiniteLoop/
│ │ └── CheckDeferInInfiniteLoop.go
│ ├── sa5004/
│ │ ├── sa5004.go
│ │ ├── sa5004_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckLoopEmptyDefault/
│ │ ├── CheckLoopEmptyDefault.go
│ │ └── CheckLoopEmptyDefault.go.golden
│ ├── sa5005/
│ │ ├── sa5005.go
│ │ ├── sa5005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckCyclicFinalizer/
│ │ └── CheckCyclicFinalizer.go
│ ├── sa5007/
│ │ ├── sa5007.go
│ │ ├── sa5007_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckInfiniteRecursion/
│ │ └── CheckInfiniteRecursion.go
│ ├── sa5008/
│ │ ├── jsonv2.go
│ │ ├── sa5008.go
│ │ ├── sa5008_test.go
│ │ ├── structtag.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ ├── CheckStructTags/
│ │ │ │ └── CheckStructTags.go
│ │ │ ├── CheckStructTags2/
│ │ │ │ └── CheckStructTags2.go
│ │ │ └── vendor/
│ │ │ └── github.com/
│ │ │ └── jessevdk/
│ │ │ └── go-flags/
│ │ │ └── pkg.go
│ │ └── go1.18/
│ │ └── CheckStructTags/
│ │ └── generics.go
│ ├── sa5009/
│ │ ├── sa5009.go
│ │ ├── sa5009_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckPrintf/
│ │ └── CheckPrintf.go
│ ├── sa5010/
│ │ ├── sa5010.go
│ │ ├── sa5010_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckImpossibleTypeAssertion/
│ │ │ └── CheckImpossibleTypeAssertion.go
│ │ └── go1.18/
│ │ └── CheckImpossibleTypeAssertion/
│ │ └── CheckImpossibleTypeAssertion.go
│ ├── sa5011/
│ │ ├── sa5011.go
│ │ ├── sa5011_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckMaybeNil/
│ │ │ └── CheckMaybeNil.go
│ │ └── go1.18/
│ │ └── CheckMaybeNil/
│ │ └── generics.go
│ ├── sa5012/
│ │ ├── sa5012.go
│ │ ├── sa5012_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckEvenSliceLength/
│ │ └── CheckEvenSliceLength.go
│ ├── sa6000/
│ │ ├── sa6000.go
│ │ ├── sa6000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckRegexpMatchLoop/
│ │ └── CheckRegexpMatchLoop.go
│ ├── sa6001/
│ │ ├── sa6001.go
│ │ ├── sa6001_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckMapBytesKey/
│ │ │ └── key.go
│ │ └── go1.18/
│ │ └── CheckMapBytesKey/
│ │ └── key_generics.go
│ ├── sa6002/
│ │ ├── sa6002.go
│ │ ├── sa6002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckSyncPoolValue/
│ │ └── CheckSyncPoolValue.go
│ ├── sa6003/
│ │ ├── sa6003.go
│ │ ├── sa6003_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckRangeStringRunes/
│ │ │ └── CheckRangeStringRunes.go
│ │ └── go1.18/
│ │ └── CheckRangeStringRunes/
│ │ └── generics.go
│ ├── sa6005/
│ │ ├── sa6005.go
│ │ ├── sa6005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckToLowerToUpperComparison/
│ │ ├── CheckToLowerToUpperComparison.go
│ │ └── CheckToLowerToUpperComparison.go.golden
│ ├── sa6006/
│ │ ├── sa6006.go
│ │ ├── sa6006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckByteSliceInIOWriteString/
│ │ └── CheckByteSliceInIOWriteString.go
│ ├── sa9001/
│ │ ├── sa9001.go
│ │ ├── sa9001_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckDubiousDeferInChannelRangeLoop/
│ │ │ └── CheckDubiousDeferInChannelRangeLoop.go
│ │ └── go1.18/
│ │ └── CheckDubiousDeferInChannelRangeLoop/
│ │ └── generics.go
│ ├── sa9002/
│ │ ├── sa9002.go
│ │ ├── sa9002_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNonOctalFileMode/
│ │ ├── CheckNonOctalFileMode.go
│ │ └── CheckNonOctalFileMode.go.golden
│ ├── sa9003/
│ │ ├── sa9003.go
│ │ ├── sa9003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckEmptyBranch/
│ │ ├── CheckEmptyBranch.go
│ │ ├── CheckEmptyBranch_generated.go
│ │ └── CheckEmptyBranch_test.go
│ ├── sa9004/
│ │ ├── sa9004.go
│ │ ├── sa9004_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckMissingEnumTypesInDeclaration/
│ │ ├── CheckMissingEnumTypesInDeclaration.go
│ │ └── CheckMissingEnumTypesInDeclaration.go.golden
│ ├── sa9005/
│ │ ├── sa9005.go
│ │ ├── sa9005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckNoopMarshal/
│ │ └── CheckNoopMarshal.go
│ ├── sa9006/
│ │ ├── sa9006.go
│ │ ├── sa9006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckStaticBitShift/
│ │ └── CheckStaticBitShift.go
│ ├── sa9007/
│ │ ├── sa9007.go
│ │ ├── sa9007_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckBadRemoveAll/
│ │ └── CheckBadRemoveAll.go
│ ├── sa9008/
│ │ ├── sa9008.go
│ │ ├── sa9008_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTypeAssertionShadowingElse/
│ │ └── CheckTypeAssertionShadowingElse.go
│ ├── sa9009/
│ │ ├── sa9009.go
│ │ ├── sa9009_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── Directives/
│ │ └── pkg.go
│ └── sa9010/
│ ├── sa9010.go
│ ├── sa9010_test.go
│ └── testdata/
│ ├── go1.0/
│ │ └── example.com/
│ │ └── deferr/
│ │ └── deferr.go
│ └── go1.18/
│ └── deferr/
│ └── deferr.go
├── staticcheck.conf
├── structlayout/
│ └── layout.go
├── stylecheck/
│ ├── analysis.go
│ ├── doc.go
│ ├── st1000/
│ │ ├── st1000.go
│ │ ├── st1000_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ ├── CheckPackageComment-1/
│ │ │ └── CheckPackageComment-1.go
│ │ ├── CheckPackageComment-2/
│ │ │ └── CheckPackageComment-2.go
│ │ ├── CheckPackageComment-3/
│ │ │ └── CheckPackageComment-3.go
│ │ ├── CheckPackageComment-4/
│ │ │ └── CheckPackageComment-4.go
│ │ ├── CheckPackageComment-5/
│ │ │ └── CheckPackageComment-5.go
│ │ └── CheckPackageComment-6/
│ │ └── CheckPackageComment-6.go
│ ├── st1001/
│ │ ├── st1001.go
│ │ ├── st1001_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDotImports/
│ │ ├── CheckDotImports.go
│ │ └── CheckDotImports_test.go
│ ├── st1003/
│ │ ├── st1003.go
│ │ ├── st1003_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ ├── CheckNames/
│ │ │ └── CheckNames.go
│ │ └── CheckNames_generated/
│ │ └── CheckNames_generated.go
│ ├── st1005/
│ │ ├── st1005.go
│ │ ├── st1005_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckErrorStrings/
│ │ └── CheckErrorStrings.go
│ ├── st1006/
│ │ ├── st1006.go
│ │ ├── st1006_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckReceiverNames/
│ │ └── CheckReceiverNames.go
│ ├── st1008/
│ │ ├── st1008.go
│ │ ├── st1008_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckErrorReturn/
│ │ │ └── CheckErrorReturn.go
│ │ └── go1.9/
│ │ └── CheckErrorReturn/
│ │ └── CheckErrorReturn.go
│ ├── st1011/
│ │ ├── st1011.go
│ │ ├── st1011_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckTimeNames/
│ │ └── CheckTimeNames.go
│ ├── st1012/
│ │ ├── st1012.go
│ │ ├── st1012_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckErrorVarNames/
│ │ └── CheckErrorVarNames.go
│ ├── st1013/
│ │ ├── st1013.go
│ │ ├── st1013_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckHTTPStatusCodes/
│ │ ├── CheckHTTPStatusCodes.go
│ │ └── CheckHTTPStatusCodes.go.golden
│ ├── st1015/
│ │ ├── st1015.go
│ │ ├── st1015_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDefaultCaseOrder/
│ │ └── CheckDefaultCaseOrder.go
│ ├── st1016/
│ │ ├── st1016.go
│ │ ├── st1016_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckReceiverNamesIdentical/
│ │ ├── CheckReceiverNames.go
│ │ └── generated.go
│ ├── st1017/
│ │ ├── st1017.go
│ │ ├── st1017_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckYodaConditions/
│ │ ├── CheckYodaConditions.go
│ │ └── CheckYodaConditions.go.golden
│ ├── st1018/
│ │ ├── st1018.go
│ │ ├── st1018_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckInvisibleCharacters/
│ │ ├── CheckInvisibleCharacters.go
│ │ └── CheckInvisibleCharacters.go.golden
│ ├── st1019/
│ │ ├── st1019.go
│ │ ├── st1019_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckDuplicatedImports/
│ │ └── CheckDuplicatedImports.go
│ ├── st1020/
│ │ ├── st1020.go
│ │ ├── st1020_test.go
│ │ └── testdata/
│ │ ├── go1.0/
│ │ │ └── CheckExportedFunctionDocs/
│ │ │ ├── CheckExportedFunctionDocs.go
│ │ │ └── foo_test.go
│ │ └── go1.18/
│ │ └── CheckExportedFunctionDocs/
│ │ └── generics.go
│ ├── st1021/
│ │ ├── st1021.go
│ │ ├── st1021_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckExportedTypeDocs/
│ │ └── CheckExportedTypeDocs.go
│ ├── st1022/
│ │ ├── st1022.go
│ │ ├── st1022_test.go
│ │ └── testdata/
│ │ └── go1.0/
│ │ └── CheckExportedVarDocs/
│ │ └── CheckExportedVarDocs.go
│ └── st1023/
│ ├── st1023.go
│ ├── st1023_test.go
│ └── testdata/
│ └── go1.0/
│ ├── CheckRedundantTypeInDeclaration/
│ │ ├── CheckRedundantTypeInDeclaration.go
│ │ └── CheckRedundantTypeInDeclaration.go.golden
│ └── CheckRedundantTypeInDeclaration_syscall/
│ └── CheckRedundantTypeInDeclaration_syscall.go
├── unused/
│ ├── implements.go
│ ├── runtime.go
│ ├── serialize.go
│ ├── testdata/
│ │ └── src/
│ │ └── example.com/
│ │ ├── alias/
│ │ │ └── alias.go
│ │ ├── anonymous/
│ │ │ └── anonymous.go
│ │ ├── blank/
│ │ │ └── blank.go
│ │ ├── blank-function-parameter/
│ │ │ └── blank.go
│ │ ├── cgo/
│ │ │ └── cgo.go
│ │ ├── constexpr/
│ │ │ └── constexpr.go
│ │ ├── consts/
│ │ │ └── consts.go
│ │ ├── conversion/
│ │ │ └── conversion.go
│ │ ├── cyclic/
│ │ │ └── cyclic.go
│ │ ├── defer/
│ │ │ └── defer.go
│ │ ├── elem/
│ │ │ └── elem.go
│ │ ├── embedded_call/
│ │ │ └── embedded_call.go
│ │ ├── embedding/
│ │ │ └── embedding.go
│ │ ├── embedding-alias/
│ │ │ └── embedding-alias.go
│ │ ├── embedding2/
│ │ │ └── embedding2.go
│ │ ├── exported_fields/
│ │ │ └── exported_fields.go
│ │ ├── exported_fields_main/
│ │ │ └── exported_fields_main.go
│ │ ├── exported_method_test/
│ │ │ ├── exported_method.go
│ │ │ └── exported_method_test.go
│ │ ├── fields/
│ │ │ ├── fields.go
│ │ │ └── fields_go123.go
│ │ ├── functions/
│ │ │ └── functions.go
│ │ ├── generated/
│ │ │ ├── generated.go
│ │ │ └── normal.go
│ │ ├── generic-interfaces/
│ │ │ └── generic-interfaces.go
│ │ ├── ignored/
│ │ │ ├── ignored.go
│ │ │ ├── ignored2.go
│ │ │ ├── ignored3.go
│ │ │ └── ignored4.go
│ │ ├── implicit-conversion/
│ │ │ └── implicit-conversion.go
│ │ ├── increment/
│ │ │ ├── increment.go
│ │ │ └── increment_test.go
│ │ ├── index-write/
│ │ │ └── write.go
│ │ ├── initializers/
│ │ │ └── initializers.go
│ │ ├── instantiated-functions/
│ │ │ └── instantiated-functions.go
│ │ ├── interfaces/
│ │ │ └── interfaces.go
│ │ ├── interfaces2/
│ │ │ └── interfaces.go
│ │ ├── issue1289/
│ │ │ └── issue1289.go
│ │ ├── linkname/
│ │ │ └── linkname.go
│ │ ├── local-type-param-sink/
│ │ │ └── typeparam.go
│ │ ├── local-type-param-source/
│ │ │ └── typeparam.go
│ │ ├── main/
│ │ │ └── main.go
│ │ ├── mapslice/
│ │ │ └── mapslice.go
│ │ ├── methods/
│ │ │ └── methods.go
│ │ ├── named/
│ │ │ └── named.go
│ │ ├── nested/
│ │ │ └── nested.go
│ │ ├── nocopy/
│ │ │ └── nocopy.go
│ │ ├── nocopy-main/
│ │ │ ├── nocopy-main.go
│ │ │ └── stub.go
│ │ ├── parens/
│ │ │ └── parens.go
│ │ ├── pointer-type-embedding/
│ │ │ └── pointer-type-embedding.go
│ │ ├── pointers/
│ │ │ └── pointers.go
│ │ ├── quiet/
│ │ │ └── quiet.go
│ │ ├── selectors/
│ │ │ └── selectors.go
│ │ ├── skipped-test/
│ │ │ └── skipped_test.go
│ │ ├── switch_interface/
│ │ │ └── switch_interface.go
│ │ ├── tests/
│ │ │ ├── tests.go
│ │ │ └── tests_test.go
│ │ ├── tests-main/
│ │ │ ├── main.go
│ │ │ └── main_test.go
│ │ ├── type-dedup/
│ │ │ └── dedup.go
│ │ ├── type-dedup2/
│ │ │ └── dedup.go
│ │ ├── type-dedup3/
│ │ │ └── dedup.go
│ │ ├── typeparams/
│ │ │ ├── typeparams.go
│ │ │ └── typeparams_17.go
│ │ ├── types/
│ │ │ └── types.go
│ │ ├── unsafe-recursive/
│ │ │ └── conversion.go
│ │ ├── unused-argument/
│ │ │ └── unused-argument.go
│ │ ├── unused_type/
│ │ │ └── unused_type.go
│ │ └── variables/
│ │ ├── variables.go
│ │ └── vartype.go
│ ├── unused.go
│ └── unused_test.go
└── website/
├── archetypes/
│ └── default.md
├── assets/
│ ├── js/
│ │ └── base.js
│ └── scss/
│ ├── _styles_project.scss
│ └── _variables_project.scss
├── build.sh
├── cmd/
│ ├── generate_checks/
│ │ └── generate_checks.go
│ └── generate_config/
│ └── generate_config.go
├── config.toml
├── content/
│ ├── _index.md
│ ├── changes/
│ │ ├── 2017.2.md
│ │ ├── 2019.1.md
│ │ ├── 2019.2.md
│ │ ├── 2020.1.md
│ │ ├── 2020.2.md
│ │ ├── 2021.1.md
│ │ ├── 2022.1.md
│ │ ├── 2023.1.md
│ │ ├── 2024.1.md
│ │ ├── 2025.1.md
│ │ ├── 2026.1.md
│ │ └── _index.md
│ ├── contact.md
│ ├── docs/
│ │ ├── _index.md
│ │ ├── changes.md
│ │ ├── checks.html
│ │ ├── configuration/
│ │ │ ├── _index.md
│ │ │ └── options.md
│ │ ├── faq.md
│ │ ├── getting-started.md
│ │ └── running-staticcheck/
│ │ ├── _index.md
│ │ ├── ci/
│ │ │ ├── _index.md
│ │ │ └── github-actions/
│ │ │ └── index.md
│ │ ├── cli/
│ │ │ ├── _index.md
│ │ │ ├── build-tags/
│ │ │ │ └── index.md
│ │ │ └── formatters.md
│ │ └── editors.md
│ └── sponsors.md
├── go.mod
├── go.sum
├── layouts/
│ ├── _internal/
│ │ └── twitter_cards.html
│ ├── changes/
│ │ └── list.rss.xml
│ ├── index.redir
│ ├── partials/
│ │ ├── breadcrumb.html
│ │ ├── footer.html
│ │ ├── hooks/
│ │ │ └── head-end.html
│ │ └── navbar.html
│ └── shortcodes/
│ ├── check.html
│ ├── commit.html
│ ├── content.html
│ ├── details.html
│ ├── faq/
│ │ ├── list.html
│ │ └── question.md
│ ├── issue.html
│ ├── issueref.html
│ └── option.html
├── package.json
├── shell.nix
├── static/
│ └── _headers
└── website.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.golden -text
*.svg binary
**/testdata/** -text
================================================
FILE: .github/FUNDING.yml
================================================
patreon: dominikh
github: dominikh
================================================
FILE: .github/ISSUE_TEMPLATE/1_false_positive.md
================================================
---
name: 💢 False positive in Staticcheck
about: Your code is fine but Staticcheck complains about it, anyway.
labels: false-positive, needs-triage
title: ""
---
================================================
FILE: .github/ISSUE_TEMPLATE/2_false_negative.md
================================================
---
name: 🦆 False negative in Staticcheck
about: Your code is wrong but Staticcheck doesn't complain about it.
labels: false-negative, needs-triage
title: ""
---
================================================
FILE: .github/ISSUE_TEMPLATE/3_bug.md
================================================
---
name: 🐞 General bugs with Staticcheck
about: Something in Staticcheck isn't working as it should.
labels: bug, needs-triage
title: ""
---
================================================
FILE: .github/ISSUE_TEMPLATE/4_other.md
================================================
---
name: 🛠 Other
about: Ideas, feature requests, and all other issues not fitting into another category.
labels: needs-triage
title: ""
---
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/workflows/ci.yml
================================================
name: "CI"
on: ["push", "pull_request"]
jobs:
ci:
name: "Run CI"
strategy:
fail-fast: false
matrix:
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
go: ["1.25", "1.26"]
godebug: ["gotypesalias=0", "gotypesalias=1"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- run: "go test ./..."
env:
GODEBUG: ${{ matrix.godebug }}
- run: "go vet ./..."
- uses: dominikh/staticcheck-action@v1
with:
version: "2026.1"
min-go-version: "module"
install-go: false
cache-key: ${{ matrix.go }}
output-format: binary
output-file: "./staticcheck.bin"
- uses: actions/upload-artifact@v4
with:
name: "staticcheck-${{ github.sha }}-${{ matrix.go }}-${{ matrix.os }}-${{ matrix.godebug }}.bin"
path: "./staticcheck.bin"
retention-days: 1
if-no-files-found: warn
output:
name: "Output Staticcheck findings"
needs: ci
runs-on: "ubuntu-latest"
steps:
- uses: actions/setup-go@v6
with:
go-version: "stable"
# this downloads all artifacts of the current workflow into the current working directory, creating one directory per artifact
- uses: actions/download-artifact@v4
- id: glob
run: |
# We replace newlines with %0A, which GitHub apparently magically turns back into newlines
out=$(ls -1 ./staticcheck-*.bin/*.bin)
echo "::set-output name=files::${out//$'\n'/%0A}"
- uses: dominikh/staticcheck-action@v1
with:
install-go: false
merge-files: ${{ steps.glob.outputs.files }}
================================================
FILE: .gitignore
================================================
/cmd/keyify/keyify
/cmd/staticcheck/staticcheck
/cmd/structlayout-optimize/structlayout-optimize
/cmd/structlayout-pretty/structlayout-pretty
/cmd/structlayout/structlayout
/dist/20??.?.?/
/dist/20??.?/
/internal/cmd/irdump/irdump
/website/.hugo_build.lock
/website/public
/website/resources
/website/assets/img/sponsors
/website/data/sponsors.toml
/website/data/copyrights.toml
/website/data/checks.json
/website/content/docs/configuration/default_config/index.md
================================================
FILE: .gitmodules
================================================
[submodule "website/themes/docsy"]
path = website/themes/docsy
url = https://github.com/google/docsy
================================================
FILE: LICENSE
================================================
Copyright (c) 2016 Dominik Honnef
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: LICENSE-THIRD-PARTY
================================================
Staticcheck and its related tools make use of third party projects,
either by reusing their code, or by statically linking them into
resulting binaries. These projects are:
* The Go Programming Language - https://golang.org/
golang.org/x/mod - https://github.com/golang/mod
golang.org/x/tools - https://github.com/golang/tools
golang.org/x/sys - https://github.com/golang/sys
golang.org/x/xerrors - https://github.com/golang/xerrors
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* github.com/BurntSushi/toml - https://github.com/BurntSushi/toml
The MIT License (MIT)
Copyright (c) 2013 TOML authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
* gogrep - https://github.com/mvdan/gogrep
Copyright (c) 2017, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* gosmith - https://github.com/dvyukov/gosmith
Copyright (c) 2014 Dmitry Vyukov. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of Dmitry Vyukov may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
The advanced Go linter
Staticcheck is a state of the art linter for the [Go programming
language](https://go.dev/). Using static analysis, it finds bugs and performance issues,
offers simplifications, and enforces style rules.
**Financial support by [private and corporate sponsors](https://staticcheck.dev/sponsors) guarantees the tool's continued development.
Please [become a sponsor](https://github.com/users/dominikh/sponsorship) if you or your company rely on Staticcheck.**
## Documentation
You can find extensive documentation on Staticcheck on [its website](https://staticcheck.dev/docs/).
## Installation
### Releases
It is recommended that you run released versions of the tools.
These releases can be found as git tags (e.g. `2022.1`).
The easiest way of installing a release is by using `go install`, for example `go install honnef.co/go/tools/cmd/staticcheck@2022.1`.
Alternatively, we also offer [prebuilt binaries](https://github.com/dominikh/go-tools/releases).
You can find more information about installation and releases in the [documentation](https://staticcheck.dev/docs/getting-started/).
### Master
You can also run the master branch instead of a release. Note that
while the master branch is usually stable, it may still contain new
checks or backwards incompatible changes that break your build. By
using the master branch you agree to become a beta tester.
## Tools
All of the following tools can be found in the cmd/ directory. Each
tool is accompanied by its own README, describing it in more detail.
| Tool | Description |
|----------------------------------------------------|-------------------------------------------------------------------------|
| [staticcheck](cmd/staticcheck/) | Go static analysis, detecting bugs, performance issues, and much more. |
| [structlayout](cmd/structlayout/) | Displays the layout (field sizes and padding) of structs. |
| [structlayout-optimize](cmd/structlayout-optimize) | Reorders struct fields to minimize the amount of padding. |
| [structlayout-pretty](cmd/structlayout-pretty) | Formats the output of structlayout with ASCII art. |
## Libraries
In addition to the aforementioned tools, this repository contains the
libraries necessary to implement these tools.
Unless otherwise noted, none of these libraries have stable APIs.
Their main purpose is to aid the implementation of the tools.
You'll have to expect semiregular backwards-incompatible changes if you decide to use these libraries.
## System requirements
Staticcheck can be compiled and run with the latest release of Go. It can analyze code targeting any version of Go upto
the latest release.
================================================
FILE: _benchmarks/bench.sh
================================================
#!/usr/bin/env bash
set -e
declare -A PKGS=(
["strconv"]="strconv"
["net/http"]="net/http"
["image/color"]="image/color"
["std"]="std"
["k8s"]="k8s.io/kubernetes/pkg/..."
)
MIN_CORES=32
MAX_CORES=32
INCR_CORES=2
MIN_GOGC=100
MAX_GOGC=100
SAMPLES=10
WIPE_CACHE=1
FORMAT=bench
BIN=$(realpath ./silent-staticcheck.sh)
runBenchmark() {
local pkg="$1"
local label="$2"
local gc="$3"
local cores="$4"
local wipe="$5"
if [ $wipe -ne 0 ]; then
rm -rf ~/.cache/staticcheck
fi
local out=$(GOGC=$gc GOMAXPROCS=$cores env time -f "%e %M" $BIN $pkg 2>&1)
local t=$(echo "$out" | cut -f1 -d" ")
local m=$(echo "$out" | cut -f2 -d" ")
local ns=$(printf "%s 1000000000 * p" $t | dc)
local b=$((m * 1024))
case $FORMAT in
bench)
printf "BenchmarkStaticcheck-%s-GOGC%d-wiped%d-%d 1 %.0f ns/op %.0f B/op\n" "$label" "$gc" "$wipe" "$cores" "$ns" "$b"
;;
csv)
printf "%s,%d,%d,%d,%.0f,%.0f\n" "$label" "$gc" "$cores" "$wipe" "$ns" "$b"
;;
esac
}
export GO111MODULE=off
if [ "$FORMAT" = "csv" ]; then
printf "packages,gogc,gomaxprocs,wipe-cache,time,memory\n"
fi
for label in "${!PKGS[@]}"; do
pkg=${PKGS[$label]}
for gc in $(seq $MIN_GOGC 10 $MAX_GOGC); do
for cores in $(seq $MIN_CORES $INCR_CORES $MAX_CORES); do
for i in $(seq 1 $SAMPLES); do
runBenchmark "$pkg" "$label" "$gc" "$cores" 1
runBenchmark "$pkg" "$label" "$gc" "$cores" 0
done
done
done
done
================================================
FILE: _benchmarks/silent-staticcheck.sh
================================================
#!/usr/bin/env sh
/home/dominikh/prj/src/honnef.co/go/tools/cmd/staticcheck/staticcheck -checks "all" -fail "" $1 &>/dev/null
exit 0
================================================
FILE: add-check.go
================================================
//go:build ignore
package main
import (
"bytes"
"go/format"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"text/template"
)
var tmpl = `
package {{.lname}}
import (
"honnef.co/go/tools/analysis/lint"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "{{.name}}",
Run: run,
Requires: []*analysis.Analyzer{},
},
Doc: &lint.RawDocumentation{
Title: "",
Text: {{.emptyRaw}},
{{- if .quickfix }}
Before: {{.emptyRaw}},
After: {{.emptyRaw}},
{{- end }}
Since: "Unreleased",
Severity: lint.SeverityWarning,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
return nil, nil
}
`
func main() {
log.SetFlags(0)
var t template.Template
if _, err := t.Parse(tmpl); err != nil {
log.Fatalln("couldn't parse template:", err)
}
if len(os.Args) != 2 {
log.Fatalf("Usage: %s ", os.Args[0])
}
name := os.Args[1]
checkRe := regexp.MustCompile(`^([A-Za-z]+)\d{4}$`)
parts := checkRe.FindStringSubmatch(name)
if parts == nil {
log.Fatalf("invalid check name %q", name)
}
var catDir string
prefix := strings.ToUpper(parts[1])
switch prefix {
case "SA":
catDir = "staticcheck"
case "S":
catDir = "simple"
case "ST":
catDir = "stylecheck"
case "QF":
catDir = "quickfix"
default:
log.Fatalf("unknown check prefix %q", prefix)
}
lname := strings.ToLower(name)
dir := filepath.Join(catDir, lname)
dst := filepath.Join(dir, lname+".go")
mkdirp(dir)
buf := bytes.NewBuffer(nil)
vars := map[string]any{
"name": name,
"lname": lname,
"emptyRaw": "``",
"quickfix": prefix == "QF",
}
if err := t.Execute(buf, vars); err != nil {
log.Fatalf("couldn't generate %s: %s", dst, err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("couldn't gofmt %s: %s", dst, err)
}
writeFile(dst, b)
testdata := filepath.Join(dir, "testdata", "src", "example.com", "pkg")
mkdirp(testdata)
writeFile(filepath.Join(testdata, "pkg.go"), []byte("package pkg\n"))
out, err := exec.Command("go", "generate", "./...").CombinedOutput()
if err != nil {
log.Printf("could not run 'go generate ./...': %s", err)
log.Println("Output:")
log.Fatalln(string(out))
}
flags := []string{
"add",
"--intent-to-add",
"--verbose",
filepath.Join(dir, lname+"_test.go"),
filepath.Join(testdata, "pkg.go"),
dst,
}
cmd := exec.Command("git", flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalln("could not run 'git add':", err)
}
}
func writeFile(path string, data []byte) {
if err := os.WriteFile(path, data, 0677); err != nil {
log.Fatalf("couldn't write %s: %s", path, err)
}
}
func mkdirp(path string) {
if err := os.MkdirAll(path, 0777); err != nil {
log.Fatalf("couldn't create directory %s: %s", path, err)
}
}
================================================
FILE: analysis/callcheck/callcheck.go
================================================
// Package callcheck provides a framework for validating arguments in function calls.
package callcheck
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
)
type Call struct {
Pass *analysis.Pass
Instr ir.CallInstruction
Args []*Argument
Parent *ir.Function
invalids []string
}
func (c *Call) Invalid(msg string) {
c.invalids = append(c.invalids, msg)
}
type Argument struct {
Value Value
invalids []string
}
type Value struct {
Value ir.Value
}
func (arg *Argument) Invalid(msg string) {
arg.invalids = append(arg.invalids, msg)
}
type Check func(call *Call)
func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (any, error) {
return func(pass *analysis.Pass) (any, error) {
return checkCalls(pass, rules)
}
}
func checkCalls(pass *analysis.Pass, rules map[string]Check) (any, error) {
cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
obj, ok := callee.Object().(*types.Func)
if !ok {
return
}
r, ok := rules[typeutil.FuncName(obj)]
if !ok {
return
}
var args []*Argument
irargs := site.Common().Args
if callee.Signature.Recv() != nil {
irargs = irargs[1:]
}
for _, arg := range irargs {
if iarg, ok := arg.(*ir.MakeInterface); ok {
arg = iarg.X
}
args = append(args, &Argument{Value: Value{arg}})
}
call := &Call{
Pass: pass,
Instr: site,
Args: args,
Parent: site.Parent(),
}
r(call)
var astcall *ast.CallExpr
switch source := site.Source().(type) {
case *ast.CallExpr:
astcall = source
case *ast.DeferStmt:
astcall = source.Call
case *ast.GoStmt:
astcall = source.Call
case nil:
// TODO(dh): I am not sure this can actually happen. If it
// can't, we should remove this case, and also stop
// checking for astcall == nil in the code that follows.
default:
panic(fmt.Sprintf("unhandled case %T", source))
}
for idx, arg := range call.Args {
for _, e := range arg.invalids {
if astcall != nil {
if idx < len(astcall.Args) {
report.Report(pass, astcall.Args[idx], e)
} else {
// this is an instance of fn1(fn2()) where fn2
// returns multiple values. Report the error
// at the next-best position that we have, the
// first argument. An example of a check that
// triggers this is checkEncodingBinaryRules.
report.Report(pass, astcall.Args[0], e)
}
} else {
report.Report(pass, site, e)
}
}
}
for _, e := range call.invalids {
report.Report(pass, call.Instr, e)
}
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
eachCall(fn, cb)
}
return nil, nil
}
func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ir.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
cb(fn, site, g)
}
}
}
}
}
func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const {
k := extractConst(v.Value)
if k == nil || k.Value == nil || k.Value.Kind() != kind {
return nil
}
return k
}
func ExtractConst(v Value) *ir.Const {
return extractConst(v.Value)
}
func extractConst(v ir.Value) *ir.Const {
v = irutil.Flatten(v)
switch v := v.(type) {
case *ir.Const:
return v
case *ir.MakeInterface:
return extractConst(v.X)
default:
return nil
}
}
================================================
FILE: analysis/code/code.go
================================================
// Package code answers structural and type questions about Go code.
package code
import (
"fmt"
"go/ast"
"go/build/constraint"
"go/constant"
"go/token"
"go/types"
"go/version"
"path/filepath"
"slices"
"strings"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/facts/purity"
"honnef.co/go/tools/analysis/facts/tokenfile"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
type Positioner interface {
Pos() token.Pos
}
func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
if !ok {
return false
}
elem := types.Unalias(typ.Elem())
if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
// Before Go 1.18, one could not directly convert from []T (where 'type T byte')
// to string. See also https://github.com/golang/go/issues/23536.
elem = elem.Underlying()
}
return types.Identical(elem, types.Typ[types.Byte])
}
func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
if !ok {
return false
}
return typeutil.IsTypeWithName(ptr.Elem(), name)
}
func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name)
}
func IsInTest(pass *analysis.Pass, node Positioner) bool {
// FIXME(dh): this doesn't work for global variables with
// initializers
f := pass.Fset.File(node.Pos())
return f != nil && strings.HasSuffix(f.Name(), "_test.go")
}
// IsMain reports whether the package being processed is a package
// main.
func IsMain(pass *analysis.Pass) bool {
return pass.Pkg.Name() == "main"
}
// IsMainLike reports whether the package being processed is a
// main-like package. A main-like package is a package that is
// package main, or that is intended to be used by a tool framework
// such as cobra to implement a command.
//
// Note that this function errs on the side of false positives; it may
// return true for packages that aren't main-like. IsMainLike is
// intended for analyses that wish to suppress diagnostics for
// main-like packages to avoid false positives.
func IsMainLike(pass *analysis.Pass) bool {
if pass.Pkg.Name() == "main" {
return true
}
for _, imp := range pass.Pkg.Imports() {
if imp.Path() == "github.com/spf13/cobra" {
return true
}
}
return false
}
func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
info := pass.TypesInfo
sel := info.Selections[expr]
if sel == nil {
switch x := expr.X.(type) {
case *ast.Ident:
pkg, ok := info.ObjectOf(x).(*types.PkgName)
if !ok {
return fmt.Sprintf("(%s).%s", info.TypeOf(x), expr.Sel.Name)
}
return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
case *ast.SelectorExpr:
return fmt.Sprintf("(%s).%s", SelectorName(pass, x), expr.Sel.Name)
default:
panic(fmt.Sprintf("unsupported selector: %v", expr))
}
}
if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
} else {
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
}
}
func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
return pass.TypesInfo.Types[expr].IsNil()
}
func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
return constant.BoolVal(val)
}
func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
// We explicitly don't support typed bools because more often than
// not, custom bool types are used as binary enums and the explicit
// comparison is desired. We err on the side of false negatives and
// treat aliases like other custom types.
ident, ok := expr.(*ast.Ident)
if !ok {
return false
}
obj := pass.TypesInfo.ObjectOf(ident)
c, ok := obj.(*types.Const)
if !ok {
return false
}
basic, ok := c.Type().(*types.Basic)
if !ok {
return false
}
if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
return false
}
return true
}
func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
tv := pass.TypesInfo.Types[expr]
if tv.Value == nil {
return 0, false
}
if tv.Value.Kind() != constant.Int {
return 0, false
}
return constant.Int64Val(tv.Value)
}
func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
val := pass.TypesInfo.Types[expr].Value
if val == nil {
return "", false
}
if val.Kind() != constant.String {
return "", false
}
return constant.StringVal(val), true
}
func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
fun := astutil.Unparen(call.Fun)
// Instantiating a function cannot return another generic function, so doing this once is enough
switch idx := fun.(type) {
case *ast.IndexExpr:
fun = idx.X
case *ast.IndexListExpr:
fun = idx.X
}
// (foo)[T] is not a valid instantiation, so no need to unparen again.
switch fun := fun.(type) {
case *ast.SelectorExpr:
fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
if !ok {
return ""
}
return typeutil.FuncName(fn)
case *ast.Ident:
obj := pass.TypesInfo.ObjectOf(fun)
switch obj := obj.(type) {
case *types.Func:
return typeutil.FuncName(obj)
case *types.Builtin:
return obj.Name()
default:
return ""
}
default:
return ""
}
}
func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
return CallName(pass, call) == name
}
func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
// See the comment in typeutil.FuncName for why this doesn't require special handling
// of aliases.
call, ok := node.(*ast.CallExpr)
if !ok {
return false
}
q := CallName(pass, call)
return slices.Contains(names, q)
}
func File(pass *analysis.Pass, node Positioner) *ast.File {
m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File)
return m[pass.Fset.File(node.Pos())]
}
// BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
// GOOS and GOARCH in file names.
func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
var expr constraint.Expr
for _, cmt := range f.Comments {
if len(cmt.List) == 0 {
continue
}
for _, el := range cmt.List {
if el.Pos() > f.Package {
break
}
if line := el.Text; strings.HasPrefix(line, "//go:build") {
var err error
expr, err = constraint.Parse(line)
if err != nil {
expr = nil
}
break
}
}
}
name := pass.Fset.PositionFor(f.Pos(), false).Filename
oexpr := constraintsFromName(name)
if oexpr != nil {
if expr == nil {
expr = oexpr
} else {
expr = &constraint.AndExpr{X: expr, Y: oexpr}
}
}
return expr, expr != nil
}
func constraintsFromName(name string) constraint.Expr {
name = filepath.Base(name)
name = strings.TrimSuffix(name, ".go")
name = strings.TrimSuffix(name, "_test")
var goos, goarch string
switch strings.Count(name, "_") {
case 0:
// No GOOS or GOARCH in the file name.
case 1:
_, c, _ := strings.Cut(name, "_")
if _, ok := knowledge.KnownGOOS[c]; ok {
goos = c
} else if _, ok := knowledge.KnownGOARCH[c]; ok {
goarch = c
}
default:
n := strings.LastIndex(name, "_")
if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
// The file name is *_stuff_GOOS.go
goos = name[n+1:]
} else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
// The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
goarch = name[n+1:]
_, c, _ := strings.Cut(name[:n], "_")
if _, ok := knowledge.KnownGOOS[c]; ok {
// The file name is *_GOOS_GOARCH.go
goos = c
}
} else {
// The file name could also be something like foo_windows_nonsense.go — and because nonsense
// isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
}
}
var expr constraint.Expr
if goos != "" {
expr = &constraint.TagExpr{Tag: goos}
}
if goarch != "" {
if expr == nil {
expr = &constraint.TagExpr{Tag: goarch}
} else {
expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
}
}
return expr
}
// IsGenerated reports whether pos is in a generated file. It ignores
// //line directives.
func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
_, ok := Generator(pass, pos)
return ok
}
// Generator returns the generator that generated the file containing
// pos. It ignores //line directives.
func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) {
file := pass.Fset.PositionFor(pos, false).Filename
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
g, ok := m[file]
return g, ok
}
// MayHaveSideEffects reports whether expr may have side effects. If
// the purity argument is nil, this function implements a purely
// syntactic check, meaning that any function call may have side
// effects, regardless of the called function's body. Otherwise,
// purity will be consulted to determine the purity of function calls.
func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool {
switch expr := expr.(type) {
case *ast.BadExpr:
return true
case *ast.Ellipsis:
return MayHaveSideEffects(pass, expr.Elt, purity)
case *ast.FuncLit:
// the literal itself cannot have side effects, only calling it
// might, which is handled by CallExpr.
return false
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
// types cannot have side effects
return false
case *ast.BasicLit:
return false
case *ast.BinaryExpr:
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
case *ast.CallExpr:
if purity == nil {
return true
}
switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
case *types.Func:
if _, ok := purity[obj]; !ok {
return true
}
case *types.Builtin:
switch obj.Name() {
case "len", "cap":
default:
return true
}
default:
return true
}
for _, arg := range expr.Args {
if MayHaveSideEffects(pass, arg, purity) {
return true
}
}
return false
case *ast.CompositeLit:
if MayHaveSideEffects(pass, expr.Type, purity) {
return true
}
for _, elt := range expr.Elts {
if MayHaveSideEffects(pass, elt, purity) {
return true
}
}
return false
case *ast.Ident:
return false
case *ast.IndexExpr:
return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
case *ast.IndexListExpr:
// In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
// being safe.
if MayHaveSideEffects(pass, expr.X, purity) {
return true
}
for _, idx := range expr.Indices {
if MayHaveSideEffects(pass, idx, purity) {
return true
}
}
return false
case *ast.KeyValueExpr:
return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
case *ast.SelectorExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.SliceExpr:
return MayHaveSideEffects(pass, expr.X, purity) ||
MayHaveSideEffects(pass, expr.Low, purity) ||
MayHaveSideEffects(pass, expr.High, purity) ||
MayHaveSideEffects(pass, expr.Max, purity)
case *ast.StarExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.TypeAssertExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case *ast.UnaryExpr:
if MayHaveSideEffects(pass, expr.X, purity) {
return true
}
return expr.Op == token.ARROW || expr.Op == token.AND
case *ast.ParenExpr:
return MayHaveSideEffects(pass, expr.X, purity)
case nil:
return false
default:
panic(fmt.Sprintf("internal error: unhandled type %T", expr))
}
}
// LanguageVersion returns the version of the Go language that node has access to. This
// might differ from the version of the Go standard library.
func LanguageVersion(pass *analysis.Pass, node Positioner) string {
// As of Go 1.21, two places can specify the minimum Go version:
// - 'go' directives in go.mod and go.work files
// - individual files by using '//go:build'
//
// Individual files can upgrade to a higher version than the module version. Individual files
// can also downgrade to a lower version, but only if the module version is at least Go 1.21.
//
// The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
// not type-check on versions that are too old, and thus never reach our analyzes. In practice,
// such ineffective downgrading will always be useless, as the compiler will not restrict the
// language features used, and doesn't ever rely on minimum versions to restrict the use of the
// standard library. However, for us, both choices (respecting or ignoring ineffective
// downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
// noisy positives.
//
// The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
// ast.File's version is populated by the parser, whereas types.Package's version is populated
// from the Go version specified in the types.Config, which is set by our package loader, based
// on the module information provided by go/packages, via 'go list -json'.
//
// As of Go 1.21, standard library packages do not present themselves as modules, and thus do
// not have a version set on their types.Package. In this case, we fall back to the version
// provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
// the Go version that Staticcheck was built with when no module information exists. In the
// future, the standard library will hopefully be a proper module (see
// https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
// of standard library packages will match that of the used Go version. At that point,
// Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
// code due to language changes.
//
// We also lack module information when building in GOPATH mode. In this case, the implied
// language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
// don't handle this yet, and it will not matter until Go 1.22.
//
// It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
// version at all is provided, which should preclude per-file downgrading. On the other hand,
// https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
// in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
// relevant language changes before Go 1.22 will lead to type-checking failures and never reach
// us.
//
// Per-file upgrading is permitted in GOPATH mode.
// If the file has its own Go version, we will return that. Otherwise, we default to
// the type checker's GoVersion, which is populated from either the Go module, or from
// our '-go' flag.
return pass.TypesInfo.FileVersions[File(pass, node)]
}
// StdlibVersion returns the version of the Go standard library that node can expect to
// have access to. This might differ from the language version for versions of Go older
// than 1.21.
func StdlibVersion(pass *analysis.Pass, node Positioner) string {
// The Go version as specified in go.mod or via the '-go' flag
n := pass.Pkg.GoVersion()
f := File(pass, node)
if f == nil {
panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
}
if nf := f.GoVersion; nf != "" {
if version.Compare(n, "go1.21") == -1 {
// Before Go 1.21, the Go version set in go.mod specified the maximum language
// version available to the module. It wasn't uncommon to set the version to
// Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
// to files tagged for 1.20, and supporting a lower version overall. As such,
// a file tagged lower than the module version couldn't expect to have access
// to the standard library of the version set in go.mod.
//
// At the same time, a file tagged higher than the module version, while not
// able to use newer language features, would still have been able to use a
// newer standard library.
//
// While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
// expectations have not.
return nf
} else {
// Go 1.21 and newer refuse to build modules that depend on versions newer
// than the used version of the Go toolchain. This means that in a 1.22 module
// with a file tagged as 1.17, the file can expect to have access to 1.22's
// standard library (but not to 1.22 language features). A file tagged with a
// version higher than the minimum version has access to the newer standard
// library (and language features.)
//
// Do note that strictly speaking we're conflating the Go version and the
// module version in our check. Nothing is stopping a user from using Go 1.17
// (which didn't implement the new rules for versions in go.mod) to build a Go
// 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22
// standard library. However, we believe that if a module requires 1.21 or
// newer, then the author clearly expects the new behavior, and doesn't care
// for the old one. Otherwise they would've specified an older version.
//
// In other words, the module version also specifies what it itself actually means, with
// >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
// the language.
if version.Compare(nf, n) == 1 {
return nf
}
}
}
return n
}
var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
m, ok := Match(pass, integerLiteralQ, node)
if !ok {
return types.TypeAndValue{}, false
}
return m.State["tv"].(types.TypeAndValue), true
}
func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
tv, ok := IntegerLiteral(pass, node)
if !ok {
return false
}
return constant.Compare(tv.Value, token.EQL, value)
}
// IsMethod reports whether expr is a method call of a named method with signature meth.
// If name is empty, it is not checked.
// For now, method expressions (Type.Method(recv, ..)) are not considered method calls.
func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool {
if name != "" && expr.Sel.Name != name {
return false
}
sel, ok := pass.TypesInfo.Selections[expr]
if !ok || sel.Kind() != types.MethodVal {
return false
}
return types.Identical(sel.Type(), meth)
}
func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
found := false
fn := func(node ast.Node) bool {
ident2, ok := node.(*ast.Ident)
if !ok {
return true
}
if ident == pass.TypesInfo.ObjectOf(ident2) {
found = true
return false
}
return true
}
ast.Inspect(expr, fn)
return found
}
================================================
FILE: analysis/code/code_test.go
================================================
package code
import "testing"
var constraintsFromNameTests = []struct {
in string
out string
}{
{"foo.go", ""},
{"foo_windows.go", "windows"},
{"foo_unix.go", ""},
{"foo_windows_amd64.go", "windows && amd64"},
{"foo_amd64.go", "amd64"},
{"foo_windows_nonsense.go", ""},
{"foo_nonsense_amd64.go", "amd64"},
{"foo_nonsense_windows.go", "windows"},
{"foo_nonsense_windows_amd64.go", "amd64"},
{"foo_windows_test.go", "windows"},
{"linux.go", ""},
{"linux_amd64.go", "amd64"},
{"amd64_linux.go", "linux"},
{"amd64.go", ""},
}
func TestConstraintsFromName(t *testing.T) {
for _, tc := range constraintsFromNameTests {
expr := constraintsFromName(tc.in)
var out string
if expr != nil {
out = expr.String()
}
if out != tc.out {
t.Errorf("constraintsFromName(%q) == %q, expected %q", tc.in, out, tc.out)
}
}
}
func FuzzConstraintsFromName(f *testing.F) {
for _, tc := range constraintsFromNameTests {
f.Add(tc.in)
}
f.Fuzz(func(t *testing.T, name string) {
constraintsFromName(name)
})
}
================================================
FILE: analysis/code/visit.go
================================================
package code
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/types"
"iter"
"slices"
typeindexanalyzer "honnef.co/go/tools/internal/analysisinternal/typeindex"
"honnef.co/go/tools/internal/typesinternal/typeindex"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var RequiredAnalyzers = []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer}
func Cursor(pass *analysis.Pass) inspector.Cursor {
return pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root()
}
func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)
}
func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
if push {
fn(n, stack)
}
return true
})
}
func Matches(pass *analysis.Pass, qs ...pattern.Pattern) iter.Seq2[ast.Node, *pattern.Matcher] {
return func(yield func(ast.Node, *pattern.Matcher) bool) {
for _, q := range qs {
if !CouldMatchAny(pass, q) {
continue
}
if len(q.RootCallSymbols) != 0 {
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for _, isym := range q.RootCallSymbols {
var obj types.Object
if isym.Type == "" {
obj = index.Object(isym.Path, isym.Ident)
} else {
obj = index.Selection(isym.Path, isym.Type, isym.Ident)
}
for c := range index.Calls(obj) {
node := c.Node()
if m, ok := Match(pass, q, node); ok {
if !yield(node, m) {
return
}
}
}
}
} else {
ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
fn := func(node ast.Node, push bool) bool {
if !push {
return true
}
if m, ok := Match(pass, q, node); ok {
return yield(node, m)
}
return true
}
ins.Nodes(q.EntryNodes, fn)
}
}
}
}
func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) {
// Note that we ignore q.Relevant – callers of Match usually use
// AST inspectors that already filter on nodes we're interested
// in.
m := &pattern.Matcher{TypesInfo: pass.TypesInfo}
ok := m.Match(q, node)
return m, ok
}
func CouldMatchAny(pass *analysis.Pass, qs ...pattern.Pattern) bool {
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
var do func(node pattern.Node) bool
do = func(node pattern.Node) bool {
switch node := node.(type) {
case pattern.Any:
return true
case pattern.Or:
return slices.ContainsFunc(node.Nodes, do)
case pattern.And:
for _, child := range node.Nodes {
if !do(child) {
return false
}
}
return true
case pattern.IndexSymbol:
if node.Type == "" {
return index.Object(node.Path, node.Ident) != nil
} else {
return index.Selection(node.Path, node.Type, node.Ident) != nil
}
default:
panic(fmt.Sprintf("internal error: unexpected type %T", node))
}
}
for _, q := range qs {
if do(q.SymbolsPattern) {
return true
}
}
return false
}
func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) {
m, ok := Match(pass, before, node)
if !ok {
return m, nil, false
}
r := pattern.NodeToAST(after.Root, m.State)
buf := &bytes.Buffer{}
format.Node(buf, pass.Fset, r)
edit := []analysis.TextEdit{{
Pos: node.Pos(),
End: node.End(),
NewText: buf.Bytes(),
}}
return m, edit, true
}
func EditMatch(pass *analysis.Pass, node ast.Node, m *pattern.Matcher, after pattern.Pattern) []analysis.TextEdit {
r := pattern.NodeToAST(after.Root, m.State)
buf := &bytes.Buffer{}
format.Node(buf, pass.Fset, r)
edit := []analysis.TextEdit{{
Pos: node.Pos(),
End: node.End(),
NewText: buf.Bytes(),
}}
return edit
}
================================================
FILE: analysis/dfa/dfa.el
================================================
(require 'cl-lib)
(defun format-state (prefix state ⊤ ⊥)
(cond ((string= state "⊥") ⊥)
((string= state "⊤") ⊤)
(t (format "%s%s" prefix state))))
(defun dh/orgtbl-to-dfa-binary-table (table params)
(let* ((table (--filter (not (equal 'hline it)) table))
(rows (1- (length table)))
(cols (1- (length (nth 0 table))))
(prefix (plist-get params :prefix))
(var (plist-get params :var))
(⊤ (plist-get params :⊤))
(⊥ (plist-get params :⊥)))
(concat
(if var (concat "var " var " = ") "")
(format
"dfa.BinaryTable(%s, map[[2]%s]%s{\n"
⊤ prefix prefix)
(mapconcat
(lambda (rowIdx)
(mapconcat
(lambda (colIdx)
(let* ((x (nth 0 (nth rowIdx table)))
(y (nth colIdx (nth 0 table)))
(z (nth colIdx (nth rowIdx table))))
(format "{%s, %s}: %s," (format-state prefix x ⊤ ⊥) (format-state prefix y ⊤ ⊥) (format-state prefix z ⊤ ⊥))))
(number-sequence 1 cols)
"\n"))
(number-sequence 1 rows)
"\n\n")
"\n})")))
================================================
FILE: analysis/dfa/dfa.go
================================================
// Package dfa provides types and functions for implementing data-flow analyses.
package dfa
import (
"cmp"
"fmt"
"log"
"math/bits"
"slices"
"strings"
"sync"
"golang.org/x/exp/constraints"
"honnef.co/go/tools/go/ir"
)
const debugging = false
func debugf(f string, args ...any) {
if debugging {
log.Printf(f, args...)
}
}
// Join defines the [∨] operation for a [join-semilattice]. It must implement a commutative and associative binary operation
// that returns the least upper bound of two states from S.
//
// Code that calls Join functions is expected to handle the [⊥ and ⊤ elements], as well as implement idempotency. That is,
// the following properties will be enforced:
//
// - x ∨ ⊥ = x
// - x ∨ ⊤ = ⊤
// - x ∨ x = x
//
// Simple table-based join functions can be created using [JoinTable].
//
// [∨]: https://en.wikipedia.org/wiki/Join_and_meet
// [join-semilattice]: https://en.wikipedia.org/wiki/Semilattice
// [⊥ and ⊤ elements]: https://en.wikipedia.org/wiki/Greatest_element_and_least_element#Top_and_bottom
type Join[S comparable] func(S, S) S
// Mapping maps a single [ir.Value] to an abstract state.
type Mapping[S comparable] struct {
Value ir.Value
State S
Decision Decision
}
// Decision describes how a mapping from an [ir.Value] to an abstract state came to be.
// Decisions are provided by transfer functions when they create mappings.
type Decision struct {
// The relevant values that the transfer function used to make the decision.
Inputs []ir.Value
// A human-readable description of the decision.
Description string
// Whether this is the source of an abstract state. For example, in a taint analysis, the call to a function that
// produces a tainted value would be the source of the taint state, and any instructions that operate on
// and propagate tainted values would not be sources.
Source bool
}
func (m Mapping[S]) String() string {
return fmt.Sprintf("%s = %v", m.Value.Name(), m.State)
}
// M is a helper for constructing instances of [Mapping].
func M[S comparable](v ir.Value, s S, d Decision) Mapping[S] {
return Mapping[S]{Value: v, State: s, Decision: d}
}
// Ms is a helper for constructing slices of mappings.
//
// Example:
//
// Ms(M(v1, d1, ...), M(v2, d2, ...))
func Ms[S comparable](ms ...Mapping[S]) []Mapping[S] {
return ms
}
// Framework describes a monotone data-flow framework ⟨S, ∨, Transfer⟩ using a bounded join-semilattice ⟨S, ∨⟩ and a
// monotonic transfer function.
//
// Transfer implements the transfer function. Given an instruction, it should return zero or more mappings from IR
// values to abstract values, i.e. values from the semilattice. Transfer must be monotonic. ϕ instructions are handled
// automatically and do not cause Transfer to be called.
//
// The set S is defined implicitly by the values returned by Join and Transfer and needn't be finite. In addition, it
// contains the elements ⊥ and ⊤ (Bottom and Top) with Join(x, ⊥) = x and Join(x, ⊤) = ⊤. The provided Join function is
// wrapped to handle these elements automatically. All IR values start in the ⊥ state.
//
// Abstract states are associated with IR values. As such, the analysis is sparse and favours the partitioned variable
// lattice (PVL) property.
type Framework[S comparable] struct {
Join Join[S]
Transfer func(*Instance[S], ir.Instruction) []Mapping[S]
Bottom S
Top S
}
// Start returns a new instance of the framework. See also [Framework.Forward].
func (fw *Framework[S]) Start() *Instance[S] {
if fw.Bottom == fw.Top {
panic("framework's ⊥ and ⊤ are identical; did you forget to specify them?")
}
return &Instance[S]{
Framework: fw,
Mapping: map[ir.Value]Mapping[S]{},
}
}
// Forward runs an intraprocedural forward data flow analysis, using an iterative fixed-point algorithm, given the
// functions specified in the framework. It combines [Framework.Start] and [Instance.Forward].
func (fw *Framework[S]) Forward(fn *ir.Function) *Instance[S] {
ins := fw.Start()
ins.Forward(fn)
return ins
}
// Dot returns a directed graph in [Graphviz] format that represents the finite join-semilattice ⟨S, ≤⟩.
// Vertices represent elements in S and edges represent the ≤ relation between elements.
// We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by computing x ∨ y for all elements in [S]², where x ≤ y iff x ∨ y == y.
//
// The resulting graph can be filtered through [tred] to compute the transitive reduction of the graph, the
// visualisation of which corresponds to the Hasse diagram of the semilattice.
//
// The set of states should not include the ⊥ and ⊤ elements.
//
// [Graphviz]: https://graphviz.org/
// [tred]: https://graphviz.org/docs/cli/tred/
func Dot[S comparable](fn Join[S], states []S, bottom, top S) string {
var sb strings.Builder
sb.WriteString("digraph{\n")
sb.WriteString("rankdir=\"BT\"\n")
for i, v := range states {
if vs, ok := any(v).(fmt.Stringer); ok {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs)
} else {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v))
}
}
for dx, x := range states {
for dy, y := range states {
if dx == dy {
continue
}
if join(fn, x, y, bottom, top) == y {
fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy)
}
}
}
sb.WriteString("}")
return sb.String()
}
// Instance is an instance of a data-flow analysis. It is created by [Framework.Forward].
type Instance[S comparable] struct {
Framework *Framework[S]
// Mapping is the result of the analysis. Consider using Instance.Value instead of accessing Mapping
// directly, as it correctly returns ⊥ for missing values.
Mapping map[ir.Value]Mapping[S]
}
// Set maps v to the abstract value d. It does not apply any checks. This should only be used before calling [Instance.Forward], to set
// initial states of values.
func (ins *Instance[S]) Set(v ir.Value, d S) {
ins.Mapping[v] = Mapping[S]{Value: v, State: d}
}
// Value returns the abstract value for v. If none was set, it returns ⊥.
func (ins *Instance[S]) Value(v ir.Value) S {
m, ok := ins.Mapping[v]
if ok {
return m.State
} else {
return ins.Framework.Bottom
}
}
// Decision returns the decision of the mapping for v, if any.
func (ins *Instance[S]) Decision(v ir.Value) Decision {
return ins.Mapping[v].Decision
}
var dfsDebugMu sync.Mutex
func join[S comparable](fn Join[S], a, b, bottom, top S) S {
switch {
case a == top || b == top:
return top
case a == bottom:
return b
case b == bottom:
return a
case a == b:
return a
default:
return fn(a, b)
}
}
// Forward runs a forward data-flow analysis on fn.
func (ins *Instance[S]) Forward(fn *ir.Function) {
if debugging {
dfsDebugMu.Lock()
defer dfsDebugMu.Unlock()
}
debugf("Analyzing %s\n", fn)
if ins.Mapping == nil {
ins.Mapping = map[ir.Value]Mapping[S]{}
}
worklist := map[ir.Instruction]struct{}{}
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
worklist[instr] = struct{}{}
}
}
for len(worklist) > 0 {
var instr ir.Instruction
for instr = range worklist {
break
}
delete(worklist, instr)
var ds []Mapping[S]
if phi, ok := instr.(*ir.Phi); ok {
d := ins.Framework.Bottom
for _, edge := range phi.Edges {
a, b := d, ins.Value(edge)
d = join(ins.Framework.Join, a, b, ins.Framework.Bottom, ins.Framework.Top)
debugf("join(%v, %v) = %v", a, b, d)
}
ds = []Mapping[S]{{Value: phi, State: d, Decision: Decision{Inputs: phi.Edges, Description: "this variable merges the results of multiple branches"}}}
} else {
ds = ins.Framework.Transfer(ins, instr)
}
if len(ds) > 0 {
if v, ok := instr.(ir.Value); ok {
debugf("transfer(%s = %s) = %v", v.Name(), instr, ds)
} else {
debugf("transfer(%s) = %v", instr, ds)
}
}
for i, d := range ds {
old := ins.Value(d.Value)
dd := d.State
if dd != old {
if j := join(ins.Framework.Join, old, dd, ins.Framework.Bottom, ins.Framework.Top); j != dd {
panic(fmt.Sprintf("transfer function isn't monotonic; Transfer(%v)[%d] = %v; join(%v, %v) = %v", instr, i, dd, old, dd, j))
}
ins.Mapping[d.Value] = Mapping[S]{Value: d.Value, State: dd, Decision: d.Decision}
for _, ref := range *instr.Referrers() {
worklist[ref] = struct{}{}
}
}
}
printMapping(fn, ins.Mapping)
}
}
// Propagate is a helper for creating a [Mapping] that propagates the abstract state of src to dst.
// The desc parameter is used as the value of Decision.Description.
func (ins *Instance[S]) Propagate(dst, src ir.Value, desc string) Mapping[S] {
return M(dst, ins.Value(src), Decision{Inputs: []ir.Value{src}, Description: desc})
}
func (ins *Instance[S]) Transform(dst ir.Value, s S, src ir.Value, desc string) Mapping[S] {
return M(dst, s, Decision{Inputs: []ir.Value{src}, Description: desc})
}
func printMapping[S any](fn *ir.Function, m map[ir.Value]S) {
if !debugging {
return
}
debugf("Mapping for %s:\n", fn)
var keys []ir.Value
for k := range m {
keys = append(keys, k)
}
slices.SortFunc(keys, func(a, b ir.Value) int {
return cmp.Compare(a.ID(), b.ID())
})
for _, k := range keys {
v := m[k]
debugf("\t%v\n", v)
}
}
// BinaryTable returns a binary operator based on the provided mapping.
// For missing pairs of values, the default value will be returned.
func BinaryTable[S comparable](default_ S, m map[[2]S]S) func(S, S) S {
return func(a, b S) S {
if d, ok := m[[2]S{a, b}]; ok {
return d
} else if d, ok := m[[2]S{b, a}]; ok {
return d
} else {
return default_
}
}
}
// JoinTable returns a [Join] function based on the provided mapping.
// For missing pairs of values, the default value will be returned.
func JoinTable[S comparable](top S, m map[[2]S]S) Join[S] {
return func(a, b S) S {
if d, ok := m[[2]S{a, b}]; ok {
return d
} else if d, ok := m[[2]S{b, a}]; ok {
return d
} else {
return top
}
}
}
func PowerSet[S constraints.Integer](all S) []S {
out := make([]S, all+1)
for i := range out {
out[i] = S(i)
}
return out
}
func MapSet[S constraints.Integer](set S, fn func(S) S) S {
bits := 64 - bits.LeadingZeros64(uint64(set))
var out S
for i := range bits {
if b := (set & (1 << i)); b != 0 {
out |= fn(b)
}
}
return out
}
func MapCartesianProduct[S constraints.Integer](x, y S, fn func(S, S) S) S {
bitsX := 64 - bits.LeadingZeros64(uint64(x))
bitsY := 64 - bits.LeadingZeros64(uint64(y))
var out S
for i := range bitsX {
for j := range bitsY {
bx := x & (1 << i)
by := y & (1 << j)
if bx != 0 && by != 0 {
out |= fn(bx, by)
}
}
}
return out
}
================================================
FILE: analysis/edit/edit.go
================================================
// Package edit contains helpers for creating suggested fixes.
package edit
import (
"bytes"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/pattern"
)
// Ranger describes values that have a start and end position.
// In most cases these are either ast.Node or manually constructed ranges.
type Ranger interface {
Pos() token.Pos
End() token.Pos
}
// Range implements the Ranger interface.
type Range [2]token.Pos
func (r Range) Pos() token.Pos { return r[0] }
func (r Range) End() token.Pos { return r[1] }
// ReplaceWithString replaces a range with a string.
func ReplaceWithString(old Ranger, new string) analysis.TextEdit {
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: []byte(new),
}
}
// ReplaceWithNode replaces a range with an AST node.
func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit {
buf := &bytes.Buffer{}
if err := format.Node(buf, fset, new); err != nil {
panic("internal error: " + err.Error())
}
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: buf.Bytes(),
}
}
// ReplaceWithPattern replaces a range with the result of executing a pattern.
func ReplaceWithPattern(fset *token.FileSet, old Ranger, new pattern.Pattern, state pattern.State) analysis.TextEdit {
r := pattern.NodeToAST(new.Root, state)
buf := &bytes.Buffer{}
format.Node(buf, fset, r)
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: buf.Bytes(),
}
}
// Delete deletes a range of code.
func Delete(old Ranger) analysis.TextEdit {
return analysis.TextEdit{
Pos: old.Pos(),
End: old.End(),
NewText: nil,
}
}
func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix {
return analysis.SuggestedFix{
Message: msg,
TextEdits: edits,
}
}
// Selector creates a new selector expression.
func Selector(x, sel string) *ast.SelectorExpr {
return &ast.SelectorExpr{
X: &ast.Ident{Name: x},
Sel: &ast.Ident{Name: sel},
}
}
================================================
FILE: analysis/facts/deprecated/deprecated.go
================================================
package deprecated
import (
"go/ast"
"go/token"
"go/types"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
)
type IsDeprecated struct{ Msg string }
func (*IsDeprecated) AFact() {}
func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg }
type Result struct {
Objects map[types.Object]*IsDeprecated
Packages map[*types.Package]*IsDeprecated
}
var Analyzer = &analysis.Analyzer{
Name: "fact_deprecated",
Doc: "Mark deprecated objects",
Run: deprecated,
FactTypes: []analysis.Fact{(*IsDeprecated)(nil)},
ResultType: reflect.TypeFor[Result](),
}
func deprecated(pass *analysis.Pass) (any, error) {
var names []*ast.Ident
extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
for _, doc := range docs {
if doc == nil {
continue
}
parts := strings.SplitSeq(doc.Text(), "\n\n")
for part := range parts {
if !strings.HasPrefix(part, "Deprecated: ") {
continue
}
alt := part[len("Deprecated: "):]
alt = strings.Replace(alt, "\n", " ", -1)
return alt
}
}
return ""
}
doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) {
alt := extractDeprecatedMessage(docs)
if alt == "" {
return
}
for _, name := range names {
obj := pass.TypesInfo.ObjectOf(name)
pass.ExportObjectFact(obj, &IsDeprecated{alt})
}
}
var docs []*ast.CommentGroup
for _, f := range pass.Files {
docs = append(docs, f.Doc)
}
if alt := extractDeprecatedMessage(docs); alt != "" {
// Don't mark package syscall as deprecated, even though
// it is. A lot of people still use it for simple
// constants like SIGKILL, and I am not comfortable
// telling them to use x/sys for that.
if pass.Pkg.Path() != "syscall" {
pass.ExportPackageFact(&IsDeprecated{alt})
}
}
docs = docs[:0]
for _, f := range pass.Files {
fn := func(node ast.Node) bool {
if node == nil {
return true
}
var ret bool
switch node := node.(type) {
case *ast.GenDecl:
switch node.Tok {
case token.TYPE, token.CONST, token.VAR:
docs = append(docs, node.Doc)
for i := range node.Specs {
switch n := node.Specs[i].(type) {
case *ast.ValueSpec:
names = append(names, n.Names...)
case *ast.TypeSpec:
names = append(names, n.Name)
}
}
ret = true
default:
return false
}
case *ast.FuncDecl:
docs = append(docs, node.Doc)
names = []*ast.Ident{node.Name}
ret = false
case *ast.TypeSpec:
docs = append(docs, node.Doc)
names = []*ast.Ident{node.Name}
ret = true
case *ast.ValueSpec:
docs = append(docs, node.Doc)
names = node.Names
ret = false
case *ast.File:
return true
case *ast.StructType:
for _, field := range node.Fields.List {
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
}
return false
case *ast.InterfaceType:
for _, field := range node.Methods.List {
doDocs(field.Names, []*ast.CommentGroup{field.Doc})
}
return false
default:
return false
}
if len(names) == 0 || len(docs) == 0 {
return ret
}
doDocs(names, docs)
docs = docs[:0]
names = nil
return ret
}
ast.Inspect(f, fn)
}
out := Result{
Objects: map[types.Object]*IsDeprecated{},
Packages: map[*types.Package]*IsDeprecated{},
}
for _, fact := range pass.AllObjectFacts() {
out.Objects[fact.Object] = fact.Fact.(*IsDeprecated)
}
for _, fact := range pass.AllPackageFacts() {
out.Packages[fact.Package] = fact.Fact.(*IsDeprecated)
}
return out, nil
}
================================================
FILE: analysis/facts/deprecated/deprecated_test.go
================================================
package deprecated
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestDeprecated(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analyzer, "example.com/Deprecated")
}
================================================
FILE: analysis/facts/deprecated/testdata/src/example.com/Deprecated/Deprecated.go
================================================
package pkg
// Deprecated: Don't use this.
func fn2() { // want fn2:`Deprecated: Don't use this\.`
}
// This is a function.
//
// Deprecated: Don't use this.
//
// Here is how you might use it instead.
func fn3() { // want fn3:`Deprecated: Don't use this\.`
}
// Handle cases like:
//
// Taken from "os" package:
//
// ```
// // Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
// const (
// SEEK_SET int = 0 // seek relative to the origin of the file
// SEEK_CUR int = 1 // seek relative to the current offset
// SEEK_END int = 2 // seek relative to the end
// )
// ```
//
// Here all three consts i.e., os.SEEK_SET, os.SEEK_CUR and os.SEEK_END are
// deprecated and not just os.SEEK_SET.
// Deprecated: Don't use this.
var (
SEEK_A = 0 // want SEEK_A:`Deprecated: Don't use this\.`
SEEK_B = 1 // want SEEK_B:`Deprecated: Don't use this\.`
SEEK_C = 2 // want SEEK_C:`Deprecated: Don't use this\.`
)
// Deprecated: Don't use this.
type (
pair struct{ x, y int } // want pair:`Deprecated: Don't use this\.`
cube struct{ x, y, z int } // want cube:`Deprecated: Don't use this\.`
)
// Deprecated: Don't use this.
var SEEK_D = 3 // want SEEK_D:`Deprecated: Don't use this\.`
var SEEK_E = 4
var SEEK_F = 5
================================================
FILE: analysis/facts/directives/directives.go
================================================
package directives
import (
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/lint"
)
func directives(pass *analysis.Pass) (any, error) {
return lint.ParseDirectives(pass.Files, pass.Fset), nil
}
var Analyzer = &analysis.Analyzer{
Name: "directives",
Doc: "extracts linter directives",
Run: directives,
RunDespiteErrors: true,
ResultType: reflect.TypeFor[[]lint.Directive](),
}
================================================
FILE: analysis/facts/generated/generated.go
================================================
package generated
import (
"bufio"
"bytes"
"io"
"os"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
)
type Generator int
// A list of known generators we can detect
const (
Unknown Generator = iota
Goyacc
Cgo
Stringer
ProtocGenGo
)
var (
// used by cgo before Go 1.11
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
prefix = []byte("// Code generated ")
suffix = []byte(" DO NOT EDIT.")
nl = []byte("\n")
crnl = []byte("\r\n")
)
func isGenerated(path string) (Generator, bool) {
f, err := os.Open(path)
if err != nil {
return 0, false
}
defer f.Close()
br := bufio.NewReader(f)
for {
s, err := br.ReadBytes('\n')
if err != nil && err != io.EOF {
return 0, false
}
s = bytes.TrimSuffix(s, crnl)
s = bytes.TrimSuffix(s, nl)
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
if len(s)-len(suffix) < len(prefix) {
return Unknown, true
}
text := string(s[len(prefix) : len(s)-len(suffix)])
switch text {
case "by goyacc.":
return Goyacc, true
case "by cmd/cgo;":
return Cgo, true
case "by protoc-gen-go.":
return ProtocGenGo, true
}
if strings.HasPrefix(text, `by "stringer `) {
return Stringer, true
}
if strings.HasPrefix(text, `by goyacc `) {
return Goyacc, true
}
return Unknown, true
}
if bytes.Equal(s, oldCgo) {
return Cgo, true
}
if err == io.EOF {
break
}
}
return 0, false
}
var Analyzer = &analysis.Analyzer{
Name: "isgenerated",
Doc: "annotate file names that have been code generated",
Run: func(pass *analysis.Pass) (any, error) {
m := map[string]Generator{}
for _, f := range pass.Files {
path := pass.Fset.PositionFor(f.Pos(), false).Filename
g, ok := isGenerated(path)
if ok {
m[path] = g
}
}
return m, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeFor[map[string]Generator](),
}
================================================
FILE: analysis/facts/nilness/nilness.go
================================================
package nilness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
// neverReturnsNilFact denotes that a function's return value will never
// be nil (typed or untyped). The analysis errs on the side of false
// negatives.
type neverReturnsNilFact struct {
Rets []neverNilness
}
func (*neverReturnsNilFact) AFact() {}
func (fact *neverReturnsNilFact) String() string {
return fmt.Sprintf("never returns nil: %v", fact.Rets)
}
type Result struct {
m map[*types.Func][]neverNilness
}
var Analysis = &analysis.Analyzer{
Name: "nilness",
Doc: "Annotates return values that will never be nil (typed or untyped)",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*neverReturnsNilFact)(nil)},
ResultType: reflect.TypeFor[*Result](),
}
// MayReturnNil reports whether the ret's return value of fn might be
// a typed or untyped nil value. The value of ret is zero-based. When
// globalOnly is true, the only possible nil values are global
// variables.
//
// The analysis has false positives: MayReturnNil can incorrectly
// report true, but never incorrectly reports false.
func (r *Result) MayReturnNil(fn *types.Func, ret int) (yes bool, globalOnly bool) {
if !typeutil.IsPointerLike(fn.Type().(*types.Signature).Results().At(ret).Type()) {
return false, false
}
if len(r.m[fn]) == 0 {
return true, false
}
v := r.m[fn][ret]
return v != neverNil, v == onlyGlobal
}
func run(pass *analysis.Pass) (any, error) {
seen := map[*ir.Function]struct{}{}
out := &Result{
m: map[*types.Func][]neverNilness{},
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
impl(pass, fn, seen)
}
for _, fact := range pass.AllObjectFacts() {
out.m[fact.Object.(*types.Func)] = fact.Fact.(*neverReturnsNilFact).Rets
}
return out, nil
}
type neverNilness uint8
const (
neverNil neverNilness = 1
onlyGlobal neverNilness = 2
nilly neverNilness = 3
)
func (n neverNilness) String() string {
switch n {
case neverNil:
return "never"
case onlyGlobal:
return "global"
case nilly:
return "nil"
default:
return "BUG"
}
}
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []neverNilness {
if fn.Object() == nil {
// TODO(dh): support closures
return nil
}
if fact := new(neverReturnsNilFact); pass.ImportObjectFact(fn.Object(), fact) {
return fact.Rets
}
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
return nil
}
if fn.Blocks == nil {
return nil
}
if _, ok := seenFns[fn]; ok {
// break recursion
return nil
}
seenFns[fn] = struct{}{}
seen := map[ir.Value]struct{}{}
var mightReturnNil func(v ir.Value) neverNilness
mightReturnNil = func(v ir.Value) neverNilness {
if _, ok := seen[v]; ok {
// break cycle
return nilly
}
if !typeutil.IsPointerLike(v.Type()) {
return neverNil
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.MakeInterface:
return mightReturnNil(v.X)
case *ir.Convert:
return mightReturnNil(v.X)
case *ir.SliceToArrayPointer:
if typeutil.CoreType(v.Type()).(*types.Pointer).Elem().Underlying().(*types.Array).Len() == 0 {
return mightReturnNil(v.X)
} else {
// converting a slice to an array pointer of length > 0 panics if the slice is nil
return neverNil
}
case *ir.Slice:
return mightReturnNil(v.X)
case *ir.Phi:
ret := neverNil
for _, e := range v.Edges {
if n := mightReturnNil(e); n > ret {
ret = n
}
}
return ret
case *ir.Extract:
switch d := v.Tuple.(type) {
case *ir.Call:
if callee := d.Call.StaticCallee(); callee != nil {
ret := impl(pass, callee, seenFns)
if len(ret) == 0 {
return nilly
}
return ret[v.Index]
} else {
return nilly
}
case *ir.TypeAssert, *ir.Next, *ir.Select, *ir.MapLookup, *ir.TypeSwitch, *ir.Recv, *ir.Sigma:
// we don't need to look at the Extract's index
// because we've already checked its type.
return nilly
default:
panic(fmt.Sprintf("internal error: unhandled type %T", d))
}
case *ir.Call:
if callee := v.Call.StaticCallee(); callee != nil {
ret := impl(pass, callee, seenFns)
if len(ret) == 0 {
return nilly
}
return ret[0]
} else {
return nilly
}
case *ir.BinOp, *ir.UnOp, *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr, *ir.Global, *ir.MakeSlice, *ir.MakeClosure, *ir.Function, *ir.MakeMap, *ir.MakeChan:
return neverNil
case *ir.Sigma:
iff, ok := v.From.Control().(*ir.If)
if !ok {
return nilly
}
binop, ok := iff.Cond.(*ir.BinOp)
if !ok {
return nilly
}
isNil := func(v ir.Value) bool {
k, ok := v.(*ir.Const)
if !ok {
return false
}
return k.Value == nil
}
if binop.X == v.X && isNil(binop.Y) || binop.Y == v.X && isNil(binop.X) {
op := binop.Op
if v.From.Succs[0] != v.Block() {
// we're in the false branch, negate op
switch op {
case token.EQL:
op = token.NEQ
case token.NEQ:
op = token.EQL
default:
panic(fmt.Sprintf("internal error: unhandled token %v", op))
}
}
switch op {
case token.EQL:
return nilly
case token.NEQ:
return neverNil
default:
panic(fmt.Sprintf("internal error: unhandled token %v", op))
}
}
return nilly
case *ir.ChangeType:
return mightReturnNil(v.X)
case *ir.MultiConvert:
return mightReturnNil(v.X)
case *ir.Load:
if _, ok := v.X.(*ir.Global); ok {
return onlyGlobal
}
return nilly
case *ir.AggregateConst:
return neverNil
case *ir.TypeAssert, *ir.ChangeInterface, *ir.Field, *ir.Const, *ir.GenericConst, *ir.Index, *ir.MapLookup, *ir.Parameter, *ir.Recv, *ir.TypeSwitch:
return nilly
default:
panic(fmt.Sprintf("internal error: unhandled type %T", v))
}
}
ret := fn.Exit.Control().(*ir.Return)
out := make([]neverNilness, len(ret.Results))
export := false
for i, v := range ret.Results {
// OPT(dh): couldn't we check the result type's pointer-likeness early, and skip
// processing the return value altogether?
v := mightReturnNil(v)
out[i] = v
if v != nilly && typeutil.IsPointerLike(fn.Signature.Results().At(i).Type()) {
export = true
}
}
if export {
pass.ExportObjectFact(fn.Object(), &neverReturnsNilFact{out})
}
return out
}
================================================
FILE: analysis/facts/nilness/nilness_test.go
================================================
package nilness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestNilness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Nilness")
}
================================================
FILE: analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness.go
================================================
package pkg
import "errors"
type T struct{ f *int }
type T2 T
func fn1() *T {
if true {
return nil
}
return &T{}
}
func fn2() *T { // want fn2:`never returns nil: \[never\]`
return &T{}
}
func fn3() *T { // want fn3:`never returns nil: \[never\]`
return new(T)
}
func fn4() *T { // want fn4:`never returns nil: \[never\]`
return fn3()
}
func fn5() *T {
return fn1()
}
func fn6() *T2 { // want fn6:`never returns nil: \[never\]`
return (*T2)(fn4())
}
func fn7() interface{} {
return nil
}
func fn8() interface{} { // want fn8:`never returns nil: \[never\]`
return 1
}
func fn9() []int { // want fn9:`never returns nil: \[never\]`
x := []int{}
y := x[:1]
return y
}
func fn10(x []int) []int {
return x[:1]
}
func fn11(x *T) *T {
return x
}
func fn12(x *T) *int {
return x.f
}
func fn13() *int { // want fn13:`never returns nil: \[never\]`
return new(int)
}
func fn14() []int { // want fn14:`never returns nil: \[never\]`
return make([]int, 0)
}
func fn15() []int { // want fn15:`never returns nil: \[never\]`
return []int{}
}
func fn16() []int {
return nil
}
func fn17() error {
if true {
return errors.New("")
}
return nil
}
func fn18() (err error) { // want fn18:`never returns nil: \[never\]`
for {
if err = fn17(); err != nil {
return
}
}
}
var x *int
func fn19() *int { // want fn19:`never returns nil: \[global\]`
return x
}
func fn20() *int {
if true {
return x
}
return nil
}
func fn27[T ~struct{ F int }]() T {
return T{0}
}
func fn28[T [8]int]() T {
return T{}
}
func fn29[T []int]() T { // want fn29:`never returns nil: \[never\]`
return T{}
}
================================================
FILE: analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness_go118.go
================================================
//go:build go1.18
package pkg
// Make sure we don't crash upon seeing a MultiConvert instruction.
func generic1[T []byte | string](s T) T {
switch v := any(s).(type) {
case string:
return T(v)
case []byte:
return T(v)
default:
return s
}
}
// Make sure we don't emit a fact for a function whose return type isn't pointer-like.
func generic2[T [4]byte | string](s T) T {
switch v := any(s).(type) {
case string:
return T([]byte(v))
case [4]byte:
return T(v[:])
default:
return s
}
}
// Make sure we detect that the return value cannot be nil. It is either a string, a
// non-nil slice we got passed, or a non-nil slice we allocate. Note that we don't
// understand that the switch's non-default branches are exhaustive over the type set and
// for the fact to be computed, we have to return something non-nil from the unreachable
// default branch.
func generic3[T []byte | string](s T) T { // want generic3:`never returns nil: \[never\]`
switch v := any(s).(type) {
case string:
return T(v)
case []byte:
if v == nil {
return T([]byte{})
} else {
return T(v)
}
default:
return T([]byte{})
}
}
================================================
FILE: analysis/facts/nilness/testdata/src/example.com/Nilness/Nilness_go17.go
================================================
//go:build go1.17
// +build go1.17
package pkg
func fn21() *[5]int { // want fn21:`never returns nil: \[never\]`
var x []int
return (*[5]int)(x)
}
func fn22() *[0]int {
var x []int
return (*[0]int)(x)
}
func fn23() *[5]int { // want fn23:`never returns nil: \[never\]`
var x []int
type T [5]int
ret := (*T)(x)
return (*[5]int)(ret)
}
func fn24() *[0]int {
var x []int
type T [0]int
ret := (*T)(x)
return (*[0]int)(ret)
}
func fn25() *[5]int { // want fn25:`never returns nil: \[never\]`
var x []int
type T *[5]int
return (T)(x)
}
func fn26() *[0]int {
var x []int
type T *[0]int
return (T)(x)
}
================================================
FILE: analysis/facts/purity/purity.go
================================================
package purity
// TODO(dh): we should split this into two facts, one tracking actual purity, and one tracking side-effects. A function
// that returns a heap allocation isn't pure, but it may be free of side effects.
import (
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
type IsPure struct{}
func (*IsPure) AFact() {}
func (d *IsPure) String() string { return "is pure" }
type Result map[*types.Func]*IsPure
var Analyzer = &analysis.Analyzer{
Name: "fact_purity",
Doc: "Mark pure functions",
Run: purity,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*IsPure)(nil)},
ResultType: reflect.TypeFor[Result](),
}
var pureStdlib = map[string]struct{}{
"errors.New": {},
"fmt.Errorf": {},
"fmt.Sprintf": {},
"fmt.Sprint": {},
"sort.Reverse": {},
"strings.Map": {},
"strings.Repeat": {},
"strings.Replace": {},
"strings.Title": {},
"strings.ToLower": {},
"strings.ToLowerSpecial": {},
"strings.ToTitle": {},
"strings.ToTitleSpecial": {},
"strings.ToUpper": {},
"strings.ToUpperSpecial": {},
"strings.Trim": {},
"strings.TrimFunc": {},
"strings.TrimLeft": {},
"strings.TrimLeftFunc": {},
"strings.TrimPrefix": {},
"strings.TrimRight": {},
"strings.TrimRightFunc": {},
"strings.TrimSpace": {},
"strings.TrimSuffix": {},
"(*net/http.Request).WithContext": {},
"time.Now": {},
"time.Parse": {},
"time.ParseInLocation": {},
"time.Unix": {},
"time.UnixMicro": {},
"time.UnixMilli": {},
"(time.Time).Add": {},
"(time.Time).AddDate": {},
"(time.Time).After": {},
"(time.Time).Before": {},
"(time.Time).Clock": {},
"(time.Time).Compare": {},
"(time.Time).Date": {},
"(time.Time).Day": {},
"(time.Time).Equal": {},
"(time.Time).Format": {},
"(time.Time).GoString": {},
"(time.Time).GobEncode": {},
"(time.Time).Hour": {},
"(time.Time).ISOWeek": {},
"(time.Time).In": {},
"(time.Time).IsDST": {},
"(time.Time).IsZero": {},
"(time.Time).Local": {},
"(time.Time).Location": {},
"(time.Time).MarshalBinary": {},
"(time.Time).MarshalJSON": {},
"(time.Time).MarshalText": {},
"(time.Time).Minute": {},
"(time.Time).Month": {},
"(time.Time).Nanosecond": {},
"(time.Time).Round": {},
"(time.Time).Second": {},
"(time.Time).String": {},
"(time.Time).Sub": {},
"(time.Time).Truncate": {},
"(time.Time).UTC": {},
"(time.Time).Unix": {},
"(time.Time).UnixMicro": {},
"(time.Time).UnixMilli": {},
"(time.Time).UnixNano": {},
"(time.Time).Weekday": {},
"(time.Time).Year": {},
"(time.Time).YearDay": {},
"(time.Time).Zone": {},
"(time.Time).ZoneBounds": {},
}
func purity(pass *analysis.Pass) (any, error) {
seen := map[*ir.Function]struct{}{}
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
var check func(fn *ir.Function) (ret bool)
check = func(fn *ir.Function) (ret bool) {
if fn.Object() == nil {
// TODO(dh): support closures
return false
}
if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
return true
}
if fn.Pkg != irpkg {
// Function is in another package but wasn't marked as
// pure, ergo it isn't pure
return false
}
// Break recursion
if _, ok := seen[fn]; ok {
return false
}
seen[fn] = struct{}{}
defer func() {
if ret {
pass.ExportObjectFact(fn.Object(), &IsPure{})
}
}()
if irutil.IsStub(fn) {
return false
}
if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
return true
}
if fn.Signature.Results().Len() == 0 {
// A function with no return values is empty or is doing some
// work we cannot see (for example because of build tags);
// don't consider it pure.
return false
}
var isBasic func(typ types.Type) bool
isBasic = func(typ types.Type) bool {
switch u := typ.Underlying().(type) {
case *types.Basic:
return true
case *types.Struct:
for field := range u.Fields() {
if !isBasic(field.Type()) {
return false
}
}
return true
default:
return false
}
}
for _, param := range fn.Params {
// TODO(dh): this may not be strictly correct. pure code can, to an extent, operate on non-basic types.
if !isBasic(param.Type()) {
return false
}
}
// Don't consider external functions pure.
if fn.Blocks == nil {
return false
}
checkCall := func(common *ir.CallCommon) bool {
if common.IsInvoke() {
return false
}
builtin, ok := common.Value.(*ir.Builtin)
if !ok {
if common.StaticCallee() != fn {
if common.StaticCallee() == nil {
return false
}
if !check(common.StaticCallee()) {
return false
}
}
} else {
switch builtin.Name() {
case "len", "cap":
default:
return false
}
}
return true
}
var isStackAddr func(ir.Value) bool
isStackAddr = func(v ir.Value) bool {
switch v := v.(type) {
case *ir.Alloc:
return !v.Heap
case *ir.FieldAddr:
return isStackAddr(v.X)
default:
return false
}
}
for _, b := range fn.Blocks {
for _, ins := range b.Instrs {
switch ins := ins.(type) {
case *ir.Call:
if !checkCall(ins.Common()) {
return false
}
case *ir.Defer:
if !checkCall(&ins.Call) {
return false
}
case *ir.Select:
return false
case *ir.Send:
return false
case *ir.Go:
return false
case *ir.Panic:
return false
case *ir.Store:
if !isStackAddr(ins.Addr) {
return false
}
case *ir.FieldAddr:
if !isStackAddr(ins.X) {
return false
}
case *ir.Alloc:
// TODO(dh): make use of proper escape analysis
if ins.Heap {
return false
}
case *ir.Load:
if !isStackAddr(ins.X) {
return false
}
}
}
}
return true
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
check(fn)
}
out := Result{}
for _, fact := range pass.AllObjectFacts() {
out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)
}
return out, nil
}
================================================
FILE: analysis/facts/purity/purity_test.go
================================================
package purity
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestPurity(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analyzer, "example.com/Purity")
}
================================================
FILE: analysis/facts/purity/testdata/src/example.com/Purity/CheckPureFunctions.go
================================================
package pkg
func foo(a, b int) int { return a + b } // want foo:"is pure"
func bar(a, b int) int {
println(a + b)
return a + b
}
func empty() {}
func stubPointer() *int { return nil }
func stubInt() int { return 0 }
func fn3() {
empty()
stubPointer()
stubInt()
}
func ptr1() *int { return new(int) }
func ptr2() *int { var x int; return &x }
func lit() []int { return []int{} }
var X int
func load() int { _ = X; return 0 }
func assign(x int) int { _ = x; return 0 } // want assign:"is pure"
type pureStruct1 struct {
a int
b string
pureStruct2
}
type pureStruct2 struct {
c float64
}
func (arg pureStruct1) get() int { // want get:"is pure"
return arg.a
}
================================================
FILE: analysis/facts/tokenfile/token.go
================================================
package tokenfile
import (
"go/ast"
"go/token"
"reflect"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "tokenfileanalyzer",
Doc: "creates a mapping of *token.File to *ast.File",
Run: func(pass *analysis.Pass) (any, error) {
m := map[*token.File]*ast.File{}
for _, af := range pass.Files {
tf := pass.Fset.File(af.Pos())
m[tf] = af
}
return m, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeFor[map[*token.File]*ast.File](),
}
================================================
FILE: analysis/facts/typedness/testdata/src/example.com/Typedness/Typedness.go
================================================
package pkg
import (
"errors"
"os/exec"
)
type T struct{ x *int }
func notAStub() {}
func fn1() *int { return nil }
func fn2() (int, *int, int) { return 0, nil, 0 }
func fn3() (out1 int, out2 error) { notAStub(); return 0, nil }
func fn4() error { notAStub(); return nil }
func gen2() (out1 interface{}) { // want gen2:`always typed: 00000001`
return 1
}
func gen3() (out1 interface{}) { // want gen3:`always typed: 00000001`
// flag, always returns a typed value
m := map[int]*int{}
return m[0]
}
func gen4() (out1 int, out2 interface{}, out3 *int) { // want gen4:`always typed: 00000010`
// flag ret[1], always a typed value
m := map[int]*int{}
return 0, m[0], nil
}
func gen5() (out1 interface{}) { // want gen5:`always typed: 00000001`
// flag, propagate gen3
return gen3()
}
func gen6(b bool) interface{} {
// don't flag, sometimes returns untyped nil
if b {
m := map[int]*int{}
return m[0]
} else {
return nil
}
}
func gen7() (out1 interface{}) { // want gen7:`always typed: 00000001`
// flag, always returns a typed value
return fn1()
}
func gen8(x *int) (out1 interface{}) { // want gen8:`always typed: 00000001`
// flag
if x == nil {
return x
}
return x
}
func gen9() (out1 interface{}) { // want gen9:`always typed: 00000001`
// flag
var x *int
return x
}
func gen10() (out1 interface{}) { // want gen10:`always typed: 00000001`
// flag
var x *int
if x == nil {
return x
}
return errors.New("")
}
func gen11() interface{} {
// don't flag, we sometimes return untyped nil
if true {
return nil
} else {
return (*int)(nil)
}
}
func gen12(b bool) (out1 interface{}) { // want gen12:`always typed: 00000001`
// flag, all branches return typed nils
var x interface{}
if b {
x = (*int)(nil)
} else {
x = (*string)(nil)
}
return x
}
func gen13() (out1 interface{}) { // want gen13:`always typed: 00000001`
// flag, always returns a typed value
_, x, _ := fn2()
return x
}
func gen14(ch chan *int) (out1 interface{}) { // want gen14:`always typed: 00000001`
// flag
return <-ch
}
func gen15() (out1 interface{}) { // want gen15:`always typed: 00000001`
// flag
t := &T{}
return t.x
}
var g *int = new(int)
func gen16() (out1 interface{}) { // want gen16:`always typed: 00000001`
return g
}
func gen17(x interface{}) interface{} {
// don't flag
if x != nil {
return x
}
return x
}
func gen18() (int, error) {
// don't flag
_, err := fn3()
if err != nil {
return 0, errors.New("yo")
}
return 0, err
}
func gen19() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen20() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen21() error {
if false {
return (*exec.Error)(nil)
}
return fn4()
}
func gen22() interface{} {
// don't flag, propagate gen6
return gen6(false)
}
func gen23() interface{} {
return gen24()
}
func gen24() interface{} {
return gen23()
}
func gen25(x interface{}) (out1 interface{}) { // want gen25:`always typed: 00000001`
return x.(interface{})
}
func gen26(x interface{}) interface{} {
v, _ := x.(interface{})
return v
}
func gen27(x interface{}) (out1 interface{}) {
defer recover()
out1 = x.(interface{})
return out1
}
type Error struct{}
func (*Error) Error() string { return "" }
func gen28() (out1 interface{}) { // want gen28:`always typed: 00000001`
x := new(Error)
var y error = x
return y
}
func gen29() (out1 interface{}) { // want gen29:`always typed: 00000001`
var x *Error
var y error = x
return y
}
func gen30() (out1, out2 interface{}) { // want gen30:`always typed: 00000011`
return gen29(), gen28()
}
func gen31() (out1 interface{}) { // want gen31:`always typed: 00000001`
a, _ := gen30()
return a
}
func gen32() (out1 interface{}) { // want gen32:`always typed: 00000001`
_, b := gen30()
return b
}
func gen33() (out1 interface{}) { // want gen33:`always typed: 00000001`
a, b := gen30()
_ = a
return b
}
func gen34() (out1, out2 interface{}) { // want gen34:`always typed: 00000010`
return nil, 1
}
func gen35() (out1 interface{}) {
a, _ := gen34()
return a
}
func gen36() (out1 interface{}) { // want gen36:`always typed: 00000001`
_, b := gen34()
return b
}
================================================
FILE: analysis/facts/typedness/typedness.go
================================================
package typedness
import (
"fmt"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/exp/typeparams"
"golang.org/x/tools/go/analysis"
)
// alwaysTypedFact denotes that a function's return value will never
// be untyped nil. The analysis errs on the side of false negatives.
type alwaysTypedFact struct {
Rets uint8
}
func (*alwaysTypedFact) AFact() {}
func (fact *alwaysTypedFact) String() string {
return fmt.Sprintf("always typed: %08b", fact.Rets)
}
type Result struct {
m map[*types.Func]uint8
}
var Analysis = &analysis.Analyzer{
Name: "typedness",
Doc: "Annotates return values that are always typed values",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
FactTypes: []analysis.Fact{(*alwaysTypedFact)(nil)},
ResultType: reflect.TypeFor[*Result](),
}
// MustReturnTyped reports whether the ret's return value of fn must
// be a typed value, i.e. an interface value containing a concrete
// type or trivially a concrete type. The value of ret is zero-based.
//
// The analysis has false negatives: MustReturnTyped may incorrectly
// report false, but never incorrectly reports true.
func (r *Result) MustReturnTyped(fn *types.Func, ret int) bool {
if _, ok := fn.Type().(*types.Signature).Results().At(ret).Type().Underlying().(*types.Interface); !ok {
return true
}
return (r.m[fn] & (1 << ret)) != 0
}
func run(pass *analysis.Pass) (any, error) {
seen := map[*ir.Function]struct{}{}
out := &Result{
m: map[*types.Func]uint8{},
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
impl(pass, fn, seen)
}
for _, fact := range pass.AllObjectFacts() {
out.m[fact.Object.(*types.Func)] = fact.Fact.(*alwaysTypedFact).Rets
}
return out, nil
}
func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) (out uint8) {
if fn.Signature.Results().Len() > 8 {
return 0
}
if fn.Object() == nil {
// TODO(dh): support closures
return 0
}
if fact := new(alwaysTypedFact); pass.ImportObjectFact(fn.Object(), fact) {
return fact.Rets
}
if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg {
return 0
}
if fn.Blocks == nil {
return 0
}
if irutil.IsStub(fn) {
return 0
}
if _, ok := seenFns[fn]; ok {
// break recursion
return 0
}
seenFns[fn] = struct{}{}
defer func() {
for i := 0; i < fn.Signature.Results().Len(); i++ {
if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); !ok {
// we don't need facts to know that non-interface
// types can't be untyped nil. zeroing out those bits
// may result in all bits being zero, in which case we
// don't have to save any fact.
out &= ^(1 << i)
}
}
if out > 0 {
pass.ExportObjectFact(fn.Object(), &alwaysTypedFact{out})
}
}()
isUntypedNil := func(v ir.Value) bool {
k, ok := v.(*ir.Const)
if !ok {
return false
}
if _, ok := k.Type().Underlying().(*types.Interface); !ok {
return false
}
return k.Value == nil
}
var do func(v ir.Value, seen map[ir.Value]struct{}) bool
do = func(v ir.Value, seen map[ir.Value]struct{}) bool {
if _, ok := seen[v]; ok {
// break cycle
return false
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.Const:
// can't be a typed nil, because then we'd be returning the
// result of MakeInterface.
return false
case *ir.ChangeInterface:
return do(v.X, seen)
case *ir.Extract:
call, ok := v.Tuple.(*ir.Call)
if !ok {
// We only care about extracts of function results. For
// everything else (e.g. channel receives and map
// lookups), we can either not deduce any information, or
// will see a MakeInterface.
return false
}
if callee := call.Call.StaticCallee(); callee != nil {
return impl(pass, callee, seenFns)&(1<interface conversions, which
// don't tell us anything about the nilness.
return false
case *ir.MapLookup, *ir.Index, *ir.Recv, *ir.Parameter, *ir.Load, *ir.Field:
// All other instructions that tell us nothing about the
// typedness of interface values.
return false
default:
panic(fmt.Sprintf("internal error: unhandled type %T", v))
}
}
ret := fn.Exit.Control().(*ir.Return)
for i, v := range ret.Results {
typ := fn.Signature.Results().At(i).Type()
if _, ok := typ.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(typ) {
if do(v, map[ir.Value]struct{}{}) {
out |= 1 << i
}
}
}
return out
}
================================================
FILE: analysis/facts/typedness/typedness_test.go
================================================
package typedness
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestTypedness(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), Analysis, "example.com/Typedness")
}
================================================
FILE: analysis/lint/lint.go
================================================
// Package lint provides abstractions on top of go/analysis.
// These abstractions add extra information to analyzes, such as structured documentation and severities.
package lint
import (
"fmt"
"go/ast"
"go/token"
"strings"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/facts/tokenfile"
)
// Analyzer wraps a go/analysis.Analyzer and provides structured documentation.
type Analyzer struct {
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
// this field is structured, providing access to severity, options
// etc.
Doc *RawDocumentation
Analyzer *analysis.Analyzer
}
func InitializeAnalyzer(a *Analyzer) *Analyzer {
a.Analyzer.Doc = a.Doc.Compile().String()
a.Analyzer.URL = "https://staticcheck.dev/docs/checks/#" + a.Analyzer.Name
a.Analyzer.Requires = append(a.Analyzer.Requires, tokenfile.Analyzer)
return a
}
// Severity describes the severity of diagnostics reported by an analyzer.
type Severity int
const (
SeverityNone Severity = iota
SeverityError
SeverityDeprecated
SeverityWarning
SeverityInfo
SeverityHint
)
// MergeStrategy sets how merge mode should behave for diagnostics of an analyzer.
type MergeStrategy int
const (
MergeIfAny MergeStrategy = iota
MergeIfAll
)
type RawDocumentation struct {
Title string
Text string
Before string
After string
Since string
NonDefault bool
Options []string
Severity Severity
MergeIf MergeStrategy
}
type Documentation struct {
Title string
Text string
TitleMarkdown string
TextMarkdown string
Before string
After string
Since string
NonDefault bool
Options []string
Severity Severity
MergeIf MergeStrategy
}
func (doc RawDocumentation) Compile() *Documentation {
return &Documentation{
Title: strings.TrimSpace(stripMarkdown(doc.Title)),
Text: strings.TrimSpace(stripMarkdown(doc.Text)),
TitleMarkdown: strings.TrimSpace(toMarkdown(doc.Title)),
TextMarkdown: strings.TrimSpace(toMarkdown(doc.Text)),
Before: strings.TrimSpace(doc.Before),
After: strings.TrimSpace(doc.After),
Since: doc.Since,
NonDefault: doc.NonDefault,
Options: doc.Options,
Severity: doc.Severity,
MergeIf: doc.MergeIf,
}
}
func toMarkdown(s string) string {
return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s)
}
func stripMarkdown(s string) string {
return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s)
}
func (doc *Documentation) Format(metadata bool) string {
return doc.format(false, metadata)
}
func (doc *Documentation) FormatMarkdown(metadata bool) string {
return doc.format(true, metadata)
}
func (doc *Documentation) format(markdown bool, metadata bool) string {
b := &strings.Builder{}
if markdown {
fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown)
}
} else {
fmt.Fprintf(b, "%s\n\n", doc.Title)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.Text)
}
}
if doc.Before != "" {
fmt.Fprintln(b, "Before:")
fmt.Fprintln(b, "")
for line := range strings.SplitSeq(doc.Before, "\n") {
fmt.Fprint(b, " ", line, "\n")
}
fmt.Fprintln(b, "")
fmt.Fprintln(b, "After:")
fmt.Fprintln(b, "")
for line := range strings.SplitSeq(doc.After, "\n") {
fmt.Fprint(b, " ", line, "\n")
}
fmt.Fprintln(b, "")
}
if metadata {
fmt.Fprint(b, "Available since\n ")
if doc.Since == "" {
fmt.Fprint(b, "unreleased")
} else {
fmt.Fprintf(b, "%s", doc.Since)
}
if doc.NonDefault {
fmt.Fprint(b, ", non-default")
}
fmt.Fprint(b, "\n")
if len(doc.Options) > 0 {
fmt.Fprintf(b, "\nOptions\n")
for _, opt := range doc.Options {
fmt.Fprintf(b, " %s", opt)
}
fmt.Fprint(b, "\n")
}
}
return b.String()
}
func (doc *Documentation) String() string {
return doc.Format(true)
}
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
// that type switches are exhaustive.
func ExhaustiveTypeSwitch(v any) {
panic(fmt.Sprintf("internal error: unhandled case %T", v))
}
// A directive is a comment of the form '//lint:
// [arguments...]'. It represents instructions to the static analysis
// tool.
type Directive struct {
Command string
Arguments []string
Directive *ast.Comment
Node ast.Node
}
func parseDirective(s string) (cmd string, args []string) {
if !strings.HasPrefix(s, "//lint:") {
return "", nil
}
s = strings.TrimPrefix(s, "//lint:")
fields := strings.Split(s, " ")
return fields[0], fields[1:]
}
// ParseDirectives extracts all directives from a list of Go files.
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
var dirs []Directive
for _, f := range files {
// OPT(dh): in our old code, we skip all the comment map work if we
// couldn't find any directives, benchmark if that's actually
// worth doing
cm := ast.NewCommentMap(fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
for _, c := range cg.List {
if !strings.HasPrefix(c.Text, "//lint:") {
continue
}
cmd, args := parseDirective(c.Text)
d := Directive{
Command: cmd,
Arguments: args,
Directive: c,
Node: node,
}
dirs = append(dirs, d)
}
}
}
}
return dirs
}
================================================
FILE: analysis/lint/testutil/check.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is a modified copy of x/tools/go/analysis/analysistest/analysistest.go
package testutil
import (
"bytes"
"fmt"
"go/format"
"go/token"
"os"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"testing"
"honnef.co/go/tools/internal/diff/myers"
"honnef.co/go/tools/lintcmd/runner"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/txtar"
)
func CheckSuggestedFixes(t *testing.T, diagnostics []runner.Diagnostic) {
// Process each result (package) separately, matching up the suggested
// fixes into a diff, which we will compare to the .golden file. We have
// to do this per-result in case a file appears in two packages, such as in
// packages with tests, where mypkg/a.go will appear in both mypkg and
// mypkg.test. In that case, the analyzer may suggest the same set of
// changes to a.go for each package. If we merge all the results, those
// changes get doubly applied, which will cause conflicts or mismatches.
// Validating the results separately means as long as the two analyses
// don't produce conflicting suggestions for a single file, everything
// should match up.
// file -> message -> edits
fileEdits := make(map[string]map[string][]runner.TextEdit)
fileContents := make(map[string][]byte)
// Validate edits, prepare the fileEdits map and read the file contents.
for _, diag := range diagnostics {
for _, sf := range diag.SuggestedFixes {
for _, edit := range sf.TextEdits {
// Validate the edit.
if edit.Position.Offset > edit.End.Offset {
t.Errorf(
"diagnostic for analysis %v contains Suggested Fix with malformed edit: pos (%v) > end (%v)",
diag.Category, edit.Position.Offset, edit.End.Offset)
continue
}
if edit.Position.Filename != edit.End.Filename {
t.Errorf(
"diagnostic for analysis %v contains Suggested Fix with malformed edit spanning files %v and %v",
diag.Category, edit.Position.Filename, edit.End.Filename)
continue
}
if _, ok := fileContents[edit.Position.Filename]; !ok {
contents, err := os.ReadFile(edit.Position.Filename)
if err != nil {
t.Errorf("error reading %s: %v", edit.Position.Filename, err)
}
fileContents[edit.Position.Filename] = contents
}
if _, ok := fileEdits[edit.Position.Filename]; !ok {
fileEdits[edit.Position.Filename] = make(map[string][]runner.TextEdit)
}
fileEdits[edit.Position.Filename][sf.Message] = append(fileEdits[edit.Position.Filename][sf.Message], edit)
}
}
}
for file, fixes := range fileEdits {
// Get the original file contents.
orig, ok := fileContents[file]
if !ok {
t.Errorf("could not find file contents for %s", file)
continue
}
// Get the golden file and read the contents.
ar, err := txtar.ParseFile(file + ".golden")
if err != nil {
t.Errorf("error reading %s.golden: %v", file, err)
continue
}
if len(ar.Files) > 0 {
// one virtual file per kind of suggested fix
if len(ar.Comment) != 0 {
// we allow either just the comment, or just virtual
// files, not both. it is not clear how "both" should
// behave.
t.Errorf("%s.golden has leading comment; we don't know what to do with it", file)
continue
}
var sfs []string
for sf := range fixes {
sfs = append(sfs, sf)
}
slices.Sort(sfs)
for _, sf := range sfs {
edits := fixes[sf]
found := false
for _, vf := range ar.Files {
if vf.Name == sf {
found = true
out := applyEdits(orig, edits)
// the file may contain multiple trailing
// newlines if the user places empty lines
// between files in the archive. normalize
// this to a single newline.
want := string(bytes.TrimRight(vf.Data, "\n")) + "\n"
formatted, err := format.Source([]byte(out))
if err != nil {
t.Errorf("%s: error formatting edited source: %v\n%s", file, err, out)
continue
}
if want != string(formatted) {
d := myers.ComputeEdits(want, string(formatted))
var diff strings.Builder
for _, op := range d {
diff.WriteString(op.String())
}
t.Errorf("suggested fixes failed for %s[%s]:\n%s", file, sf, diff.String())
}
break
}
}
if !found {
t.Errorf("no section for suggested fix %q in %s.golden", sf, file)
}
}
for _, vf := range ar.Files {
if _, ok := fixes[vf.Name]; !ok {
t.Errorf("%s.golden has section for suggested fix %q, but we didn't produce any fix by that name", file, vf.Name)
}
}
} else {
// all suggested fixes are represented by a single file
var catchallEdits []runner.TextEdit
for _, edits := range fixes {
catchallEdits = append(catchallEdits, edits...)
}
out := applyEdits(orig, catchallEdits)
want := string(ar.Comment)
formatted, err := format.Source([]byte(out))
if err != nil {
t.Errorf("%s: error formatting resulting source: %v\n%s", file, err, out)
continue
}
if want != string(formatted) {
d := myers.ComputeEdits(want, string(formatted))
var diff strings.Builder
for _, op := range d {
diff.WriteString(op.String())
}
t.Errorf("suggested fixes failed for %s:\n%s", file, diff.String())
}
}
}
}
func Check(t *testing.T, gopath string, files []string, diagnostics []runner.Diagnostic, facts []runner.TestFact) {
relativePath := func(path string) string {
cwd, err := os.Getwd()
if err != nil {
return path
}
rel, err := filepath.Rel(cwd, path)
if err != nil {
return path
}
return rel
}
type key struct {
file string
line int
}
// the 'files' argument contains a list of all files that were part of the tested package
want := make(map[key][]*expect.Note)
fset := token.NewFileSet()
seen := map[string]struct{}{}
for _, file := range files {
seen[file] = struct{}{}
notes, err := expect.Parse(fset, file, nil)
if err != nil {
t.Fatal(err)
}
for _, note := range notes {
k := key{
file: file,
line: fset.PositionFor(note.Pos, false).Line,
}
want[k] = append(want[k], note)
}
}
for _, diag := range diagnostics {
file := diag.Position.Filename
if _, ok := seen[file]; !ok {
t.Errorf("got diagnostic in file %q, but that file isn't part of the checked package", relativePath(file))
return
}
}
check := func(posn token.Position, message string, kind string, argIdx int, identifier string) {
k := key{posn.Filename, posn.Line}
expects := want[k]
var unmatched []string
for i, exp := range expects {
if exp.Name == kind {
if kind == "fact" && exp.Args[0] != expect.Identifier(identifier) {
continue
}
matched := false
switch arg := exp.Args[argIdx].(type) {
case string:
matched = strings.Contains(message, arg)
case *regexp.Regexp:
matched = arg.MatchString(message)
default:
t.Fatalf("unexpected argument type %T", arg)
}
if matched {
// matched: remove the expectation.
expects[i] = expects[len(expects)-1]
expects = expects[:len(expects)-1]
want[k] = expects
return
}
unmatched = append(unmatched, fmt.Sprintf("%q", exp.Args[argIdx]))
}
}
if unmatched == nil {
posn.Filename = relativePath(posn.Filename)
t.Errorf("%v: unexpected diag: %v", posn, message)
} else {
posn.Filename = relativePath(posn.Filename)
t.Errorf("%v: diag %q does not match pattern %s",
posn, message, strings.Join(unmatched, " or "))
}
}
checkDiag := func(posn token.Position, message string) {
check(posn, message, "diag", 0, "")
}
checkFact := func(posn token.Position, name, message string) {
check(posn, message, "fact", 1, name)
}
// Check the diagnostics match expectations.
for _, f := range diagnostics {
// TODO(matloob): Support ranges in analysistest.
posn := f.Position
checkDiag(posn, f.Message)
}
// Check the facts match expectations.
for _, fact := range facts {
name := fact.ObjectName
posn := fact.Position
if name == "" {
name = "package"
posn.Line = 1
}
checkFact(posn, name, fact.FactString)
}
// Reject surplus expectations.
//
// Sometimes an Analyzer reports two similar diagnostics on a
// line with only one expectation. The reader may be confused by
// the error message.
// TODO(adonovan): print a better error:
// "got 2 diagnostics here; each one needs its own expectation".
var surplus []string
for key, expects := range want {
for _, exp := range expects {
surplus = append(surplus, fmt.Sprintf("%s:%d: no %s was reported matching %q", relativePath(key.file), key.line, exp.Name, exp.Args))
}
}
sort.Strings(surplus)
for _, err := range surplus {
t.Errorf("%s", err)
}
}
func applyEdits(src []byte, edits []runner.TextEdit) []byte {
// This function isn't efficient, but it doesn't have to be.
edits = slices.Clone(edits)
sort.Slice(edits, func(i, j int) bool {
if edits[i].Position.Offset < edits[j].Position.Offset {
return true
}
if edits[i].Position.Offset == edits[j].Position.Offset {
return edits[i].End.Offset < edits[j].End.Offset
}
return false
})
out := bytes.Clone(src)
offset := 0
for _, edit := range edits {
start := edit.Position.Offset + offset
end := edit.End.Offset + offset
if edit.End == (token.Position{}) {
end = -1
}
if len(edit.NewText) == 0 {
// pure deletion
copy(out[start:], out[end:])
out = out[:len(out)-(end-start)]
offset -= end - start
} else if end == -1 || end == start {
// pure insertion
tmp := make([]byte, len(out)+len(edit.NewText))
copy(tmp, out[:start])
copy(tmp[start:], edit.NewText)
copy(tmp[start+len(edit.NewText):], out[start:])
offset += len(edit.NewText)
out = tmp
} else if end-start == len(edit.NewText) {
// exact replacement
copy(out[start:], edit.NewText)
} else if end-start < len(edit.NewText) {
// replace with longer string
growth := len(edit.NewText) - (end - start)
tmp := make([]byte, len(out)+growth)
copy(tmp, out[:start])
copy(tmp[start:], edit.NewText)
copy(tmp[start+len(edit.NewText):], out[end:])
offset += growth
out = tmp
} else if end-start > len(edit.NewText) {
// replace with shorter string
shrinkage := (end - start) - len(edit.NewText)
copy(out[start:], edit.NewText)
copy(out[start+len(edit.NewText):], out[end:])
out = out[:len(out)-shrinkage]
offset -= shrinkage
}
}
// Debug code
if false {
fmt.Println("input:")
fmt.Println(string(src))
fmt.Println()
fmt.Println("edits:")
for _, edit := range edits {
fmt.Printf("%d:%d - %d:%d <- %q\n", edit.Position.Line, edit.Position.Column, edit.End.Line, edit.End.Column, edit.NewText)
}
fmt.Println("output:")
fmt.Println(string(out))
panic("")
}
return out
}
================================================
FILE: analysis/lint/testutil/util.go
================================================
package testutil
import (
"crypto/sha256"
"go/build"
"go/version"
"io"
"os"
"path/filepath"
"strings"
"testing"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/config"
"honnef.co/go/tools/go/buildid"
"honnef.co/go/tools/lintcmd/cache"
"honnef.co/go/tools/lintcmd/runner"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
)
type Test struct {
Dir string
Version string
}
func computeSalt() ([]byte, error) {
p, err := os.Executable()
if err != nil {
return nil, err
}
if id, err := buildid.ReadFile(p); err == nil {
return []byte(id), nil
} else {
// For some reason we couldn't read the build id from the executable.
// Fall back to hashing the entire executable.
f, err := os.Open(p)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
}
func Run(t *testing.T, a *lint.Analyzer) {
dirs, err := filepath.Glob("testdata/*")
if err != nil {
t.Fatalf("couldn't enumerate test data: %s", err)
}
if len(dirs) == 0 {
t.Fatalf("found no tests")
}
c, err := cache.Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
salt, err := computeSalt()
if err != nil {
t.Fatal(err)
}
c.SetSalt(salt)
tags := build.Default.ReleaseTags
maxVersion := tags[len(tags)-1]
for _, dir := range dirs {
vers := filepath.Base(dir)
t.Run(vers, func(t *testing.T) {
if !version.IsValid(vers) {
t.Fatalf("%q is not a valid Go version", dir)
}
if version.Compare(vers, maxVersion) == 1 {
t.Skipf("%s is newer than our Go version (%s), skipping", vers, maxVersion)
}
r, err := runner.New(config.Config{}, c)
if err != nil {
t.Fatal(err)
}
r.TestMode = true
testdata, err := filepath.Abs("testdata")
if err != nil {
t.Fatal(err)
}
cfg := &packages.Config{
Dir: dir,
Tests: true,
Env: append(os.Environ(), "GOPROXY=off", "GOFLAGS=-mod=vendor", "GO111MODULE="),
Overlay: map[string][]byte{
"go.mod": []byte("module example.com\ngo " + strings.TrimPrefix(vers, "go")),
},
}
res, err := r.Run(cfg, []*analysis.Analyzer{a.Analyzer}, []string{"./..."})
if err != nil {
t.Fatal(err)
}
if len(res) == 0 {
t.Fatalf("got no results for %s/...", dir)
}
for _, r := range res {
if r.Failed {
if len(r.Errors) > 0 {
sb := strings.Builder{}
for _, err := range r.Errors {
sb.WriteString(err.Error())
sb.WriteString("\n")
}
t.Fatalf("failed checking %s:\n%s", r.Package.PkgPath, sb.String())
} else {
t.Fatalf("failed processing package %s, but got no errors", r.Package.PkgPath)
}
}
data, err := r.Load()
if err != nil {
t.Fatal(err)
}
tdata, err := r.LoadTest()
if err != nil {
t.Fatal(err)
}
relevantDiags := data.Diagnostics
var relevantFacts []runner.TestFact
for _, fact := range tdata.Facts {
if fact.Analyzer != a.Analyzer.Name {
continue
}
relevantFacts = append(relevantFacts, fact)
}
Check(t, testdata, tdata.Files, relevantDiags, relevantFacts)
CheckSuggestedFixes(t, relevantDiags)
}
})
}
}
================================================
FILE: analysis/report/report.go
================================================
package report
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/version"
"path/filepath"
"strconv"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
)
type Options struct {
ShortRange bool
FilterGenerated bool
Fixes []analysis.SuggestedFix
Related []analysis.RelatedInformation
MinimumLanguageVersion string
MaximumLanguageVersion string
MinimumStdlibVersion string
MaximumStdlibVersion string
}
type Option func(*Options)
func ShortRange() Option {
return func(opts *Options) {
opts.ShortRange = true
}
}
func FilterGenerated() Option {
return func(opts *Options) {
opts.FilterGenerated = true
}
}
func Fixes(fixes ...analysis.SuggestedFix) Option {
return func(opts *Options) {
opts.Fixes = append(opts.Fixes, fixes...)
}
}
func Related(node Positioner, message string) Option {
return func(opts *Options) {
pos, end, ok := getRange(node, opts.ShortRange)
if !ok {
return
}
r := analysis.RelatedInformation{
Pos: pos,
End: end,
Message: message,
}
opts.Related = append(opts.Related, r)
}
}
func MinimumLanguageVersion(vers string) Option {
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
}
func MaximumLanguageVersion(vers string) Option {
return func(opts *Options) { opts.MinimumLanguageVersion = vers }
}
func MinimumStdlibVersion(vers string) Option {
return func(opts *Options) { opts.MinimumStdlibVersion = vers }
}
func MaximumStdlibVersion(vers string) Option {
return func(opts *Options) { opts.MaximumStdlibVersion = vers }
}
type Positioner interface {
Pos() token.Pos
}
type fullPositioner interface {
Pos() token.Pos
End() token.Pos
}
type sourcer interface {
Source() ast.Node
}
// shortRange returns the position and end of the main component of an
// AST node. For nodes that have no body, the short range is identical
// to the node's Pos and End. For nodes that do have a body, the short
// range excludes the body.
func shortRange(node ast.Node) (pos, end token.Pos) {
switch node := node.(type) {
case *ast.File:
return node.Pos(), node.Name.End()
case *ast.CaseClause:
return node.Pos(), node.Colon + 1
case *ast.CommClause:
return node.Pos(), node.Colon + 1
case *ast.DeferStmt:
return node.Pos(), node.Defer + token.Pos(len("defer"))
case *ast.ExprStmt:
return shortRange(node.X)
case *ast.ForStmt:
if node.Post != nil {
return node.For, node.Post.End()
} else if node.Cond != nil {
return node.For, node.Cond.End()
} else if node.Init != nil {
// +1 to catch the semicolon, for gofmt'ed code
return node.Pos(), node.Init.End() + 1
} else {
return node.Pos(), node.For + token.Pos(len("for"))
}
case *ast.FuncDecl:
return node.Pos(), node.Type.End()
case *ast.FuncLit:
return node.Pos(), node.Type.End()
case *ast.GoStmt:
if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
return node.Pos(), node.Go + token.Pos(len("go"))
} else {
return node.Pos(), node.End()
}
case *ast.IfStmt:
return node.Pos(), node.Cond.End()
case *ast.RangeStmt:
return node.Pos(), node.X.End()
case *ast.SelectStmt:
return node.Pos(), node.Pos() + token.Pos(len("select"))
case *ast.SwitchStmt:
if node.Tag != nil {
return node.Pos(), node.Tag.End()
} else if node.Init != nil {
// +1 to catch the semicolon, for gofmt'ed code
return node.Pos(), node.Init.End() + 1
} else {
return node.Pos(), node.Pos() + token.Pos(len("switch"))
}
case *ast.TypeSwitchStmt:
return node.Pos(), node.Assign.End()
default:
return node.Pos(), node.End()
}
}
func HasRange(node Positioner) bool {
// we don't know if getRange will be called with shortRange set to
// true, so make sure that both work.
_, _, ok := getRange(node, false)
if !ok {
return false
}
_, _, ok = getRange(node, true)
return ok
}
func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) {
switch n := node.(type) {
case sourcer:
s := n.Source()
if s == nil {
return 0, 0, false
}
if short {
p, e := shortRange(s)
return p, e, true
}
return s.Pos(), s.End(), true
case fullPositioner:
if short {
p, e := shortRange(n)
return p, e, true
}
return n.Pos(), n.End(), true
default:
return n.Pos(), token.NoPos, true
}
}
func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
cfg := &Options{}
for _, opt := range opts {
opt(cfg)
}
langVersion := code.LanguageVersion(pass, node)
stdlibVersion := code.StdlibVersion(pass, node)
if n := cfg.MaximumLanguageVersion; n != "" && version.Compare(n, langVersion) == -1 {
return
}
if n := cfg.MaximumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == -1 {
return
}
if n := cfg.MinimumLanguageVersion; n != "" && version.Compare(n, langVersion) == 1 {
return
}
if n := cfg.MinimumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == 1 {
return
}
file := DisplayPosition(pass.Fset, node.Pos()).Filename
if cfg.FilterGenerated {
m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
if _, ok := m[file]; ok {
return
}
}
pos, end, ok := getRange(node, cfg.ShortRange)
if !ok {
panic(fmt.Sprintf("no valid position for reporting node %v", node))
}
d := analysis.Diagnostic{
Pos: pos,
End: end,
Message: message,
SuggestedFixes: cfg.Fixes,
Related: cfg.Related,
}
pass.Report(d)
}
func Render(pass *analysis.Pass, x any) string {
var buf bytes.Buffer
if err := format.Node(&buf, pass.Fset, x); err != nil {
panic(err)
}
return buf.String()
}
func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
var ss []string
for _, arg := range args {
ss = append(ss, Render(pass, arg))
}
return strings.Join(ss, ", ")
}
func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
if p == token.NoPos {
return token.Position{}
}
// Only use the adjusted position if it points to another Go file.
// This means we'll point to the original file for cgo files, but
// we won't point to a YACC grammar file.
pos := fset.PositionFor(p, false)
adjPos := fset.PositionFor(p, true)
if filepath.Ext(adjPos.Filename) == ".go" {
return adjPos
}
return pos
}
func Ordinal(n int) string {
suffix := "th"
if n < 10 || n > 20 {
switch n % 10 {
case 0:
suffix = "th"
case 1:
suffix = "st"
case 2:
suffix = "nd"
case 3:
suffix = "rd"
default:
suffix = "th"
}
}
return strconv.Itoa(n) + suffix
}
================================================
FILE: analysis/report/report_test.go
================================================
package report
import "testing"
func TestOrdinal(t *testing.T) {
tests := []struct {
num int
want string
}{
{0, "0th"}, {1, "1st"}, {2, "2nd"}, {3, "3rd"}, {4, "4th"}, {5, "5th"}, {6, "6th"}, {7, "7th"}, {8, "8th"}, {9, "9th"},
{10, "10th"}, {11, "11th"}, {12, "12th"}, {13, "13th"}, {14, "14th"}, {15, "15th"}, {16, "16th"}, {17, "17th"}, {18, "18th"}, {19, "19th"},
{20, "20th"}, {21, "21st"}, {22, "22nd"}, {23, "23rd"}, {24, "24th"}, {25, "25th"}, {26, "26th"}, {27, "27th"}, {28, "28th"}, {29, "29th"},
}
for _, tt := range tests {
if got := Ordinal(tt.num); got != tt.want {
t.Errorf("Ordinal(%d) = %s, want %s", tt.num, got, tt.want)
}
}
}
================================================
FILE: cmd/staticcheck/README.md
================================================
# staticcheck
_staticcheck_ offers extensive analysis of Go code, covering a myriad
of categories. It will detect bugs, suggest code simplifications,
point out dead code, and more.
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Documentation
Detailed documentation can be found on
[staticcheck.dev](https://staticcheck.dev/docs/).
================================================
FILE: cmd/staticcheck/staticcheck.go
================================================
// staticcheck analyses Go code and makes it better.
package main
import (
"log"
"os"
"honnef.co/go/tools/lintcmd"
"honnef.co/go/tools/lintcmd/version"
"honnef.co/go/tools/quickfix"
"honnef.co/go/tools/simple"
"honnef.co/go/tools/staticcheck"
"honnef.co/go/tools/stylecheck"
"honnef.co/go/tools/unused"
)
func main() {
cmd := lintcmd.NewCommand("staticcheck")
cmd.SetVersion(version.Version, version.MachineVersion)
fs := cmd.FlagSet()
debug := fs.String("debug.unused-graph", "", "Write unused's object graph to `file`")
qf := fs.Bool("debug.run-quickfix-analyzers", false, "Run quickfix analyzers")
cmd.ParseFlags(os.Args[1:])
cmd.AddAnalyzers(simple.Analyzers...)
cmd.AddAnalyzers(staticcheck.Analyzers...)
cmd.AddAnalyzers(stylecheck.Analyzers...)
cmd.AddAnalyzers(unused.Analyzer)
if *qf {
cmd.AddAnalyzers(quickfix.Analyzers...)
}
if *debug != "" {
f, err := os.OpenFile(*debug, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
unused.Debug = f
}
cmd.Run()
}
================================================
FILE: cmd/structlayout/README.md
================================================
# structlayout
The _structlayout_ utility prints the layout of a struct – that is the
byte offset and size of each field, respecting alignment/padding.
The information is printed in human-readable form by default, but can
be emitted as JSON with the `-json` flag. This makes it easy to
consume this information in other tools.
A utility called _structlayout-pretty_ takes this JSON and prints an
ASCII graphic representing the memory layout.
_structlayout-optimize_ is another tool. Inspired by
[maligned](https://github.com/mdempsky/maligned), it reads
_structlayout_ JSON on stdin and reorders fields to minimize the
amount of padding. The tool can itself emit JSON and feed into e.g.
_structlayout-pretty_.
_structlayout-svg_ is a third-party tool that, similarly to
_structlayout-pretty_, visualises struct layouts. It does so by
generating a fancy-looking SVG graphic. You can install it via
```
go get github.com/ajstarks/svgo/structlayout-svg
```
## Installation
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
## Examples
```
$ structlayout bufio Reader
Reader.buf []byte: 0-24 (24 bytes)
Reader.rd io.Reader: 24-40 (16 bytes)
Reader.r int: 40-48 (8 bytes)
Reader.w int: 48-56 (8 bytes)
Reader.err error: 56-72 (16 bytes)
Reader.lastByte int: 72-80 (8 bytes)
Reader.lastRuneSize int: 80-88 (8 bytes)
```
```
$ structlayout -json bufio Reader | jq .
[
{
"name": "Reader.buf",
"type": "[]byte",
"start": 0,
"end": 24,
"size": 24,
"is_padding": false
},
{
"name": "Reader.rd",
"type": "io.Reader",
"start": 24,
"end": 40,
"size": 16,
"is_padding": false
},
{
"name": "Reader.r",
"type": "int",
"start": 40,
"end": 48,
"size": 8,
"is_padding": false
},
...
```
```
$ structlayout -json bufio Reader | structlayout-pretty
+--------+
0 | | <- Reader.buf []byte
+--------+
-........-
+--------+
23 | |
+--------+
24 | | <- Reader.rd io.Reader
+--------+
-........-
+--------+
39 | |
+--------+
40 | | <- Reader.r int
+--------+
-........-
+--------+
47 | |
+--------+
48 | | <- Reader.w int
+--------+
-........-
+--------+
55 | |
+--------+
56 | | <- Reader.err error
+--------+
-........-
+--------+
71 | |
+--------+
72 | | <- Reader.lastByte int
+--------+
-........-
+--------+
79 | |
+--------+
80 | | <- Reader.lastRuneSize int
+--------+
-........-
+--------+
87 | |
+--------+
```
```
$ structlayout -json bytes Buffer | structlayout-svg -t "bytes.Buffer" > /tmp/struct.svg
```

================================================
FILE: cmd/structlayout/main.go
================================================
// structlayout displays the layout (field sizes and padding) of structs.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/build"
"go/types"
"log"
"os"
"honnef.co/go/tools/go/gcsizes"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
"golang.org/x/tools/go/packages"
)
var (
fJSON bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
if len(flag.Args()) != 2 {
flag.Usage()
os.Exit(1)
}
cfg := &packages.Config{
Mode: packages.NeedImports | packages.NeedExportFile | packages.NeedTypes | packages.NeedSyntax,
Tests: true,
}
pkgs, err := packages.Load(cfg, flag.Args()[0])
if err != nil {
log.Fatal(err)
}
for _, pkg := range pkgs {
typName := flag.Args()[1]
var typ types.Type
obj := pkg.Types.Scope().Lookup(typName)
if obj == nil {
continue
}
typ = obj.Type()
st, ok := typ.Underlying().(*types.Struct)
if !ok {
log.Fatal("identifier is not a struct type")
}
fields := sizes(st, types.Unalias(typ).(*types.Named).Obj().Name(), 0, nil)
if fJSON {
emitJSON(fields)
} else {
emitText(fields)
}
return
}
log.Fatal("couldn't find type")
}
func emitJSON(fields []st.Field) {
if fields == nil {
fields = []st.Field{}
}
json.NewEncoder(os.Stdout).Encode(fields)
}
func emitText(fields []st.Field) {
for _, field := range fields {
fmt.Println(field)
}
}
func sizes(typ *types.Struct, prefix string, base int64, out []st.Field) []st.Field {
s := gcsizes.ForArch(build.Default.GOARCH)
n := typ.NumFields()
var fields []*types.Var
for i := range n {
fields = append(fields, typ.Field(i))
}
offsets := s.Offsetsof(fields)
for i := range offsets {
offsets[i] += base
}
pos := base
for i, field := range fields {
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
size := s.Sizeof(field.Type())
if typ2, ok := field.Type().Underlying().(*types.Struct); ok && typ2.NumFields() != 0 {
out = sizes(typ2, prefix+"."+field.Name(), pos, out)
} else {
out = append(out, st.Field{
Name: prefix + "." + field.Name(),
Type: field.Type().String(),
Start: offsets[i],
End: offsets[i] + size,
Size: size,
Align: s.Alignof(field.Type()),
})
}
pos += size
}
if len(out) == 0 {
return out
}
field := &out[len(out)-1]
if field.Size == 0 {
field.Size = 1
field.End++
}
pad := s.Sizeof(typ) - field.End
if pad > 0 {
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}
================================================
FILE: cmd/structlayout-optimize/main.go
================================================
// structlayout-optimize reorders struct fields to minimize the amount
// of padding.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
)
var (
fJSON bool
fRecurse bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fRecurse, "r", false, "Break up structs and reorder their fields freely")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
var in []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
log.Fatal(err)
}
if len(in) == 0 {
return
}
if !fRecurse {
in = combine(in)
}
var fields []st.Field
for _, field := range in {
if field.IsPadding {
continue
}
fields = append(fields, field)
}
optimize(fields)
fields = pad(fields)
if fJSON {
json.NewEncoder(os.Stdout).Encode(fields)
} else {
for _, field := range fields {
fmt.Println(field)
}
}
}
func combine(fields []st.Field) []st.Field {
new := st.Field{}
cur := ""
var out []st.Field
wasPad := true
for _, field := range fields {
var prefix string
if field.IsPadding {
wasPad = true
continue
}
p := strings.Split(field.Name, ".")
prefix = strings.Join(p[:2], ".")
if field.Align > new.Align {
new.Align = field.Align
}
if !wasPad {
new.End = field.Start
new.Size = new.End - new.Start
}
if prefix != cur {
if cur != "" {
out = append(out, new)
}
cur = prefix
new = field
new.Name = prefix
} else {
new.Type = "struct"
}
wasPad = false
}
new.Size = new.End - new.Start
out = append(out, new)
return out
}
func optimize(fields []st.Field) {
sort.Sort(&byAlignAndSize{fields})
}
func pad(fields []st.Field) []st.Field {
if len(fields) == 0 {
return nil
}
var out []st.Field
pos := int64(0)
offsets := offsetsof(fields)
alignment := int64(1)
for i, field := range fields {
if field.Align > alignment {
alignment = field.Align
}
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
field.Start = pos
field.End = pos + field.Size
out = append(out, field)
pos += field.Size
}
sz := size(out)
pad := align(sz, alignment) - sz
if pad > 0 {
field := out[len(out)-1]
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}
func size(fields []st.Field) int64 {
n := int64(0)
for _, field := range fields {
n += field.Size
}
return n
}
type byAlignAndSize struct {
fields []st.Field
}
func (s *byAlignAndSize) Len() int { return len(s.fields) }
func (s *byAlignAndSize) Swap(i, j int) {
s.fields[i], s.fields[j] = s.fields[j], s.fields[i]
}
func (s *byAlignAndSize) Less(i, j int) bool {
// Place zero sized objects before non-zero sized objects.
if s.fields[i].Size == 0 && s.fields[j].Size != 0 {
return true
}
if s.fields[j].Size == 0 && s.fields[i].Size != 0 {
return false
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if s.fields[i].Align != s.fields[j].Align {
return s.fields[i].Align > s.fields[j].Align
}
// Lastly, order by size.
if s.fields[i].Size != s.fields[j].Size {
return s.fields[i].Size > s.fields[j].Size
}
return false
}
func offsetsof(fields []st.Field) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := f.Align
o = align(o, a)
offsets[i] = o
o += f.Size
}
return offsets
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
================================================
FILE: cmd/structlayout-pretty/main.go
================================================
// structlayout-pretty formats the output of structlayout with ASCII
// art.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strings"
"honnef.co/go/tools/lintcmd/version"
st "honnef.co/go/tools/structlayout"
)
var (
fVerbose bool
fVersion bool
)
func init() {
flag.BoolVar(&fVerbose, "v", false, "Do not compact consecutive bytes of fields")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print(version.Version, version.MachineVersion)
os.Exit(0)
}
var fields []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil {
log.Fatal(err)
}
if len(fields) == 0 {
return
}
max := fields[len(fields)-1].End
maxLength := len(fmt.Sprintf("%d", max))
padding := strings.Repeat(" ", maxLength+2)
format := fmt.Sprintf(" %%%dd ", maxLength)
pos := int64(0)
fmt.Println(padding + "+--------+")
for _, field := range fields {
name := field.Name + " " + field.Type
if field.IsPadding {
name = "padding"
}
fmt.Printf(format+"| | <- %s (size %d, align %d)\n", pos, name, field.Size, field.Align)
fmt.Println(padding + "+--------+")
if fVerbose {
for i := int64(0); i < field.Size-1; i++ {
fmt.Printf(format+"| |\n", pos+i+1)
fmt.Println(padding + "+--------+")
}
} else {
if field.Size > 2 {
fmt.Println(padding + "-........-")
fmt.Println(padding + "+--------+")
fmt.Printf(format+"| |\n", pos+field.Size-1)
fmt.Println(padding + "+--------+")
}
}
pos += field.Size
}
}
================================================
FILE: config/config.go
================================================
package config
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/BurntSushi/toml"
"golang.org/x/tools/go/analysis"
)
// Dir looks at a list of absolute file names, which should make up a
// single package, and returns the path of the directory that may
// contain a staticcheck.conf file. It returns the empty string if no
// such directory could be determined, for example because all files
// were located in Go's build cache.
func Dir(files []string) string {
if len(files) == 0 {
return ""
}
cache, err := os.UserCacheDir()
if err != nil {
cache = ""
}
var path string
for _, p := range files {
// FIXME(dh): using strings.HasPrefix isn't technically
// correct, but it should be good enough for now.
if cache != "" && strings.HasPrefix(p, cache) {
// File in the build cache of the standard Go build system
continue
}
path = p
break
}
if path == "" {
// The package only consists of generated files.
return ""
}
dir := filepath.Dir(path)
return dir
}
func dirAST(files []*ast.File, fset *token.FileSet) string {
names := make([]string, len(files))
for i, f := range files {
names[i] = fset.PositionFor(f.Pos(), true).Filename
}
return Dir(names)
}
var Analyzer = &analysis.Analyzer{
Name: "config",
Doc: "loads configuration for the current package tree",
Run: func(pass *analysis.Pass) (any, error) {
dir := dirAST(pass.Files, pass.Fset)
if dir == "" {
cfg := DefaultConfig
return &cfg, nil
}
cfg, err := Load(dir)
if err != nil {
return nil, fmt.Errorf("error loading staticcheck.conf: %s", err)
}
return &cfg, nil
},
RunDespiteErrors: true,
ResultType: reflect.TypeFor[*Config](),
}
func For(pass *analysis.Pass) *Config {
return pass.ResultOf[Analyzer].(*Config)
}
func mergeLists(a, b []string) []string {
out := make([]string, 0, len(a)+len(b))
for _, el := range b {
if el == "inherit" {
out = append(out, a...)
} else {
out = append(out, el)
}
}
return out
}
func normalizeList(list []string) []string {
if len(list) > 1 {
nlist := make([]string, 0, len(list))
nlist = append(nlist, list[0])
for i, el := range list[1:] {
if el != list[i] {
nlist = append(nlist, el)
}
}
list = nlist
}
for _, el := range list {
if el == "inherit" {
// This should never happen, because the default config
// should not use "inherit"
panic(`unresolved "inherit"`)
}
}
return list
}
func (cfg Config) Merge(ocfg Config) Config {
if ocfg.Checks != nil {
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
}
if ocfg.Initialisms != nil {
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
}
if ocfg.DotImportWhitelist != nil {
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
}
if ocfg.HTTPStatusCodeWhitelist != nil {
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
}
return cfg
}
type Config struct {
// TODO(dh): this implementation makes it impossible for external
// clients to add their own checkers with configuration. At the
// moment, we don't really care about that; we don't encourage
// that people use this package. In the future, we may. The
// obvious solution would be using map[string]interface{}, but
// that's obviously subpar.
Checks []string `toml:"checks"`
Initialisms []string `toml:"initialisms"`
DotImportWhitelist []string `toml:"dot_import_whitelist"`
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
}
func (c Config) String() string {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "Checks: %#v\n", c.Checks)
fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms)
fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist)
fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist)
return buf.String()
}
// DefaultConfig is the default configuration.
// Its initial value describes the majority of the default configuration,
// but the Checks field can be updated at runtime based on the analyzers being used, to disable non-default checks.
// For cmd/staticcheck, this is handled by (*lintcmd.Command).Run.
//
// Note that DefaultConfig shouldn't be modified while analyzers are executing.
var DefaultConfig = Config{
Checks: []string{"all"},
Initialisms: []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS",
},
DotImportWhitelist: []string{
"simd/archsimd",
"github.com/mmcloughlin/avo/build",
"github.com/mmcloughlin/avo/operand",
"github.com/mmcloughlin/avo/reg",
},
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
}
const ConfigName = "staticcheck.conf"
type ParseError struct {
Filename string
toml.ParseError
}
func parseConfigs(dir string) ([]Config, error) {
var out []Config
// TODO(dh): consider stopping at the GOPATH/module boundary
for dir != "" {
path := filepath.Join(dir, ConfigName)
fi, err := os.Stat(path)
if os.IsNotExist(err) || (err == nil && !fi.Mode().IsRegular()) {
// walk up
ndir := filepath.Dir(dir)
if ndir == dir {
break
}
dir = ndir
continue
}
if err != nil {
return nil, err
}
// There is a small TOCTOU window here, but we're fine with reporting an
// error if the source tree is modified concurrently in weird ways while
// running Staticcheck.
f, err := os.Open(path)
if err != nil {
return nil, err
}
var cfg Config
_, err = toml.NewDecoder(f).Decode(&cfg)
f.Close()
if err != nil {
if err, ok := err.(toml.ParseError); ok {
return nil, ParseError{
Filename: filepath.Join(dir, ConfigName),
ParseError: err,
}
}
return nil, err
}
out = append(out, cfg)
ndir := filepath.Dir(dir)
if ndir == dir {
break
}
dir = ndir
}
out = append(out, DefaultConfig)
if len(out) < 2 {
return out, nil
}
for i := 0; i < len(out)/2; i++ {
out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
}
return out, nil
}
func mergeConfigs(confs []Config) Config {
if len(confs) == 0 {
// This shouldn't happen because we always have at least a
// default config.
panic("trying to merge zero configs")
}
if len(confs) == 1 {
return confs[0]
}
conf := confs[0]
for _, oconf := range confs[1:] {
conf = conf.Merge(oconf)
}
return conf
}
func Load(dir string) (Config, error) {
confs, err := parseConfigs(dir)
if err != nil {
return Config{}, err
}
conf := mergeConfigs(confs)
conf.Checks = normalizeList(conf.Checks)
conf.Initialisms = normalizeList(conf.Initialisms)
conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
return conf, nil
}
================================================
FILE: config/example.conf
================================================
checks = ["all", "-SA9003", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"]
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
"UDP", "UI", "GID", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
"XSS", "SIP", "RTP", "AMQP", "DB", "TS"]
dot_import_whitelist = [
"simd/archsimd",
"github.com/mmcloughlin/avo/build",
"github.com/mmcloughlin/avo/operand",
"github.com/mmcloughlin/avo/reg",
]
http_status_code_whitelist = ["200", "400", "404", "500"]
================================================
FILE: debug/debug.go
================================================
// Package debug contains helpers for debugging static analyses.
package debug
import (
"bytes"
"go/ast"
"go/format"
"go/importer"
"go/parser"
"go/token"
"go/types"
"sync"
)
// TypeCheck parses and type-checks a single-file Go package from a string.
// The package must not have any imports.
func TypeCheck(src string) (*ast.File, *types.Package, *types.Info, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
return nil, nil, nil, err
}
pkg := types.NewPackage("foo", f.Name.Name)
info := &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
Defs: map[*ast.Ident]types.Object{},
Uses: map[*ast.Ident]types.Object{},
Implicits: map[ast.Node]types.Object{},
Selections: map[*ast.SelectorExpr]*types.Selection{},
Scopes: map[ast.Node]*types.Scope{},
InitOrder: []*types.Initializer{},
Instances: map[*ast.Ident]types.Instance{},
}
tcfg := &types.Config{
Importer: importer.Default(),
}
if err := types.NewChecker(tcfg, fset, pkg, info).Files([]*ast.File{f}); err != nil {
return nil, nil, nil, err
}
return f, pkg, info, nil
}
func FormatNode(node ast.Node) string {
var buf bytes.Buffer
fset := token.NewFileSet()
format.Node(&buf, fset, node)
return buf.String()
}
var aliasesDefaultOnce sync.Once
var gotypesaliasDefault bool
func AliasesEnabled() bool {
// Dynamically check if Aliases will be produced from go/types.
aliasesDefaultOnce.Do(func() {
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
_, gotypesaliasDefault = pkg.Scope().Lookup("A").Type().(*types.Alias)
})
return gotypesaliasDefault
}
================================================
FILE: dist/build.sh
================================================
#!/bin/sh -e
build() {
ROOT="$GOPATH/src/honnef.co/go/tools"
os="$1"
arch="$2"
echo "Building GOOS=$os GOARCH=$arch..."
exe="staticcheck"
if [ $os = "windows" ]; then
exe="${exe}.exe"
fi
target="staticcheck_${os}_${arch}"
arm=""
case "$arch" in
armv5l)
arm=5
arch=arm
;;
armv6l)
arm=6
arch=arm
;;
armv7l)
arm=7
arch=arm
;;
arm64)
arch=arm64
;;
esac
mkdir "$d/staticcheck"
cp "$ROOT/LICENSE" "$ROOT/LICENSE-THIRD-PARTY" "$d/staticcheck"
CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm GO111MODULE=on go build -trimpath -o "$d/staticcheck/$exe" honnef.co/go/tools/cmd/staticcheck
(
cd "$d"
tar -czf "$target.tar.gz" staticcheck
sha256sum "$target.tar.gz" > "$target.tar.gz.sha256"
)
rm -rf "$d/staticcheck"
}
rev="$1"
if [ -z "$rev" ]; then
echo "Usage: $0 "
exit 1
fi
mkdir "$rev"
d=$(realpath "$rev")
wrk=$(mktemp -d)
trap "{ rm -rf \"$wrk\"; }" EXIT
cd "$wrk"
go mod init foo
GO111MODULE=on go get -d honnef.co/go/tools/cmd/staticcheck@"$rev"
SYSTEMS=(windows linux freebsd)
ARCHS=(amd64 386)
for os in ${SYSTEMS[@]}; do
for arch in ${ARCHS[@]}; do
build "$os" "$arch"
done
done
build "darwin" "amd64"
build "darwin" "arm64"
for arch in armv5l armv6l armv7l arm64; do
build "linux" "$arch"
done
(
cd "$d"
sha256sum -c --strict *.sha256
)
================================================
FILE: doc/articles/customizing_staticcheck.html
================================================
- how to customize staticcheck
- tools serve humans
- tools should assist workflows
- don't let tools bully you
- exit status
- which checks run
- ignoring findings
- output format
- go version
- tests
================================================
FILE: doc/run.html
================================================
Running Staticcheck
Checking packages
The staticcheck command works much like go build or go vet do.
It supports all of the same package patterns.
For example, staticcheck . will check the current package, and staticcheck ./... will check all packages.
For more details on specifying packages to check, see go help packages
Explaining checks
You can use staticcheck -explain <check> to get a helpful description of a check.
Every diagnostic that staticcheck reports is annotated with the identifier of the specific check that found the issue.
For example, in
foo.go:1248:4: unnecessary use of fmt.Sprintf (S1039)
the check's identifier is S1039.
Running staticcheck -explain S1039 will output the following:
Unnecessary use of fmt.Sprint
Calling fmt.Sprint with a single string argument is unnecessary and identical to using the string directly.
Available since
2020.1
Online documentation
https://staticcheck.dev/docs/checks#S1039
The output includes a one-line summary,
one or more paragraphs of helpful text,
the first version of Staticcheck that the check appeared in,
and a link to online documentation, which contains the same information as the output of staticcheck -explain.
================================================
FILE: generate.go
================================================
//go:build ignore
package main
import (
"bytes"
"go/format"
"log"
"os"
"path/filepath"
"regexp"
"text/template"
)
var tmpl = `
{{define "analyzers"}}
// Code generated by generate.go. DO NOT EDIT.
package {{.dir}}
import (
"honnef.co/go/tools/analysis/lint"
{{- range $check := .checks}}
"honnef.co/go/tools/{{$.dir}}/{{$check}}"
{{- end}}
)
var Analyzers = []*lint.Analyzer{
{{- range $check := .checks}}
{{$check}}.SCAnalyzer,
{{- end}}
}
{{end}}
{{define "tests"}}
// Code generated by generate.go. DO NOT EDIT.
package {{.check}}
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
{{end}}
`
func main() {
log.SetFlags(0)
dir, err := os.Getwd()
if err != nil {
log.Fatalln("couldn't determine current directory:", err)
}
dir = filepath.Base(dir)
var t template.Template
if _, err = t.Parse(tmpl); err != nil {
log.Fatalln("couldn't parse templates:", err)
}
dirs, err := filepath.Glob("*")
if err != nil {
log.Fatalln("couldn't enumerate checks:", err)
}
checkRe := regexp.MustCompile(`^[a-z]+\d{4}$`)
out := dirs[:0]
for _, dir := range dirs {
if checkRe.MatchString(dir) {
out = append(out, dir)
}
}
dirs = out
buf := bytes.NewBuffer(nil)
if err := t.ExecuteTemplate(buf, "analyzers", map[string]any{"checks": dirs, "dir": dir}); err != nil {
log.Fatalln("couldn't generate analysis.go:", err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalln("couldn't gofmt analysis.go:", err)
}
if err := os.WriteFile("analysis.go", b, 0666); err != nil {
log.Fatalln("couldn't write analysis.go:", err)
}
for _, dir := range dirs {
buf.Reset()
dst := filepath.Join(dir, dir+"_test.go")
if err := t.ExecuteTemplate(buf, "tests", map[string]any{"check": dir}); err != nil {
log.Fatalf("couldn't generate %s: %s", dst, err)
}
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("couldn't gofmt %s: %s", dst, err)
}
if err := os.WriteFile(dst, b, 0666); err != nil {
log.Fatalf("couldn't write %s: %s", dst, err)
}
}
}
================================================
FILE: go/ast/astutil/upstream.go
================================================
package astutil
import (
"go/ast"
"go/token"
_ "unsafe"
"golang.org/x/tools/go/ast/astutil"
)
type Cursor = astutil.Cursor
type ApplyFunc = astutil.ApplyFunc
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
return astutil.Apply(root, pre, post)
}
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
return astutil.PathEnclosingInterval(root, start, end)
}
================================================
FILE: go/ast/astutil/util.go
================================================
package astutil
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
func IsIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
// isBlank returns whether id is the blank identifier "_".
// If id == nil, the answer is false.
func IsBlank(id ast.Expr) bool {
ident, _ := id.(*ast.Ident)
return ident != nil && ident.Name == "_"
}
// Deprecated: use code.IsIntegerLiteral instead.
func IsIntLiteral(expr ast.Expr, literal string) bool {
lit, ok := expr.(*ast.BasicLit)
return ok && lit.Kind == token.INT && lit.Value == literal
}
// Deprecated: use IsIntLiteral instead
func IsZero(expr ast.Expr) bool {
return IsIntLiteral(expr, "0")
}
func Preamble(f *ast.File) string {
cutoff := f.Package
if f.Doc != nil {
cutoff = f.Doc.Pos()
}
var out []string
for _, cmt := range f.Comments {
if cmt.Pos() >= cutoff {
break
}
out = append(out, cmt.Text())
}
return strings.Join(out, "\n")
}
func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
if len(specs) == 0 {
return nil
}
groups := make([][]ast.Spec, 1)
groups[0] = append(groups[0], specs[0])
for _, spec := range specs[1:] {
g := groups[len(groups)-1]
if fset.PositionFor(spec.Pos(), false).Line-1 !=
fset.PositionFor(g[len(g)-1].End(), false).Line {
groups = append(groups, nil)
}
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
}
return groups
}
// Unparen returns e with any enclosing parentheses stripped.
func Unparen(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
return e
}
e = p.X
}
}
// CopyExpr creates a deep copy of an expression.
// It doesn't support copying FuncLits and returns ok == false when encountering one.
func CopyExpr(node ast.Expr) (ast.Expr, bool) {
switch node := node.(type) {
case *ast.BasicLit:
cp := *node
return &cp, true
case *ast.BinaryExpr:
cp := *node
var ok1, ok2 bool
cp.X, ok1 = CopyExpr(cp.X)
cp.Y, ok2 = CopyExpr(cp.Y)
return &cp, ok1 && ok2
case *ast.CallExpr:
var ok bool
cp := *node
cp.Fun, ok = CopyExpr(cp.Fun)
if !ok {
return nil, false
}
cp.Args = make([]ast.Expr, len(node.Args))
for i, v := range node.Args {
cp.Args[i], ok = CopyExpr(v)
if !ok {
return nil, false
}
}
return &cp, true
case *ast.CompositeLit:
var ok bool
cp := *node
cp.Type, ok = CopyExpr(cp.Type)
if !ok {
return nil, false
}
cp.Elts = make([]ast.Expr, len(node.Elts))
for i, v := range node.Elts {
cp.Elts[i], ok = CopyExpr(v)
if !ok {
return nil, false
}
}
return &cp, true
case *ast.Ident:
cp := *node
return &cp, true
case *ast.IndexExpr:
var ok1, ok2 bool
cp := *node
cp.X, ok1 = CopyExpr(cp.X)
cp.Index, ok2 = CopyExpr(cp.Index)
return &cp, ok1 && ok2
case *ast.IndexListExpr:
var ok bool
cp := *node
cp.X, ok = CopyExpr(cp.X)
if !ok {
return nil, false
}
for i, v := range node.Indices {
cp.Indices[i], ok = CopyExpr(v)
if !ok {
return nil, false
}
}
return &cp, true
case *ast.KeyValueExpr:
var ok1, ok2 bool
cp := *node
cp.Key, ok1 = CopyExpr(cp.Key)
cp.Value, ok2 = CopyExpr(cp.Value)
return &cp, ok1 && ok2
case *ast.ParenExpr:
var ok bool
cp := *node
cp.X, ok = CopyExpr(cp.X)
return &cp, ok
case *ast.SelectorExpr:
var ok bool
cp := *node
cp.X, ok = CopyExpr(cp.X)
if !ok {
return nil, false
}
sel, ok := CopyExpr(cp.Sel)
if !ok {
// this is impossible
return nil, false
}
cp.Sel = sel.(*ast.Ident)
return &cp, true
case *ast.SliceExpr:
var ok1, ok2, ok3, ok4 bool
cp := *node
cp.X, ok1 = CopyExpr(cp.X)
cp.Low, ok2 = CopyExpr(cp.Low)
cp.High, ok3 = CopyExpr(cp.High)
cp.Max, ok4 = CopyExpr(cp.Max)
return &cp, ok1 && ok2 && ok3 && ok4
case *ast.StarExpr:
var ok bool
cp := *node
cp.X, ok = CopyExpr(cp.X)
return &cp, ok
case *ast.TypeAssertExpr:
var ok1, ok2 bool
cp := *node
cp.X, ok1 = CopyExpr(cp.X)
cp.Type, ok2 = CopyExpr(cp.Type)
return &cp, ok1 && ok2
case *ast.UnaryExpr:
var ok bool
cp := *node
cp.X, ok = CopyExpr(cp.X)
return &cp, ok
case *ast.MapType:
var ok1, ok2 bool
cp := *node
cp.Key, ok1 = CopyExpr(cp.Key)
cp.Value, ok2 = CopyExpr(cp.Value)
return &cp, ok1 && ok2
case *ast.ArrayType:
var ok1, ok2 bool
cp := *node
cp.Len, ok1 = CopyExpr(cp.Len)
cp.Elt, ok2 = CopyExpr(cp.Elt)
return &cp, ok1 && ok2
case *ast.Ellipsis:
var ok bool
cp := *node
cp.Elt, ok = CopyExpr(cp.Elt)
return &cp, ok
case *ast.InterfaceType:
cp := *node
return &cp, true
case *ast.StructType:
cp := *node
return &cp, true
case *ast.FuncLit, *ast.FuncType:
// TODO(dh): implement copying of function literals and types.
return nil, false
case *ast.ChanType:
var ok bool
cp := *node
cp.Value, ok = CopyExpr(cp.Value)
return &cp, ok
case nil:
return nil, true
default:
panic(fmt.Sprintf("unreachable: %T", node))
}
}
func Equal(a, b ast.Node) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
if reflect.TypeOf(a) != reflect.TypeOf(b) {
return false
}
switch a := a.(type) {
case *ast.BasicLit:
b := b.(*ast.BasicLit)
return a.Kind == b.Kind && a.Value == b.Value
case *ast.BinaryExpr:
b := b.(*ast.BinaryExpr)
return Equal(a.X, b.X) && a.Op == b.Op && Equal(a.Y, b.Y)
case *ast.CallExpr:
b := b.(*ast.CallExpr)
if len(a.Args) != len(b.Args) {
return false
}
for i, arg := range a.Args {
if !Equal(arg, b.Args[i]) {
return false
}
}
return Equal(a.Fun, b.Fun) &&
(a.Ellipsis == token.NoPos && b.Ellipsis == token.NoPos || a.Ellipsis != token.NoPos && b.Ellipsis != token.NoPos)
case *ast.CompositeLit:
b := b.(*ast.CompositeLit)
if len(a.Elts) != len(b.Elts) {
return false
}
for i, elt := range b.Elts {
if !Equal(elt, b.Elts[i]) {
return false
}
}
return Equal(a.Type, b.Type) && a.Incomplete == b.Incomplete
case *ast.Ident:
b := b.(*ast.Ident)
return a.Name == b.Name
case *ast.IndexExpr:
b := b.(*ast.IndexExpr)
return Equal(a.X, b.X) && Equal(a.Index, b.Index)
case *ast.IndexListExpr:
b := b.(*ast.IndexListExpr)
if len(a.Indices) != len(b.Indices) {
return false
}
for i, v := range a.Indices {
if !Equal(v, b.Indices[i]) {
return false
}
}
return Equal(a.X, b.X)
case *ast.KeyValueExpr:
b := b.(*ast.KeyValueExpr)
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
case *ast.ParenExpr:
b := b.(*ast.ParenExpr)
return Equal(a.X, b.X)
case *ast.SelectorExpr:
b := b.(*ast.SelectorExpr)
return Equal(a.X, b.X) && Equal(a.Sel, b.Sel)
case *ast.SliceExpr:
b := b.(*ast.SliceExpr)
return Equal(a.X, b.X) && Equal(a.Low, b.Low) && Equal(a.High, b.High) && Equal(a.Max, b.Max) && a.Slice3 == b.Slice3
case *ast.StarExpr:
b := b.(*ast.StarExpr)
return Equal(a.X, b.X)
case *ast.TypeAssertExpr:
b := b.(*ast.TypeAssertExpr)
return Equal(a.X, b.X) && Equal(a.Type, b.Type)
case *ast.UnaryExpr:
b := b.(*ast.UnaryExpr)
return a.Op == b.Op && Equal(a.X, b.X)
case *ast.MapType:
b := b.(*ast.MapType)
return Equal(a.Key, b.Key) && Equal(a.Value, b.Value)
case *ast.ArrayType:
b := b.(*ast.ArrayType)
return Equal(a.Len, b.Len) && Equal(a.Elt, b.Elt)
case *ast.Ellipsis:
b := b.(*ast.Ellipsis)
return Equal(a.Elt, b.Elt)
case *ast.InterfaceType:
b := b.(*ast.InterfaceType)
return a.Incomplete == b.Incomplete && Equal(a.Methods, b.Methods)
case *ast.StructType:
b := b.(*ast.StructType)
return a.Incomplete == b.Incomplete && Equal(a.Fields, b.Fields)
case *ast.FuncLit:
// TODO(dh): support function literals
return false
case *ast.ChanType:
b := b.(*ast.ChanType)
return a.Dir == b.Dir && (a.Arrow == token.NoPos && b.Arrow == token.NoPos || a.Arrow != token.NoPos && b.Arrow != token.NoPos)
case *ast.FieldList:
b := b.(*ast.FieldList)
if len(a.List) != len(b.List) {
return false
}
for i, fieldA := range a.List {
if !Equal(fieldA, b.List[i]) {
return false
}
}
return true
case *ast.Field:
b := b.(*ast.Field)
if len(a.Names) != len(b.Names) {
return false
}
for j, name := range a.Names {
if !Equal(name, b.Names[j]) {
return false
}
}
if !Equal(a.Type, b.Type) || !Equal(a.Tag, b.Tag) {
return false
}
return true
default:
panic(fmt.Sprintf("unreachable: %T", a))
}
}
func NegateDeMorgan(expr ast.Expr, recursive bool) ast.Expr {
switch expr := expr.(type) {
case *ast.BinaryExpr:
var out ast.BinaryExpr
switch expr.Op {
case token.EQL:
out.X = expr.X
out.Op = token.NEQ
out.Y = expr.Y
case token.LSS:
out.X = expr.X
out.Op = token.GEQ
out.Y = expr.Y
case token.GTR:
out.X = expr.X
out.Op = token.LEQ
out.Y = expr.Y
case token.NEQ:
out.X = expr.X
out.Op = token.EQL
out.Y = expr.Y
case token.LEQ:
out.X = expr.X
out.Op = token.GTR
out.Y = expr.Y
case token.GEQ:
out.X = expr.X
out.Op = token.LSS
out.Y = expr.Y
case token.LAND:
out.X = NegateDeMorgan(expr.X, recursive)
out.Op = token.LOR
out.Y = NegateDeMorgan(expr.Y, recursive)
case token.LOR:
out.X = NegateDeMorgan(expr.X, recursive)
out.Op = token.LAND
out.Y = NegateDeMorgan(expr.Y, recursive)
}
return &out
case *ast.ParenExpr:
if recursive {
return &ast.ParenExpr{
X: NegateDeMorgan(expr.X, recursive),
}
} else {
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
case *ast.UnaryExpr:
if expr.Op == token.NOT {
return expr.X
} else {
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
default:
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
}
}
func SimplifyParentheses(node ast.Expr) ast.Expr {
var changed bool
// XXX accept list of ops to operate on
// XXX copy AST node, don't modify in place
post := func(c *astutil.Cursor) bool {
out := c.Node()
if paren, ok := c.Node().(*ast.ParenExpr); ok {
out = paren.X
}
if binop, ok := out.(*ast.BinaryExpr); ok {
if right, ok := binop.Y.(*ast.BinaryExpr); ok && binop.Op == right.Op {
// XXX also check that Op is associative
root := binop
pivot := root.Y.(*ast.BinaryExpr)
root.Y = pivot.X
pivot.X = root
root = pivot
out = root
}
}
if out != c.Node() {
changed = true
c.Replace(out)
}
return true
}
for changed = true; changed; {
changed = false
node = astutil.Apply(node, nil, post).(ast.Expr)
}
return node
}
================================================
FILE: go/buildid/UPSTREAM
================================================
This package extracts buildid.go and note.go from cmd/internal/buildid/.
We have modified it to remove support for AIX big archive files, to cut down on our dependencies.
The last upstream commit we've looked at was: d3ddc4854429185e6e06ca1f7628bb790404abb5
================================================
FILE: go/buildid/buildid.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildid
import (
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
)
var errBuildIDMalformed = fmt.Errorf("malformed object file")
var (
bangArch = []byte("!")
pkgdef = []byte("__.PKGDEF")
goobject = []byte("go object ")
buildid = []byte("build id ")
)
// ReadFile reads the build ID from an archive or executable file.
func ReadFile(name string) (id string, err error) {
f, err := os.Open(name)
if err != nil {
return "", err
}
defer f.Close()
buf := make([]byte, 8)
if _, err := f.ReadAt(buf, 0); err != nil {
return "", err
}
if string(buf) != "!\n" {
if string(buf) == "\n" {
return "", errors.New("unsupported")
}
return readBinary(name, f)
}
// Read just enough of the target to fetch the build ID.
// The archive is expected to look like:
//
// !
// __.PKGDEF 0 0 0 644 7955 `
// go object darwin amd64 devel X:none
// build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
//
// The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
// Reading the first 1024 bytes should be plenty.
data := make([]byte, 1024)
n, err := io.ReadFull(f, data)
if err != nil && n == 0 {
return "", err
}
tryGccgo := func() (string, error) {
return readGccgoArchive(name, f)
}
// Archive header.
for i := 0; ; i++ { // returns during i==3
j := bytes.IndexByte(data, '\n')
if j < 0 {
return tryGccgo()
}
line := data[:j]
data = data[j+1:]
switch i {
case 0:
if !bytes.Equal(line, bangArch) {
return tryGccgo()
}
case 1:
if !bytes.HasPrefix(line, pkgdef) {
return tryGccgo()
}
case 2:
if !bytes.HasPrefix(line, goobject) {
return tryGccgo()
}
case 3:
if !bytes.HasPrefix(line, buildid) {
// Found the object header, just doesn't have a build id line.
// Treat as successful, with empty build id.
return "", nil
}
id, err := strconv.Unquote(string(line[len(buildid):]))
if err != nil {
return tryGccgo()
}
return id, nil
}
}
}
// readGccgoArchive tries to parse the archive as a standard Unix
// archive file, and fetch the build ID from the _buildid.o entry.
// The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile
// in cmd/go/internal/work/exec.go.
func readGccgoArchive(name string, f *os.File) (string, error) {
bad := func() (string, error) {
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
}
off := int64(8)
for {
if _, err := f.Seek(off, io.SeekStart); err != nil {
return "", err
}
// TODO(iant): Make a debug/ar package, and use it
// here and in cmd/link.
var hdr [60]byte
if _, err := io.ReadFull(f, hdr[:]); err != nil {
if err == io.EOF {
// No more entries, no build ID.
return "", nil
}
return "", err
}
off += 60
sizeStr := strings.TrimSpace(string(hdr[48:58]))
size, err := strconv.ParseInt(sizeStr, 0, 64)
if err != nil {
return bad()
}
name := strings.TrimSpace(string(hdr[:16]))
if name == "_buildid.o/" {
sr := io.NewSectionReader(f, off, size)
e, err := elf.NewFile(sr)
if err != nil {
return bad()
}
s := e.Section(".go.buildid")
if s == nil {
return bad()
}
data, err := s.Data()
if err != nil {
return bad()
}
return string(data), nil
}
off += size
if off&1 != 0 {
off++
}
}
}
var (
goBuildPrefix = []byte("\xff Go build ID: \"")
goBuildEnd = []byte("\"\n \xff")
elfPrefix = []byte("\x7fELF")
machoPrefixes = [][]byte{
{0xfe, 0xed, 0xfa, 0xce},
{0xfe, 0xed, 0xfa, 0xcf},
{0xce, 0xfa, 0xed, 0xfe},
{0xcf, 0xfa, 0xed, 0xfe},
}
)
var readSize = 32 * 1024 // changed for testing
// readBinary reads the build ID from a binary.
//
// ELF binaries store the build ID in a proper PT_NOTE section.
//
// Other binary formats are not so flexible. For those, the linker
// stores the build ID as non-instruction bytes at the very beginning
// of the text segment, which should appear near the beginning
// of the file. This is clumsy but fairly portable. Custom locations
// can be added for other binary types as needed, like we did for ELF.
func readBinary(name string, f *os.File) (id string, err error) {
// Read the first 32 kB of the binary file.
// That should be enough to find the build ID.
// In ELF files, the build ID is in the leading headers,
// which are typically less than 4 kB, not to mention 32 kB.
// In Mach-O files, there's no limit, so we have to parse the file.
// On other systems, we're trying to read enough that
// we get the beginning of the text segment in the read.
// The offset where the text segment begins in a hello
// world compiled for each different object format today:
//
// Plan 9: 0x20
// Windows: 0x600
//
data := make([]byte, readSize)
_, err = io.ReadFull(f, data)
if err == io.ErrUnexpectedEOF {
err = nil
}
if err != nil {
return "", err
}
if bytes.HasPrefix(data, elfPrefix) {
return readELF(name, f, data)
}
for _, m := range machoPrefixes {
if bytes.HasPrefix(data, m) {
return readMacho(name, f, data)
}
}
return readRaw(name, data)
}
// readRaw finds the raw build ID stored in text segment data.
func readRaw(name string, data []byte) (id string, err error) {
i := bytes.Index(data, goBuildPrefix)
if i < 0 {
// Missing. Treat as successful but build ID empty.
return "", nil
}
j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd)
if j < 0 {
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
}
quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1]
id, err = strconv.Unquote(string(quoted))
if err != nil {
return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed}
}
return id, nil
}
================================================
FILE: go/buildid/note.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildid
import (
"bytes"
"debug/elf"
"debug/macho"
"encoding/binary"
"fmt"
"io"
"os"
)
func readAligned4(r io.Reader, sz int32) ([]byte, error) {
full := (sz + 3) &^ 3
data := make([]byte, full)
_, err := io.ReadFull(r, data)
if err != nil {
return nil, err
}
data = data[:sz]
return data, nil
}
func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
f, err := elf.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
for _, sect := range f.Sections {
if sect.Type != elf.SHT_NOTE {
continue
}
r := sect.Open()
for {
var namesize, descsize, noteType int32
err = binary.Read(r, f.ByteOrder, &namesize)
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("read namesize failed: %v", err)
}
err = binary.Read(r, f.ByteOrder, &descsize)
if err != nil {
return nil, fmt.Errorf("read descsize failed: %v", err)
}
err = binary.Read(r, f.ByteOrder, ¬eType)
if err != nil {
return nil, fmt.Errorf("read type failed: %v", err)
}
noteName, err := readAligned4(r, namesize)
if err != nil {
return nil, fmt.Errorf("read name failed: %v", err)
}
desc, err := readAligned4(r, descsize)
if err != nil {
return nil, fmt.Errorf("read desc failed: %v", err)
}
if name == string(noteName) && typ == noteType {
return desc, nil
}
}
}
return nil, nil
}
var elfGoNote = []byte("Go\x00\x00")
var elfGNUNote = []byte("GNU\x00")
// The Go build ID is stored in a note described by an ELF PT_NOTE prog
// header. The caller has already opened filename, to get f, and read
// at least 4 kB out, in data.
func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
// Assume the note content is in the data, already read.
// Rewrite the ELF header to set shnum to 0, so that we can pass
// the data to elf.NewFile and it will decode the Prog list but not
// try to read the section headers and the string table from disk.
// That's a waste of I/O when all we care about is the Prog list
// and the one ELF note.
switch elf.Class(data[elf.EI_CLASS]) {
case elf.ELFCLASS32:
data[48] = 0
data[49] = 0
case elf.ELFCLASS64:
data[60] = 0
data[61] = 0
}
const elfGoBuildIDTag = 4
const gnuBuildIDTag = 3
ef, err := elf.NewFile(bytes.NewReader(data))
if err != nil {
return "", &os.PathError{Path: name, Op: "parse", Err: err}
}
var gnu string
for _, p := range ef.Progs {
if p.Type != elf.PT_NOTE || p.Filesz < 16 {
continue
}
var note []byte
if p.Off+p.Filesz < uint64(len(data)) {
note = data[p.Off : p.Off+p.Filesz]
} else {
// For some linkers, such as the Solaris linker,
// the buildid may not be found in data (which
// likely contains the first 16kB of the file)
// or even the first few megabytes of the file
// due to differences in note segment placement;
// in that case, extract the note data manually.
_, err = f.Seek(int64(p.Off), io.SeekStart)
if err != nil {
return "", err
}
note = make([]byte, p.Filesz)
_, err = io.ReadFull(f, note)
if err != nil {
return "", err
}
}
filesz := p.Filesz
off := p.Off
for filesz >= 16 {
nameSize := ef.ByteOrder.Uint32(note)
valSize := ef.ByteOrder.Uint32(note[4:])
tag := ef.ByteOrder.Uint32(note[8:])
nname := note[12:16]
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
return string(note[16 : 16+valSize]), nil
}
if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
gnu = string(note[16 : 16+valSize])
}
nameSize = (nameSize + 3) &^ 3
valSize = (valSize + 3) &^ 3
notesz := uint64(12 + nameSize + valSize)
if filesz <= notesz {
break
}
off += notesz
align := p.Align
alignedOff := (off + align - 1) &^ (align - 1)
notesz += alignedOff - off
off = alignedOff
filesz -= notesz
note = note[notesz:]
}
}
// If we didn't find a Go note, use a GNU note if available.
// This is what gccgo uses.
if gnu != "" {
return gnu, nil
}
// No note. Treat as successful but build ID empty.
return "", nil
}
// The Go build ID is stored at the beginning of the Mach-O __text segment.
// The caller has already opened filename, to get f, and read a few kB out, in data.
// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
// of other junk placed in the file ahead of the main text.
func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
// If the data we want has already been read, don't worry about Mach-O parsing.
// This is both an optimization and a hedge against the Mach-O parsing failing
// in the future due to, for example, the name of the __text section changing.
if b, err := readRaw(name, data); b != "" && err == nil {
return b, err
}
mf, err := macho.NewFile(f)
if err != nil {
return "", &os.PathError{Path: name, Op: "parse", Err: err}
}
sect := mf.Section("__text")
if sect == nil {
// Every binary has a __text section. Something is wrong.
return "", &os.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
}
// It should be in the first few bytes, but read a lot just in case,
// especially given our past problems on OS X with the build ID moving.
// There shouldn't be much difference between reading 4kB and 32kB:
// the hard part is getting to the data, not transferring it.
n := min(sect.Size, uint64(readSize))
buf := make([]byte, n)
if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
return "", err
}
return readRaw(name, buf)
}
================================================
FILE: go/gcsizes/LICENSE
================================================
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: go/gcsizes/sizes.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gcsizes provides a types.Sizes implementation that adheres
// to the rules used by the gc compiler.
package gcsizes
import (
"go/build"
"go/types"
)
type Sizes struct {
WordSize int64
MaxAlign int64
}
// ForArch returns a correct Sizes for the given architecture.
func ForArch(arch string) *Sizes {
wordSize := int64(8)
maxAlign := int64(8)
switch build.Default.GOARCH {
case "386", "arm":
wordSize, maxAlign = 4, 4
case "amd64p32":
wordSize = 4
}
return &Sizes{WordSize: wordSize, MaxAlign: maxAlign}
}
func (s *Sizes) Alignof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Array:
return s.Alignof(t.Elem())
case *types.Struct:
max := int64(1)
n := t.NumFields()
var fields []*types.Var
for i := range n {
fields = append(fields, t.Field(i))
}
for _, f := range fields {
if a := s.Alignof(f.Type()); a > max {
max = a
}
}
return max
}
a := s.Sizeof(T) // may be 0
if a < 1 {
return 1
}
if a > s.MaxAlign {
return s.MaxAlign
}
return a
}
func (s *Sizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
o = align(o, a)
offsets[i] = o
o += s.Sizeof(f.Type())
}
return offsets
}
var basicSizes = [...]byte{
types.Bool: 1,
types.Int8: 1,
types.Int16: 2,
types.Int32: 4,
types.Int64: 8,
types.Uint8: 1,
types.Uint16: 2,
types.Uint32: 4,
types.Uint64: 8,
types.Float32: 4,
types.Float64: 8,
types.Complex64: 8,
types.Complex128: 16,
}
func (s *Sizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
if int(k) < len(basicSizes) {
if s := basicSizes[k]; s > 0 {
return int64(s)
}
}
if k == types.String {
return s.WordSize * 2
}
case *types.Array:
n := t.Len()
if n == 0 {
return 0
}
a := s.Alignof(t.Elem())
z := s.Sizeof(t.Elem())
return align(z, a)*(n-1) + z
case *types.Slice:
return s.WordSize * 3
case *types.Struct:
n := t.NumFields()
if n == 0 {
return 0
}
var fields []*types.Var
for i := range n {
fields = append(fields, t.Field(i))
}
offsets := s.Offsetsof(fields)
a := s.Alignof(T)
lsz := s.Sizeof(fields[n-1].Type())
if lsz == 0 {
lsz = 1
}
z := offsets[n-1] + lsz
return align(z, a)
case *types.Interface:
return s.WordSize * 2
}
return s.WordSize // catch-all
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
================================================
FILE: go/ir/LICENSE
================================================
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright (c) 2016 Dominik Honnef. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: go/ir/UPSTREAM
================================================
This package started as a copy of golang.org/x/tools/go/ssa, imported from an unknown commit in 2016.
It has since been heavily modified to match our own needs in an IR.
The changes are too many to list here, and it is best to consider this package independent of go/ssa.
Upstream changes still get applied when they address bugs in portions of code we have inherited.
The last upstream commit we've looked at was:
05409620da166985e94b711ad4103bee40406eee
================================================
FILE: go/ir/bench_test.go
================================================
package ir_test
import (
"testing"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/go/ir"
)
func BenchmarkSSA(b *testing.B) {
cfg := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
Tests: false,
}
pkgs, err := packages.Load(cfg, "std")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
prog := ir.NewProgram(pkgs[0].Fset, ir.GlobalDebug)
seen := map[*packages.Package]struct{}{}
var create func(pkg *packages.Package)
create = func(pkg *packages.Package) {
if _, ok := seen[pkg]; ok {
return
}
seen[pkg] = struct{}{}
prog.CreatePackage(pkg.Types, pkg.Syntax, pkg.TypesInfo, true)
for _, imp := range pkg.Imports {
create(imp)
}
}
for _, pkg := range pkgs {
create(pkg)
}
prog.Build()
}
}
================================================
FILE: go/ir/blockopt.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// Simple block optimizations to simplify the control flow graph.
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
import (
"fmt"
"os"
)
// If true, perform sanity checking and show progress at each
// successive iteration of optimizeBlocks. Very verbose.
const debugBlockOpt = false
// markReachable sets Index=-1 for all blocks reachable from b.
func markReachable(b *BasicBlock) {
b.gaps = -1
for _, succ := range b.Succs {
if succ.gaps == 0 {
markReachable(succ)
}
}
}
// deleteUnreachableBlocks marks all reachable blocks of f and
// eliminates (nils) all others, including possibly cyclic subgraphs.
func deleteUnreachableBlocks(f *Function) {
const white, black = 0, -1
// We borrow b.gaps temporarily as the mark bit.
for _, b := range f.Blocks {
b.gaps = white
}
markReachable(f.Blocks[0])
// In SSI form, we need the exit to be reachable for correct
// post-dominance information. In original form, however, we
// cannot unconditionally mark it reachable because we won't
// be adding fake edges, and this breaks the calculation of
// dominance information.
markReachable(f.Exit)
for i, b := range f.Blocks {
if b.gaps == white {
for _, c := range b.Succs {
if c.gaps == black {
c.removePred(b) // delete white->black edge
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "unreachable", b)
}
f.Blocks[i] = nil // delete b
}
}
f.removeNilBlocks()
}
// jumpThreading attempts to apply simple jump-threading to block b,
// in which a->b->c become a->c if b is just a Jump.
// The result is true if the optimization was applied.
func jumpThreading(f *Function, b *BasicBlock) bool {
if b.Index == 0 {
return false // don't apply to entry block
}
if b.Instrs == nil {
return false
}
for _, pred := range b.Preds {
switch pred.Control().(type) {
case *ConstantSwitch:
// don't optimize away the head blocks of switch statements
return false
}
}
if _, ok := b.Instrs[0].(*Jump); !ok {
return false // not just a jump
}
c := b.Succs[0]
if c == b {
return false // don't apply to degenerate jump-to-self.
}
if c.hasPhi() {
return false // not sound without more effort
}
for j, a := range b.Preds {
a.replaceSucc(b, c)
// If a now has two edges to c, replace its degenerate If by Jump.
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
jump := new(Jump)
jump.setBlock(a)
a.Instrs[len(a.Instrs)-1] = jump
a.Succs = a.Succs[:1]
c.removePred(b)
} else {
if j == 0 {
c.replacePred(b, a)
} else {
c.Preds = append(c.Preds, a)
}
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
}
}
f.Blocks[b.Index] = nil // delete b
return true
}
// fuseBlocks attempts to apply the block fusion optimization to block
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
// The result is true if the optimization was applied.
func fuseBlocks(f *Function, a *BasicBlock) bool {
if len(a.Succs) != 1 {
return false
}
if a.Succs[0] == f.Exit {
return false
}
b := a.Succs[0]
if len(b.Preds) != 1 {
return false
}
if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok {
// panics aren't simple jumps, they have side effects.
return false
}
// Degenerate &&/|| ops may result in a straight-line CFG
// containing φ-nodes. (Ideally we'd replace such them with
// their sole operand but that requires Referrers, built later.)
if b.hasPhi() {
return false // not sound without further effort
}
// Eliminate jump at end of A, then copy all of B across.
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
for _, instr := range b.Instrs {
instr.setBlock(a)
}
// A inherits B's successors
a.Succs = append(a.succs2[:0], b.Succs...)
// Fix up Preds links of all successors of B.
for _, c := range b.Succs {
c.replacePred(b, a)
}
if debugBlockOpt {
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
}
f.Blocks[b.Index] = nil // delete b
return true
}
// optimizeBlocks() performs some simple block optimizations on a
// completed function: dead block elimination, block fusion, jump
// threading.
func optimizeBlocks(f *Function) {
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
deleteUnreachableBlocks(f)
// Loop until no further progress.
changed := true
for changed {
changed = false
if debugBlockOpt {
f.WriteTo(os.Stderr)
mustSanityCheck(f, nil)
}
for _, b := range f.Blocks {
// f.Blocks will temporarily contain nils to indicate
// deleted blocks; we remove them at the end.
if b == nil {
continue
}
// Fuse blocks. b->c becomes bc.
if fuseBlocks(f, b) {
changed = true
}
// a->b->c becomes a->c if b contains only a Jump.
if jumpThreading(f, b) {
changed = true
continue // (b was disconnected)
}
}
}
f.removeNilBlocks()
}
================================================
FILE: go/ir/builder.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the BUILD phase of IR construction.
//
// IR construction has two phases, CREATE and BUILD. In the CREATE phase
// (create.go), all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are
// computed, and wrapper methods are synthesized.
// ir.Packages are created in arbitrary order.
//
// In the BUILD phase (builder.go), the builder traverses the AST of
// each Go source function and generates IR instructions for the
// function body. Initializer expressions for package-level variables
// are emitted to the package's init() function in the order specified
// by go/types.Info.InitOrder, then code for each function in the
// package is generated in lexical order.
//
// The builder's and Program's indices (maps) are populated and
// mutated during the CREATE phase, but during the BUILD phase they
// remain constant. The sole exception is Prog.methodSets and its
// related maps, which are protected by a dedicated mutex.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"go/version"
"os"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/exp/typeparams"
)
var (
varOk = newVar("ok", tBool)
varIndex = newVar("index", tInt)
// Type constants.
tBool = types.Typ[types.Bool]
tInt = types.Typ[types.Int]
tInvalid = types.Typ[types.Invalid]
tString = types.Typ[types.String]
tUntypedNil = types.Typ[types.UntypedNil]
tEface = types.NewInterfaceType(nil, nil).Complete()
tDeferStack = types.NewPointer(typeutil.NewDeferStack())
vDeferStack = &Builtin{
name: "ssa:deferstack",
sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false),
}
)
// range-over-func jump is READY
func jReady() *Const {
c := intConst(0, nil)
c.comment = "rangefunc.exit.ready"
return c
}
// range-over-func jump is BUSY
func jBusy() *Const {
c := intConst(-1, nil)
c.comment = "rangefunc.exit.busy"
return c
}
// range-over-func jump is DONE
func jDone() *Const {
c := intConst(-2, nil)
c.comment = "rangefunc.exit.done"
return c
}
// builder holds state associated with the package currently being built.
// Its methods contain all the logic for AST-to-IR conversion.
type builder struct {
printFunc string
blocksets [5]BlockSet
}
// cond emits to fn code to evaluate boolean condition e and jump
// to t or f depending on its value, performing various simplifications.
//
// Postcondition: fn.currentBlock is nil.
func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) *If {
switch e := e.(type) {
case *ast.ParenExpr:
return b.cond(fn, e.X, t, f)
case *ast.BinaryExpr:
switch e.Op {
case token.LAND:
ltrue := fn.newBasicBlock("cond.true")
b.cond(fn, e.X, ltrue, f)
fn.currentBlock = ltrue
return b.cond(fn, e.Y, t, f)
case token.LOR:
lfalse := fn.newBasicBlock("cond.false")
b.cond(fn, e.X, t, lfalse)
fn.currentBlock = lfalse
return b.cond(fn, e.Y, t, f)
}
case *ast.UnaryExpr:
if e.Op == token.NOT {
return b.cond(fn, e.X, f, t)
}
}
// A traditional compiler would simplify "if false" (etc) here
// but we do not, for better fidelity to the source code.
//
// The value of a constant condition may be platform-specific,
// and may cause blocks that are reachable in some configuration
// to be hidden from subsequent analyses such as bug-finding tools.
return emitIf(fn, b.expr(fn, e), t, f, e)
}
// logicalBinop emits code to fn to evaluate e, a &&- or
// ||-expression whose reified boolean value is wanted.
// The value is returned.
func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value {
rhs := fn.newBasicBlock("binop.rhs")
done := fn.newBasicBlock("binop.done")
// T(e) = T(e.X) = T(e.Y) after untyped constants have been
// eliminated.
// TODO(adonovan): not true; MyBool==MyBool yields UntypedBool.
t := fn.Pkg.typeOf(e)
var short Value // value of the short-circuit path
switch e.Op {
case token.LAND:
b.cond(fn, e.X, rhs, done)
short = emitConst(fn, NewConst(constant.MakeBool(false), t, e))
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = emitConst(fn, NewConst(constant.MakeBool(true), t, e))
}
// Is rhs unreachable?
if rhs.Preds == nil {
// Simplify false&&y to false, true||y to true.
fn.currentBlock = done
return short
}
// Is done unreachable?
if done.Preds == nil {
// Simplify true&&y (or false||y) to y.
fn.currentBlock = rhs
return b.expr(fn, e.Y)
}
// All edges from e.X to done carry the short-circuit value.
var edges []Value
for range done.Preds {
edges = append(edges, short)
}
// The edge from e.Y to done carries the value of e.Y.
fn.currentBlock = rhs
edges = append(edges, b.expr(fn, e.Y))
emitJump(fn, done, e)
fn.currentBlock = done
phi := &Phi{Edges: edges}
phi.typ = t
phi.comment = e.Op.String()
return done.emit(phi, e)
}
// exprN lowers a multi-result expression e to IR form, emitting code
// to fn and returning a single Value whose type is a *types.Tuple.
// The caller must access the components via Extract.
//
// Multi-result expressions include CallExprs in a multi-value
// assignment or return statement, and "value,ok" uses of
// TypeAssertExpr, IndexExpr (when X is a map), and Recv.
func (b *builder) exprN(fn *Function, e ast.Expr) Value {
typ := fn.Pkg.typeOf(e).(*types.Tuple)
switch e := e.(type) {
case *ast.ParenExpr:
return b.exprN(fn, e.X)
case *ast.CallExpr:
// Currently, no built-in function nor type conversion
// has multiple results, so we can avoid some of the
// cases for single-valued CallExpr.
var c Call
b.setCall(fn, e, &c.Call)
c.typ = typ
return emitCall(fn, &c, e)
case *ast.IndexExpr:
mapt := typeutil.CoreType(fn.Pkg.typeOf(e.X)).Underlying().(*types.Map)
lookup := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e),
CommaOk: true,
}
lookup.setType(typ)
return fn.emit(lookup, e)
case *ast.TypeAssertExpr:
return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e)
case *ast.UnaryExpr: // must be receive <-
return emitRecv(fn, b.expr(fn, e.X), true, typ, e)
}
panic(fmt.Sprintf("exprN(%T) in %s", e, fn))
}
// builtin emits to fn IR instructions to implement a call to the
// built-in function obj with the specified arguments
// and return type. It returns the value defined by the result.
//
// The result is nil if no special handling was required; in this case
// the caller should treat this like an ordinary library function
// call.
func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value {
switch obj.Name() {
case "make":
styp := typ.Underlying()
if _, ok := typ.Underlying().(*types.Interface); ok {
// This must be a type parameter with a core type.
// Set styp to the core type and generate instructions based on it.
assert(typeparams.IsTypeParam(typ))
styp = typeutil.CoreType(typ)
assert(styp != nil)
}
switch styp.(type) {
case *types.Slice:
n := b.expr(fn, args[1])
m := n
if len(args) == 3 {
m = b.expr(fn, args[2])
}
if m, ok := m.(*Const); ok {
// treat make([]T, n, m) as new([m]T)[:n]
cap := m.Int64()
at := types.NewArray(styp.Underlying().(*types.Slice).Elem(), cap)
v := &Slice{
X: emitNew(fn, at, source, "makeslice"),
High: n,
}
v.setType(typ)
return fn.emit(v, source)
}
v := &MakeSlice{
Len: n,
Cap: m,
}
v.setType(typ)
return fn.emit(v, source)
case *types.Map:
var res Value
if len(args) == 2 {
res = b.expr(fn, args[1])
}
v := &MakeMap{Reserve: res}
v.setType(typ)
return fn.emit(v, source)
case *types.Chan:
var sz Value = emitConst(fn, intConst(0, source))
if len(args) == 2 {
sz = b.expr(fn, args[1])
}
v := &MakeChan{Size: sz}
v.setType(typ)
return fn.emit(v, source)
default:
lint.ExhaustiveTypeSwitch(typ.Underlying())
}
case "new":
alloc := emitNew(fn, deref(typ), source, "new")
if !fn.Pkg.info.Types[args[0]].IsType() {
v := b.expr(fn, args[0])
emitStore(fn, alloc, v, source)
}
return alloc
case "len", "cap":
// Special case: len or cap of an array or *array is based on the type, not the value which may be nil. We must
// still evaluate the value, though. (If it was side-effect free, the whole call would have been
// constant-folded.)
//
// For example, for len(gen()), we need to evaluate gen() for its side-effects, but don't need the returned
// value to determine the length of the array, which is constant.
//
// Technically this shouldn't apply to type parameters because their length/capacity is never constant. We still
// choose to treat them as constant so that users of the IR get the practically constant length for free.
t := typeutil.CoreType(deref(fn.Pkg.typeOf(args[0])))
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
return emitConst(fn, intConst(at.Len(), args[0]))
}
// Otherwise treat as normal.
case "panic":
fn.emit(&Panic{
X: emitConv(fn, b.expr(fn, args[0]), tEface, source),
}, source)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = fn.newBasicBlock("unreachable")
return emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil)) // any non-nil Value will do
}
return nil // treat all others as a regular function call
}
// addr lowers a single-result addressable expression e to IR form,
// emitting code to fn and returning the location (an lvalue) defined
// by the expression.
//
// If escaping is true, addr marks the base variable of the
// addressable expression e as being a potentially escaping pointer
// value. For example, in this code:
//
// a := A{
// b: [1]B{B{c: 1}}
// }
// return &a.b[0].c
//
// the application of & causes a.b[0].c to have its address taken,
// which means that ultimately the local variable a must be
// heap-allocated. This is a simple but very conservative escape
// analysis.
//
// Operations forming potentially escaping pointers include:
// - &x, including when implicit in method call or composite literals.
// - a[:] iff a is an array (not *array)
// - references to variables in lexically enclosing functions.
func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) {
switch e := e.(type) {
case *ast.Ident:
if isBlankIdent(e) {
return blank{}
}
obj := fn.Pkg.objectOf(e)
v := fn.Prog.packageLevelValue(obj) // var (address)
if v == nil {
v = fn.lookup(obj.(*types.Var), escaping)
}
return &address{addr: v, expr: e}
case *ast.CompositeLit:
t := deref(fn.Pkg.typeOf(e))
var v *Alloc
if escaping {
v = emitNew(fn, t, e, "complit")
} else {
v = emitLocal(fn, t, e, "complit")
}
var sb storebuf
b.compLit(fn, v, e, true, &sb)
sb.emit(fn)
return &address{addr: v, expr: e}
case *ast.ParenExpr:
return b.addr(fn, e.X, escaping)
case *ast.SelectorExpr:
sel, ok := fn.Pkg.info.Selections[e]
if !ok {
// qualified identifier
return b.addr(fn, e.Sel, escaping)
}
if sel.Kind() != types.FieldVal {
panic(sel)
}
wantAddr := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
index := sel.Index()[len(sel.Index())-1]
vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct)
fld := vut.Field(index)
// Due to the two phases of resolving AssignStmt, a panic from x.f = p()
// when x is nil is required to come after the side-effects of
// evaluating x and p().
emit := func(fn *Function) Value {
return emitFieldSelection(fn, v, index, true, e.Sel)
}
return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel}
case *ast.IndexExpr:
var x Value
var et types.Type
xt := fn.Pkg.typeOf(e.X)
// Indexing doesn't need a core type, it only requires all types to be similar enough. For example, []int64 |
// [5]int64 can be indexed. The element types do have to match though.
terms, err := typeparams.NormalTerms(xt)
if err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
isArrayLike := func() (types.Type, bool) {
for _, term := range terms {
arr, ok := term.Type().Underlying().(*types.Array)
if ok {
return arr.Elem(), true
}
}
return nil, false
}
isSliceLike := func() (types.Type, bool) {
for _, term := range terms {
switch t := term.Type().Underlying().(type) {
case *types.Slice:
return t.Elem(), true
case *types.Pointer:
return t.Elem().Underlying().(*types.Array).Elem(), true
}
}
return nil, false
}
if elem, ok := isArrayLike(); ok {
// array
x = b.addr(fn, e.X, escaping).address(fn)
et = types.NewPointer(elem)
} else if elem, ok := isSliceLike(); ok {
// slice or *array
x = b.expr(fn, e.X)
et = types.NewPointer(elem)
} else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok {
return &element{
m: b.expr(fn, e.X),
k: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index),
t: t.Elem(),
}
} else {
panic("unexpected container type in IndexExpr: " + t.String())
}
// Due to the two phases of resolving AssignStmt, a panic from x[i] = p()
// when x is nil or i is out-of-bounds is required to come after the
// side-effects of evaluating x, i and p().
index := b.expr(fn, e.Index)
emit := func(fn *Function) Value {
v := &IndexAddr{
X: x,
Index: index,
}
v.setType(et)
return fn.emit(v, e)
}
return &lazyAddress{addr: emit, t: deref(et), expr: e}
case *ast.StarExpr:
return &address{addr: b.expr(fn, e.X), expr: e}
}
panic(fmt.Sprintf("unexpected address expression: %T", e))
}
type store struct {
lhs lvalue
rhs Value
source ast.Node
// if debugRef is set no other fields will be set
debugRef *DebugRef
}
type storebuf struct{ stores []store }
func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) {
sb.stores = append(sb.stores, store{lhs, rhs, source, nil})
}
func (sb *storebuf) storeDebugRef(ref *DebugRef) {
sb.stores = append(sb.stores, store{debugRef: ref})
}
func (sb *storebuf) emit(fn *Function) {
for _, s := range sb.stores {
if s.debugRef == nil {
s.lhs.store(fn, s.rhs, s.source)
} else {
fn.emit(s.debugRef, nil)
}
}
}
// assign emits to fn code to initialize the lvalue loc with the value
// of expression e. If isZero is true, assign assumes that loc holds
// the zero value for its type.
//
// This is equivalent to loc.store(fn, b.expr(fn, e)), but may generate
// better code in some cases, e.g., for composite literals in an
// addressable location.
//
// If sb is not nil, assign generates code to evaluate expression e, but
// not to update loc. Instead, the necessary stores are appended to the
// storebuf sb so that they can be executed later. This allows correct
// in-place update of existing variables when the RHS is a composite
// literal that may reference parts of the LHS.
func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) {
// Can we initialize it in place?
if e, ok := unparen(e).(*ast.CompositeLit); ok {
// A CompositeLit never evaluates to a pointer,
// so if the type of the location is a pointer,
// an &-operation is implied.
if _, ok := loc.(blank); !ok { // avoid calling blank.typ()
if isPointer(loc.typ()) {
// Example input that hits this code:
//
// type S1 struct{ X int }
// x := []*S1{
// {1}, // <-- & is implied
// }
// _ = x
ptr := b.addr(fn, e, true).address(fn)
// copy address
if sb != nil {
sb.store(loc, ptr, source)
} else {
loc.store(fn, ptr, source)
}
return
}
}
if _, ok := loc.(*address); ok {
if types.IsInterface(loc.typ()) && !typeparams.IsTypeParam(loc.typ()) {
// e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value.
// Fall back to copying.
} else {
// x = T{...} or x := T{...}
addr := loc.address(fn)
if sb != nil {
b.compLit(fn, addr, e, isZero, sb)
} else {
var sb storebuf
b.compLit(fn, addr, e, isZero, &sb)
sb.emit(fn)
}
// Subtle: emit debug ref for aggregate types only;
// slice and map are handled by store ops in compLit.
switch typeutil.CoreType(loc.typ()).Underlying().(type) {
case *types.Struct, *types.Array:
if sb != nil {
// Make sure we don't emit DebugRefs before the store has actually occurred
if ref := makeDebugRef(fn, e, addr, true); ref != nil {
sb.storeDebugRef(ref)
}
} else {
emitDebugRef(fn, e, addr, true)
}
}
return
}
}
}
// simple case: just copy
rhs := b.expr(fn, e)
if sb != nil {
sb.store(loc, rhs, source)
} else {
loc.store(fn, rhs, source)
}
}
// expr lowers a single-result expression e to IR form, emitting code
// to fn and returning the Value defined by the expression.
func (b *builder) expr(fn *Function, e ast.Expr) Value {
e = unparen(e)
tv := fn.Pkg.info.Types[e]
// Is expression a constant?
if tv.Value != nil {
return emitConst(fn, NewConst(tv.Value, tv.Type, e))
}
var v Value
if tv.Addressable() {
// Prefer pointer arithmetic ({Index,Field}Addr) followed
// by Load over subelement extraction (e.g. Index, Field),
// to avoid large copies.
v = b.addr(fn, e, false).load(fn, e)
} else {
v = b.expr0(fn, e, tv)
}
if fn.debugInfo() {
emitDebugRef(fn, e, v, false)
}
return v
}
func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
switch e := e.(type) {
case *ast.BasicLit:
panic("non-constant BasicLit") // unreachable
case *ast.FuncLit:
fn2 := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature),
parent: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
functionBody: new(functionBody),
goversion: fn.goversion, // share the parent's goversion
}
fn2.uniq = fn.uniq // start from parent's unique values
fn2.source = e
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
fn2.initHTML(b.printFunc)
b.buildFunction(fn2)
fn.uniq = fn2.uniq // resume after anon's unique values
if fn2.FreeVars == nil {
return fn2
}
v := &MakeClosure{Fn: fn2}
v.setType(tv.Type)
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.outer)
fv.outer = nil
}
return fn.emit(v, e)
case *ast.TypeAssertExpr: // single-result form only
return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e)
case *ast.CallExpr:
if fn.Pkg.info.Types[e.Fun].IsType() {
// Explicit type conversion, e.g. string(x) or big.Int(x)
x := b.expr(fn, e.Args[0])
y := emitConv(fn, x, tv.Type, e)
return y
}
// Call to "intrinsic" built-ins, e.g. new, make, panic.
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
if obj, ok := fn.Pkg.info.Uses[id].(*types.Builtin); ok {
if v := b.builtin(fn, obj, e.Args, tv.Type, e); v != nil {
return v
}
}
}
// Regular function call.
var v Call
b.setCall(fn, e, &v.Call)
v.setType(tv.Type)
return emitCall(fn, &v, e)
case *ast.UnaryExpr:
switch e.Op {
case token.AND: // &X --- potentially escaping.
addr := b.addr(fn, e.X, true)
if _, ok := unparen(e.X).(*ast.StarExpr); ok {
// &*p must panic if p is nil (https://golang.org/s/go12nil).
// For simplicity, we'll just (suboptimally) rely
// on the side effects of a load.
// TODO(adonovan): emit dedicated nilcheck.
addr.load(fn, e)
}
return addr.address(fn)
case token.ADD:
return b.expr(fn, e.X)
case token.NOT, token.SUB, token.XOR: // ! <- - ^
v := &UnOp{
Op: e.Op,
X: b.expr(fn, e.X),
}
v.setType(tv.Type)
return fn.emit(v, e)
case token.ARROW:
return emitRecv(fn, b.expr(fn, e.X), false, tv.Type, e)
default:
panic(e.Op)
}
case *ast.BinaryExpr:
switch e.Op {
case token.LAND, token.LOR:
return b.logicalBinop(fn, e)
case token.SHL, token.SHR:
fallthrough
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e)
case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ:
cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e)
// The type of x==y may be UntypedBool.
return emitConv(fn, cmp, types.Default(tv.Type), e)
default:
panic("illegal op in BinaryExpr: " + e.Op.String())
}
case *ast.SliceExpr:
var x Value
if core := typeutil.CoreType(fn.Pkg.typeOf(e.X)); core != nil {
switch core.Underlying().(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).address(fn)
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
panic("unreachable")
}
} else {
// We're indexing a string | []byte. Note that other combinations such as []byte | [4]byte are currently not
// allowed by the language.
x = b.expr(fn, e.X)
}
var low, high, max Value
if e.Low != nil {
low = b.expr(fn, e.Low)
}
if e.High != nil {
high = b.expr(fn, e.High)
}
if e.Slice3 {
max = b.expr(fn, e.Max)
}
v := &Slice{
X: x,
Low: low,
High: high,
Max: max,
}
v.setType(tv.Type)
return fn.emit(v, e)
case *ast.Ident:
obj := fn.Pkg.info.Uses[e]
// Universal built-in or nil?
switch obj := obj.(type) {
case *types.Builtin:
return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)}
case *types.Nil:
return emitConst(fn, nilConst(tv.Type, e))
}
// Package-level func or var?
if v := fn.Prog.packageLevelValue(obj); v != nil {
if _, ok := obj.(*types.Var); ok {
return emitLoad(fn, v, e) // var (address)
}
if instance, ok := fn.Pkg.info.Instances[e]; ok {
// Instantiated generic function
return makeInstance(fn.Prog, v.(*Function), instance.Type.(*types.Signature), instance.TypeArgs)
}
return v // (func)
}
// Local var.
return emitLoad(fn, fn.lookup(obj.(*types.Var), false), e) // var (address)
case *ast.SelectorExpr:
sel, ok := fn.Pkg.info.Selections[e]
if !ok {
// builtin unsafe.{Add,Slice}
if obj, ok := fn.Pkg.info.Uses[e.Sel].(*types.Builtin); ok {
return &Builtin{name: "Unsafe" + obj.Name(), sig: tv.Type.(*types.Signature)}
}
// qualified identifier
return b.expr(fn, e.Sel)
}
switch sel.Kind() {
case types.MethodExpr:
// (*T).f or T.f, the method f from the method-set of type T.
// The result is a "thunk".
return emitConv(fn, makeThunk(fn.Prog, sel), tv.Type, e)
case types.MethodVal:
// e.f where e is an expression and f is a method.
// The result is a "bound".
obj := sel.Obj().(*types.Func)
rt := recvType(obj)
wantAddr := isPointer(rt)
escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
if types.IsInterface(rt) {
// If v has interface type I,
// we must emit a check that v is non-nil.
// We use: typeassert v.(I).
emitTypeAssert(fn, v, rt, e)
}
c := &MakeClosure{
Fn: makeBound(fn.Prog, obj),
Bindings: []Value{v},
}
c.source = e.Sel
c.setType(tv.Type)
return fn.emit(c, e)
case types.FieldVal:
indices := sel.Index()
last := len(indices) - 1
v := b.expr(fn, e.X)
v = emitImplicitSelections(fn, v, indices[:last], e)
v = emitFieldSelection(fn, v, indices[last], false, e.Sel)
return v
}
panic("unexpected expression-relative selector")
case *ast.IndexExpr:
// IndexExpr might either be an actual indexing operation, or an instantiation
xt := fn.Pkg.typeOf(e.X)
terms, err := typeparams.NormalTerms(xt)
if err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
isNonAddressableIndexable := func() (types.Type, bool) {
for _, term := range terms {
switch t := term.Type().Underlying().(type) {
case *types.Array:
return t.Elem(), true
case *types.Basic:
// a string
return types.Universe.Lookup("byte").Type(), true
}
}
return nil, false
}
isAddressableIndexable := func() (types.Type, bool) {
for _, term := range terms {
switch t := term.Type().Underlying().(type) {
case *types.Slice:
return t.Elem(), true
case *types.Pointer:
return t.Elem().Underlying().(*types.Array).Elem(), true
}
}
return nil, false
}
if elem, ok := isNonAddressableIndexable(); ok {
// At least one of the types is non-addressable
v := &Index{
X: b.expr(fn, e.X),
Index: b.expr(fn, e.Index),
}
v.setType(elem)
return fn.emit(v, e)
} else if _, ok := isAddressableIndexable(); ok {
// All types are addressable (otherwise the previous branch would've fired)
return b.addr(fn, e, false).load(fn, e)
} else if t, ok := typeutil.CoreType(xt).Underlying().(*types.Map); ok {
// Maps are not addressable.
v := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index),
}
v.setType(t.Elem())
return fn.emit(v, e)
} else if _, ok := xt.Underlying().(*types.Signature); ok {
// Instantiating a generic function
return b.expr(fn, e.X)
} else {
panic("unexpected container type in IndexExpr: " + t.String())
}
case *ast.IndexListExpr:
// Instantiating a generic function
return b.expr(fn, e.X)
case *ast.CompositeLit, *ast.StarExpr:
// Addressable types (lvalues)
return b.addr(fn, e, false).load(fn, e)
}
panic(fmt.Sprintf("unexpected expr: %T", e))
}
// stmtList emits to fn code for all statements in list.
func (b *builder) stmtList(fn *Function, list []ast.Stmt) {
for _, s := range list {
b.stmt(fn, s)
}
}
// receiver emits to fn code for expression e in the "receiver"
// position of selection e.f (where f may be a field or a method) and
// returns the effective receiver after applying the implicit field
// selections of sel.
//
// wantAddr requests that the result is an address. If
// !sel.Indirect(), this may require that e be built in addr() mode; it
// must thus be addressable.
//
// escaping is defined as per builder.addr().
func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection, source ast.Node) Value {
var v Value
if wantAddr && !sel.Indirect() && !isPointer(fn.Pkg.typeOf(e)) {
v = b.addr(fn, e, escaping).address(fn)
} else {
v = b.expr(fn, e)
}
last := len(sel.Index()) - 1
v = emitImplicitSelections(fn, v, sel.Index()[:last], source)
if !wantAddr && isPointer(v.Type()) {
v = emitLoad(fn, v, e)
}
return v
}
// setCallFunc populates the function parts of a CallCommon structure
// (Func, Method, Recv, Args[0]) based on the kind of invocation
// occurring in e.
func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
// Is this a method call?
if selector, ok := unparen(e.Fun).(*ast.SelectorExpr); ok {
sel, ok := fn.Pkg.info.Selections[selector]
if ok && sel.Kind() == types.MethodVal {
obj := sel.Obj().(*types.Func)
recv := recvType(obj)
wantAddr := isPointer(recv)
escaping := true
v := b.receiver(fn, selector.X, wantAddr, escaping, sel, selector)
if types.IsInterface(recv) {
// Invoke-mode call.
// Methods in interfaces cannot have their own type parameters, so we needn't do anything for type
// parameters.
c.Value = v
c.Method = obj
} else {
// "Call"-mode call.
// declaredFunc takes care of creating wrappers for functions with type parameters.
c.Value = fn.Prog.declaredFunc(obj)
c.Args = append(c.Args, v)
}
return
}
// sel.Kind()==MethodExpr indicates T.f() or (*T).f():
// a statically dispatched call to the method f in the
// method-set of T or *T. T may be an interface.
//
// e.Fun would evaluate to a concrete method, interface
// wrapper function, or promotion wrapper.
//
// For now, we evaluate it in the usual way.
//
// TODO(adonovan): opt: inline expr() here, to make the
// call static and to avoid generation of wrappers.
// It's somewhat tricky as it may consume the first
// actual parameter if the call is "invoke" mode.
//
// Examples:
// type T struct{}; func (T) f() {} // "call" mode
// type T interface { f() } // "invoke" mode
//
// type S struct{ T }
//
// var s S
// S.f(s)
// (*S).f(&s)
//
// Suggested approach:
// - consume the first actual parameter expression
// and build it with b.expr().
// - apply implicit field selections.
// - use MethodVal logic to populate fields of c.
}
// Evaluate the function operand in the usual way.
//
// Code in expr takes care of creating wrappers for functions with type parameters.
c.Value = b.expr(fn, e.Fun)
}
// emitCallArgs emits to f code for the actual parameters of call e to
// a (possibly built-in) function of effective type sig.
// The argument values are appended to args, which is then returned.
func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallExpr, args []Value) []Value {
// f(x, y, z...): pass slice z straight through.
if e.Ellipsis != 0 {
for i, arg := range e.Args {
v := emitConv(fn, b.expr(fn, arg), sig.Params().At(i).Type(), arg)
args = append(args, v)
}
return args
}
offset := len(args) // 1 if call has receiver, 0 otherwise
// Evaluate actual parameter expressions.
//
// If this is a chained call of the form f(g()) where g has
// multiple return values (MRV), they are flattened out into
// args; a suffix of them may end up in a varargs slice.
for _, arg := range e.Args {
v := b.expr(fn, arg)
if ttuple, ok := v.Type().(*types.Tuple); ok { // MRV chain
for i, n := 0, ttuple.Len(); i < n; i++ {
args = append(args, emitExtract(fn, v, i, arg))
}
} else {
args = append(args, v)
}
}
// Actual->formal assignability conversions for normal parameters.
np := sig.Params().Len() // number of normal parameters
if sig.Variadic() {
np--
}
for i := 0; i < np; i++ {
args[offset+i] = emitConv(fn, args[offset+i], sig.Params().At(i).Type(), args[offset+i].Source())
}
// Actual->formal assignability conversions for variadic parameter,
// and construction of slice.
if sig.Variadic() {
varargs := args[offset+np:]
st := sig.Params().At(np).Type().(*types.Slice)
vt := st.Elem()
if len(varargs) == 0 {
args = append(args, emitConst(fn, nilConst(st, nil)))
} else {
// Replace a suffix of args with a slice containing it.
at := types.NewArray(vt, int64(len(varargs)))
a := emitNew(fn, at, e, "varargs")
a.source = e
for i, arg := range varargs {
iaddr := &IndexAddr{
X: a,
Index: emitConst(fn, intConst(int64(i), nil)),
}
iaddr.setType(types.NewPointer(vt))
fn.emit(iaddr, e)
emitStore(fn, iaddr, arg, arg.Source())
}
s := &Slice{X: a}
s.setType(st)
args[offset+np] = fn.emit(s, args[offset+np].Source())
args = args[:offset+np+1]
}
}
return args
}
// setCall emits to fn code to evaluate all the parameters of a function
// call e, and populates *c with those values.
func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
// First deal with the f(...) part and optional receiver.
b.setCallFunc(fn, e, c)
// Then append the other actual parameters.
sig, _ := typeutil.CoreType(fn.Pkg.typeOf(e.Fun)).(*types.Signature)
if sig == nil {
panic(fmt.Sprintf("no signature for call of %s", e.Fun))
}
c.Args = b.emitCallArgs(fn, sig, e, c.Args)
}
// assignOp emits to fn code to perform loc = val.
func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, source ast.Node) {
loc.store(fn, emitArith(fn, op, loc.load(fn, source), val, loc.typ(), source), source)
}
// localValueSpec emits to fn code to define all of the vars in the
// function-local ValueSpec, spec.
func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
switch {
case len(spec.Values) == len(spec.Names):
// e.g. var x, y = 0, 1
// 1:1 assignment
for i, id := range spec.Names {
if !isBlankIdent(id) {
emitLocalVar(fn, identVar(fn, id), id)
}
lval := b.addr(fn, id, false) // non-escaping
b.assign(fn, lval, spec.Values[i], true, nil, spec)
}
case len(spec.Values) == 0:
// e.g. var x, y int
// Locals are implicitly zero-initialized.
for _, id := range spec.Names {
if !isBlankIdent(id) {
lhs := emitLocalVar(fn, identVar(fn, id), id)
if fn.debugInfo() {
emitDebugRef(fn, id, lhs, true)
}
}
}
default:
// e.g. var x, y = pos()
tuple := b.exprN(fn, spec.Values[0])
for i, id := range spec.Names {
if !isBlankIdent(id) {
emitLocalVar(fn, identVar(fn, id), id)
lhs := b.addr(fn, id, false) // non-escaping
lhs.store(fn, emitExtract(fn, tuple, i, id), id)
}
}
}
}
// assignStmt emits code to fn for a parallel assignment of rhss to lhss.
// isDef is true if this is a short variable declaration (:=).
//
// Note the similarity with localValueSpec.
func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool, source ast.Node) {
// Side effects of all LHSs and RHSs must occur in left-to-right order.
lvals := make([]lvalue, len(lhss))
isZero := make([]bool, len(lhss))
for i, lhs := range lhss {
var lval lvalue = blank{}
if !isBlankIdent(lhs) {
if isDef {
if obj, ok := fn.Pkg.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok {
emitLocalVar(fn, obj, lhs)
isZero[i] = true
}
}
lval = b.addr(fn, lhs, false) // non-escaping
}
lvals[i] = lval
}
if len(lhss) == len(rhss) {
// Simple assignment: x = f() (!isDef)
// Parallel assignment: x, y = f(), g() (!isDef)
// or short var decl: x, y := f(), g() (isDef)
//
// In all cases, the RHSs may refer to the LHSs,
// so we need a storebuf.
var sb storebuf
for i := range rhss {
b.assign(fn, lvals[i], rhss[i], isZero[i], &sb, source)
}
sb.emit(fn)
} else {
// e.g. x, y = pos()
tuple := b.exprN(fn, rhss[0])
emitDebugRef(fn, rhss[0], tuple, false)
for i, lval := range lvals {
lval.store(fn, emitExtract(fn, tuple, i, source), source)
}
}
}
// arrayLen returns the length of the array whose composite literal elements are elts.
func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 {
var max int64 = -1
var i int64 = -1
for _, e := range elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
i = b.expr(fn, kv.Key).(*Const).Int64()
} else {
i++
}
if i > max {
max = i
}
}
return max + 1
}
// compLit emits to fn code to initialize a composite literal e at
// address addr with type typ.
//
// Nested composite literals are recursively initialized in place
// where possible. If isZero is true, compLit assumes that addr
// holds the zero value for typ.
//
// Because the elements of a composite literal may refer to the
// variables being updated, as in the second line below,
//
// x := T{a: 1}
// x = T{a: x.a}
//
// all the reads must occur before all the writes. This is implicitly handled by the write buffering effected by
// compositeElement and explicitly by the storebuf for when we don't use CompositeValue.
//
// A CompositeLit may have pointer type only in the recursive (nested)
// case when the type name is implicit. e.g. in []*T{{}}, the inner
// literal has type *T behaves like &T{}.
// In that case, addr must hold a T, not a *T.
func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) {
typ := deref(fn.Pkg.typeOf(e))
switch t := typeutil.CoreType(typ).(type) {
case *types.Struct:
lvalue := &address{addr: addr, expr: e}
if len(e.Elts) == 0 {
if !isZero {
sb.store(lvalue, zeroValue(fn, deref(addr.Type()), e), e)
}
} else {
v := &CompositeValue{
Values: make([]Value, t.NumFields()),
}
for i := 0; i < t.NumFields(); i++ {
v.Values[i] = emitConst(fn, zeroConst(t.Field(i).Type(), e))
}
v.setType(typ)
for i, e := range e.Elts {
fieldIndex := i
if kv, ok := e.(*ast.KeyValueExpr); ok {
fname := kv.Key.(*ast.Ident).Name
for i, n := 0, t.NumFields(); i < n; i++ {
sf := t.Field(i)
if sf.Name() == fname {
fieldIndex = i
e = kv.Value
break
}
}
}
ce := &compositeElement{
cv: v,
idx: fieldIndex,
t: t.Field(fieldIndex).Type(),
expr: e,
}
b.assign(fn, ce, e, isZero, sb, e)
v.Bitmap.SetBit(&v.Bitmap, fieldIndex, 1)
v.NumSet++
}
fn.emit(v, e)
sb.store(lvalue, v, e)
}
case *types.Array, *types.Slice:
var at *types.Array
var array Value
switch t := t.(type) {
case *types.Slice:
at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts))
array = emitNew(fn, at, e, "slicelit")
case *types.Array:
at = t
array = addr
}
var final Value
if len(e.Elts) == 0 {
if !isZero {
zc := emitConst(fn, zeroConst(at, e))
final = zc
}
} else {
if at.Len() == int64(len(e.Elts)) {
// The literal specifies all elements, so we can use a composite value
v := &CompositeValue{
Values: make([]Value, at.Len()),
}
zc := emitConst(fn, zeroConst(at.Elem(), e))
for i := range v.Values {
v.Values[i] = zc
}
v.setType(at)
var idx *Const
for _, e := range e.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
idx = b.expr(fn, kv.Key).(*Const)
e = kv.Value
} else {
var idxval int64
if idx != nil {
idxval = idx.Int64() + 1
}
idx = emitConst(fn, intConst(idxval, e)).(*Const)
}
iaddr := &compositeElement{
cv: v,
idx: int(idx.Int64()),
t: at.Elem(),
expr: e,
}
b.assign(fn, iaddr, e, true, sb, e)
v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1)
v.NumSet++
}
final = v
fn.emit(v, e)
} else {
// Not all elements are specified. Populate the array with a series of stores, to guard against literals
// like []int{1<<62: 1}.
if !isZero {
// memclear
sb.store(&address{array, nil}, zeroValue(fn, deref(array.Type()), e), e)
}
var idx *Const
for _, e := range e.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
idx = b.expr(fn, kv.Key).(*Const)
e = kv.Value
} else {
var idxval int64
if idx != nil {
idxval = idx.Int64() + 1
}
idx = emitConst(fn, intConst(idxval, e)).(*Const)
}
iaddr := &IndexAddr{
X: array,
Index: idx,
}
iaddr.setType(types.NewPointer(at.Elem()))
fn.emit(iaddr, e)
if t != at { // slice
// backing array is unaliased => storebuf not needed.
b.assign(fn, &address{addr: iaddr, expr: e}, e, true, nil, e)
} else {
b.assign(fn, &address{addr: iaddr, expr: e}, e, true, sb, e)
}
}
}
}
if t != at { // slice
if final != nil {
sb.store(&address{addr: array}, final, e)
}
s := &Slice{X: array}
s.setType(typ)
sb.store(&address{addr: addr, expr: e}, fn.emit(s, e), e)
} else if final != nil {
sb.store(&address{addr: array, expr: e}, final, e)
}
case *types.Map:
m := &MakeMap{Reserve: emitConst(fn, intConst(int64(len(e.Elts)), e))}
m.setType(typ)
fn.emit(m, e)
for _, e := range e.Elts {
e := e.(*ast.KeyValueExpr)
// If a key expression in a map literal is itself a
// composite literal, the type may be omitted.
// For example:
// map[*struct{}]bool{{}: true}
// An &-operation may be implied:
// map[*struct{}]bool{&struct{}{}: true}
var key Value
if _, ok := unparen(e.Key).(*ast.CompositeLit); ok && isPointer(t.Key()) {
// A CompositeLit never evaluates to a pointer,
// so if the type of the location is a pointer,
// an &-operation is implied.
key = b.addr(fn, e.Key, true).address(fn)
} else {
key = b.expr(fn, e.Key)
}
loc := element{
m: m,
k: emitConv(fn, key, t.Key(), e),
t: t.Elem(),
}
// We call assign() only because it takes care
// of any &-operation required in the recursive
// case, e.g.,
// map[int]*struct{}{0: {}} implies &struct{}{}.
// In-place update is of course impossible,
// and no storebuf is needed.
b.assign(fn, &loc, e.Value, true, nil, e)
}
sb.store(&address{addr: addr, expr: e}, m, e)
default:
panic("unexpected CompositeLit type: " + t.String())
}
}
func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) {
if s.Tag == nil {
b.switchStmtDynamic(fn, s, label)
return
}
dynamic := false
for _, iclause := range s.Body.List {
clause := iclause.(*ast.CaseClause)
for _, cond := range clause.List {
if fn.Pkg.info.Types[unparen(cond)].Value == nil {
dynamic = true
break
}
}
}
if dynamic {
b.switchStmtDynamic(fn, s, label)
return
}
if s.Init != nil {
b.stmt(fn, s.Init)
}
entry := fn.currentBlock
tag := b.expr(fn, s.Tag)
heads := make([]*BasicBlock, 0, len(s.Body.List))
bodies := make([]*BasicBlock, len(s.Body.List))
conds := make([]Value, 0, len(s.Body.List))
hasDefault := false
done := fn.newBasicBlock("switch.done")
if label != nil {
label._break = done
}
for i, stmt := range s.Body.List {
body := fn.newBasicBlock(fmt.Sprintf("switch.body.%d", i))
bodies[i] = body
cas := stmt.(*ast.CaseClause)
if cas.List == nil {
// default branch
hasDefault = true
head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d", i))
conds = append(conds, nil)
heads = append(heads, head)
fn.currentBlock = head
emitJump(fn, body, cas)
}
for j, cond := range stmt.(*ast.CaseClause).List {
fn.currentBlock = entry
head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d.%d", i, j))
conds = append(conds, b.expr(fn, cond))
heads = append(heads, head)
fn.currentBlock = head
emitJump(fn, body, cond)
}
}
for i, stmt := range s.Body.List {
clause := stmt.(*ast.CaseClause)
body := bodies[i]
fn.currentBlock = body
fallthru := done
if i+1 < len(bodies) {
fallthru = bodies[i+1]
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, stmt)
}
if !hasDefault {
head := fn.newBasicBlock("switch.head.implicit-default")
body := fn.newBasicBlock("switch.body.implicit-default")
fn.currentBlock = head
emitJump(fn, body, s)
fn.currentBlock = body
emitJump(fn, done, s)
heads = append(heads, head)
conds = append(conds, nil)
}
if len(heads) != len(conds) {
panic(fmt.Sprintf("internal error: %d heads for %d conds", len(heads), len(conds)))
}
for _, head := range heads {
addEdge(entry, head)
}
fn.currentBlock = entry
entry.emit(&ConstantSwitch{
Tag: tag,
Conds: conds,
}, s)
fn.currentBlock = done
}
// switchStmt emits to fn code for the switch statement s, optionally
// labelled by label.
func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblock) {
// We treat SwitchStmt like a sequential if-else chain.
// Multiway dispatch can be recovered later by irutil.Switches()
// to those cases that are free of side effects.
if s.Init != nil {
b.stmt(fn, s.Init)
}
kTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil))
var tagv Value = kTrue
var tagSource ast.Node = s
if s.Tag != nil {
tagv = b.expr(fn, s.Tag)
tagSource = s.Tag
}
// lifting only considers loads and stores, but we want different
// sigma nodes for the different comparisons. use a temporary and
// load it in every branch.
tag := emitLocal(fn, tagv.Type(), tagSource, "switch.value")
tag.comment = "switch.tag"
emitStore(fn, tag, tagv, tagSource)
done := fn.newBasicBlock("switch.done")
if label != nil {
label._break = done
}
// We pull the default case (if present) down to the end.
// But each fallthrough label must point to the next
// body block in source order, so we preallocate a
// body block (fallthru) for the next case.
// Unfortunately this makes for a confusing block order.
var dfltBody *[]ast.Stmt
var dfltFallthrough *BasicBlock
var fallthru, dfltBlock *BasicBlock
ncases := len(s.Body.List)
for i, clause := range s.Body.List {
body := fallthru
if body == nil {
body = fn.newBasicBlock("switch.body") // first case only
}
// Preallocate body block for the next case.
fallthru = done
if i+1 < ncases {
fallthru = fn.newBasicBlock("switch.body")
}
cc := clause.(*ast.CaseClause)
if cc.List == nil {
// Default case.
dfltBody = &cc.Body
dfltFallthrough = fallthru
dfltBlock = body
continue
}
var nextCond *BasicBlock
for _, cond := range cc.List {
nextCond = fn.newBasicBlock("switch.next")
if tagv == kTrue {
// emit a proper if/else chain instead of a comparison
// of a value against true.
//
// NOTE(dh): adonovan had a todo saying "don't forget
// conversions though". As far as I can tell, there
// aren't any conversions that we need to take care of
// here. `case bool(a) && bool(b)` as well as `case
// bool(a && b)` are being taken care of by b.cond,
// and `case a` where a is not of type bool is
// invalid.
b.cond(fn, cond, body, nextCond)
} else {
cond := emitCompare(fn, token.EQL, emitLoad(fn, tag, cond), b.expr(fn, cond), cond)
emitIf(fn, cond, body, nextCond, cond.Source())
}
fn.currentBlock = nextCond
}
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
fn.currentBlock = nextCond
}
if dfltBlock != nil {
// The lack of a Source for the jump doesn't matter, block
// fusing will get rid of the jump later.
emitJump(fn, dfltBlock, s)
fn.currentBlock = dfltBlock
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: dfltFallthrough,
}
b.stmtList(fn, *dfltBody)
fn.targets = fn.targets.tail
}
emitJump(fn, done, s)
fn.currentBlock = done
}
func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) {
if s.Init != nil {
b.stmt(fn, s.Init)
}
var tag Value
switch e := s.Assign.(type) {
case *ast.ExprStmt: // x.(type)
tag = b.expr(fn, unparen(e.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type)
tag = b.expr(fn, unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X)
default:
panic("unreachable")
}
tagPtr := emitLocal(fn, tag.Type(), tag.Source(), "")
emitStore(fn, tagPtr, tag, tag.Source())
// +1 in case there's no explicit default case
heads := make([]*BasicBlock, 0, len(s.Body.List)+1)
entry := fn.currentBlock
done := fn.newBasicBlock("done")
if label != nil {
label._break = done
}
// set up type switch and constant switch, populate their conditions
tswtch := &TypeSwitch{
Tag: emitLoad(fn, tagPtr, tag.Source()),
Conds: make([]types.Type, 0, len(s.Body.List)+1),
}
cswtch := &ConstantSwitch{
Conds: make([]Value, 0, len(s.Body.List)+1),
}
rets := make([]types.Type, 0, len(s.Body.List)+1)
index := 0
var default_ *ast.CaseClause
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if obj, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok {
emitLocalVar(fn, obj, cc)
}
if cc.List == nil {
// default case
default_ = cc
} else {
for _, expr := range cc.List {
tswtch.Conds = append(tswtch.Conds, fn.Pkg.typeOf(expr))
cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(index), expr)))
index++
}
if len(cc.List) == 1 {
rets = append(rets, fn.Pkg.typeOf(cc.List[0]))
} else {
for range cc.List {
rets = append(rets, tag.Type())
}
}
}
}
// default branch
rets = append(rets, tag.Type())
var vars []*types.Var
vars = append(vars, varIndex)
for _, typ := range rets {
vars = append(vars, anonVar(typ))
}
tswtch.setType(types.NewTuple(vars...))
// default branch
fn.currentBlock = entry
fn.emit(tswtch, s)
cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(-1), nil)))
// in theory we should add a local and stores/loads for tswtch, to
// generate sigma nodes in the branches. however, there isn't any
// useful information we could possibly attach to it.
cswtch.Tag = emitExtract(fn, tswtch, 0, s)
fn.emit(cswtch, s)
// build heads and bodies
index = 0
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if cc.List == nil {
continue
}
body := fn.newBasicBlock("typeswitch.body")
for _, expr := range cc.List {
head := fn.newBasicBlock("typeswitch.head")
heads = append(heads, head)
fn.currentBlock = head
if obj, ok := fn.Pkg.info.Implicits[cc].(*types.Var); ok {
// In a switch y := x.(type), each case clause
// implicitly declares a distinct object y.
// In a single-type case, y has that type.
// In multi-type cases, 'case nil' and default,
// y has the same type as the interface operand.
l := fn.vars[obj]
if rets[index] == tUntypedNil {
emitStore(fn, l, emitConst(fn, nilConst(tswtch.Tag.Type(), nil)), s.Assign)
} else {
x := emitExtract(fn, tswtch, index+1, s.Assign)
emitStore(fn, l, x, nil)
}
}
emitJump(fn, body, expr)
index++
}
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, clause)
}
if default_ == nil {
// implicit default
heads = append(heads, done)
} else {
body := fn.newBasicBlock("typeswitch.default")
heads = append(heads, body)
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
if obj, ok := fn.Pkg.info.Implicits[default_].(*types.Var); ok {
l := fn.vars[obj]
x := emitExtract(fn, tswtch, index+1, s.Assign)
emitStore(fn, l, x, s)
}
b.stmtList(fn, default_.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
}
fn.currentBlock = entry
for _, head := range heads {
addEdge(entry, head)
}
fn.currentBlock = done
}
// selectStmt emits to fn code for the select statement s, optionally
// labelled by label.
func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (noreturn bool) {
if len(s.Body.List) == 0 {
instr := &Select{Blocking: true}
instr.setType(types.NewTuple(varIndex, varOk))
fn.emit(instr, s)
fn.emit(new(Unreachable), s)
addEdge(fn.currentBlock, fn.Exit)
return true
}
// A blocking select of a single case degenerates to a
// simple send or receive.
// TODO(adonovan): opt: is this optimization worth its weight?
if len(s.Body.List) == 1 {
clause := s.Body.List[0].(*ast.CommClause)
if clause.Comm != nil {
b.stmt(fn, clause.Comm)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, clause)
fn.currentBlock = done
return false
}
}
// First evaluate all channels in all cases, and find
// the directions of each state.
var states []*SelectState
blocking := true
debugInfo := fn.debugInfo()
for _, clause := range s.Body.List {
var st *SelectState
switch comm := clause.(*ast.CommClause).Comm.(type) {
case nil: // default case
blocking = false
continue
case *ast.SendStmt: // ch<- i
ch := b.expr(fn, comm.Chan)
st = &SelectState{
Dir: types.SendOnly,
Chan: ch,
Send: emitConv(fn, b.expr(fn, comm.Value),
typeutil.CoreType(ch.Type()).Underlying().(*types.Chan).Elem(), comm),
Pos: comm.Arrow,
}
if debugInfo {
st.DebugNode = comm
}
case *ast.AssignStmt: // x := <-ch
recv := unparen(comm.Rhs[0]).(*ast.UnaryExpr)
st = &SelectState{
Dir: types.RecvOnly,
Chan: b.expr(fn, recv.X),
Pos: recv.OpPos,
}
if debugInfo {
st.DebugNode = recv
}
case *ast.ExprStmt: // <-ch
recv := unparen(comm.X).(*ast.UnaryExpr)
st = &SelectState{
Dir: types.RecvOnly,
Chan: b.expr(fn, recv.X),
Pos: recv.OpPos,
}
if debugInfo {
st.DebugNode = recv
}
}
states = append(states, st)
}
// We dispatch on the (fair) result of Select using a
// switch on the returned index.
sel := &Select{
States: states,
Blocking: blocking,
}
sel.source = s
var vars []*types.Var
vars = append(vars, varIndex, varOk)
for _, st := range states {
if st.Dir == types.RecvOnly {
tElem := typeutil.CoreType(st.Chan.Type()).Underlying().(*types.Chan).Elem()
vars = append(vars, anonVar(tElem))
}
}
sel.setType(types.NewTuple(vars...))
fn.emit(sel, s)
idx := emitExtract(fn, sel, 0, s)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
entry := fn.currentBlock
swtch := &ConstantSwitch{
Tag: idx,
// one condition per case
Conds: make([]Value, 0, len(s.Body.List)+1),
}
// note that we don't need heads; a select case can only have a single condition
var bodies []*BasicBlock
state := 0
r := 2 // index in 'sel' tuple of value; increments if st.Dir==RECV
for _, cc := range s.Body.List {
clause := cc.(*ast.CommClause)
if clause.Comm == nil {
body := fn.newBasicBlock("select.default")
fn.currentBlock = body
bodies = append(bodies, body)
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, clause.Body)
emitJump(fn, done, s)
fn.targets = fn.targets.tail
swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(-1, nil)))
continue
}
swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(int64(state), nil)))
body := fn.newBasicBlock("select.body")
fn.currentBlock = body
bodies = append(bodies, body)
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
switch comm := clause.Comm.(type) {
case *ast.ExprStmt: // <-ch
if debugInfo {
v := emitExtract(fn, sel, r, comm)
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
}
r++
case *ast.AssignStmt: // x := <-states[state].Chan
if comm.Tok == token.DEFINE {
id := comm.Lhs[0].(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
x := b.addr(fn, comm.Lhs[0], false) // non-escaping
v := emitExtract(fn, sel, r, comm)
if debugInfo {
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
}
x.store(fn, v, comm)
if len(comm.Lhs) == 2 { // x, ok := ...
if comm.Tok == token.DEFINE {
id := comm.Lhs[1].(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
ok := b.addr(fn, comm.Lhs[1], false) // non-escaping
ok.store(fn, emitExtract(fn, sel, 1, comm), comm)
}
r++
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done, s)
state++
}
fn.currentBlock = entry
fn.emit(swtch, s)
for _, body := range bodies {
addEdge(entry, body)
}
fn.currentBlock = done
return false
}
// forStmt emits to fn code for the for statement s, optionally
// labelled by label.
func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
// Use forStmtGo122 instead if it applies.
if s.Init != nil {
if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
if version.Compare(fn.goversion, "go1.22") >= 0 {
b.forStmtGo122(fn, s, label)
return
}
}
}
// ...init...
// jump loop
// loop:
// if cond goto body else done
// body:
// ...body...
// jump post
// post: (target of continue)
// ...post...
// jump loop
// done: (target of break)
if s.Init != nil {
b.stmt(fn, s.Init)
}
body := fn.newBasicBlock("for.body")
done := fn.newBasicBlock("for.done") // target of 'break'
loop := body // target of back-edge
if s.Cond != nil {
loop = fn.newBasicBlock("for.loop")
}
cont := loop // target of 'continue'
if s.Post != nil {
cont = fn.newBasicBlock("for.post")
}
if label != nil {
label._break = done
label._continue = cont
}
emitJump(fn, loop, s)
fn.currentBlock = loop
if loop != body {
b.cond(fn, s.Cond, body, done)
fn.currentBlock = body
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: cont,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, cont, s)
if s.Post != nil {
fn.currentBlock = cont
b.stmt(fn, s.Post)
emitJump(fn, loop, s) // back-edge
}
fn.currentBlock = done
}
// forStmtGo122 emits to fn code for the for statement s, optionally
// labelled by label. s must define its variables.
//
// This allocates once per loop iteration. This is only correct in
// GoVersions >= go1.22.
func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) {
// i_outer = alloc[T]
// *i_outer = ...init... // under objects[i] = i_outer
// jump loop
// loop:
// i = phi [head: i_outer, loop: i_next]
// ...cond... // under objects[i] = i
// if cond goto body else done
// body:
// ...body... // under objects[i] = i (same as loop)
// jump post
// post:
// tmp = *i
// i_next = alloc[T]
// *i_next = tmp
// ...post... // under objects[i] = i_next
// goto loop
// done:
init := s.Init.(*ast.AssignStmt)
startingBlocks := len(fn.Blocks)
pre := fn.currentBlock // current block before starting
loop := fn.newBasicBlock("for.loop") // target of back-edge
body := fn.newBasicBlock("for.body")
post := fn.newBasicBlock("for.post") // target of 'continue'
done := fn.newBasicBlock("for.done") // target of 'break'
// For each of the n loop variables, we create five SSA values,
// outer, phi, next, load, and store in pre, loop, and post.
// There is no limit on n.
type loopVar struct {
obj *types.Var
outer *Alloc
phi *Phi
load *Load
next *Alloc
store *Store
}
vars := make([]loopVar, len(init.Lhs))
for i, lhs := range init.Lhs {
v := identVar(fn, lhs.(*ast.Ident))
fn.currentBlock = pre
outer := emitLocal(fn, v.Type(), lhs, v.Name())
fn.currentBlock = loop
phi := &Phi{}
phi.comment = v.Name()
phi.typ = outer.Type()
fn.emit(phi, lhs)
fn.currentBlock = post
// If next is local, it reuses the address and zeroes the old value so
// load before allocating next.
load := emitLoad(fn, phi, init)
next := emitLocal(fn, v.Type(), lhs, v.Name())
store := emitStore(fn, next, load, s)
phi.Edges = []Value{outer, next} // pre edge is emitted before post edge.
vars[i] = loopVar{v, outer, phi, load, next, store}
}
// ...init... under fn.objects[v] = i_outer
fn.currentBlock = pre
for _, v := range vars {
fn.vars[v.obj] = v.outer
}
const isDef = false // assign to already-allocated outers
b.assignStmt(fn, init.Lhs, init.Rhs, isDef, s)
if label != nil {
label._break = done
label._continue = post
}
emitJump(fn, loop, s)
// ...cond... under fn.objects[v] = i
fn.currentBlock = loop
for _, v := range vars {
fn.vars[v.obj] = v.phi
}
if s.Cond != nil {
b.cond(fn, s.Cond, body, done)
} else {
emitJump(fn, body, s)
}
// ...body... under fn.objects[v] = i
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: post,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, post, s)
// ...post... under fn.objects[v] = i_next
for _, v := range vars {
fn.vars[v.obj] = v.next
}
fn.currentBlock = post
if s.Post != nil {
b.stmt(fn, s.Post)
}
emitJump(fn, loop, s) // back-edge
fn.currentBlock = done
// For each loop variable that does not escape,
// (the common case), fuse its next cells into its
// (local) outer cell as they have disjoint live ranges.
//
// It is sufficient to test whether i_next escapes,
// because its Heap flag will be marked true if either
// the cond or post expression causes i to escape
// (because escape distributes over phi).
var nlocals int
for _, v := range vars {
if !v.next.Heap {
nlocals++
}
}
if nlocals > 0 {
replace := make(map[Value]Value, 2*nlocals)
dead := make(map[Instruction]bool, 4*nlocals)
for _, v := range vars {
if !v.next.Heap {
replace[v.next] = v.outer
replace[v.phi] = v.outer
dead[v.phi], dead[v.next], dead[v.load], dead[v.store] = true, true, true, true
}
}
// Replace all uses of i_next and phi with i_outer.
// Referrers have not been built for fn yet so only update Instruction operands.
// We need only look within the blocks added by the loop.
var operands []*Value // recycle storage
for _, b := range fn.Blocks[startingBlocks:] {
for _, instr := range b.Instrs {
operands = instr.Operands(operands[:0])
for _, ptr := range operands {
k := *ptr
if v := replace[k]; v != nil {
*ptr = v
}
}
}
}
// Remove instructions for phi, load, and store.
// lift() will remove the unused i_next *Alloc.
isDead := func(i Instruction) bool { return dead[i] }
loop.Instrs = removeInstrsIf(loop.Instrs, isDead)
post.Instrs = removeInstrsIf(post.Instrs, isDead)
}
}
// rangeIndexed emits to fn the header for an integer-indexed loop
// over array, *array or slice value x.
// The v result is defined only if tv is non-nil.
// forPos is the position of the "for" token.
func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) {
//
// length = len(x)
// index = -1
// loop: (target of continue)
// index++
// if index < length goto body else done
// body:
// k = index
// v = x[index]
// ...body...
// jump loop
// done: (target of break)
// We store in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes
xAlloc := newVariable(fn, x.Type(), source)
xAlloc.store(x)
// Determine number of iterations.
//
// We store the length in an Alloc and load it on each iteration so that lifting produces the necessary σ nodes
length := newVariable(fn, tInt, source)
if arr, ok := typeutil.CoreType(deref(x.Type())).(*types.Array); ok {
// For array or *array, the number of iterations is known statically thanks to the type. We avoid a data
// dependence upon x, permitting later dead-code elimination if x is pure, static unrolling, etc. Ranging over a
// nil *array may have >0 iterations. We still generate code for x, in case it has effects.
//
// We use the core type of x, even though the length of type parameters isn't constant as per the language
// specification. Just because len(x) isn't constant doesn't mean we can't emit IR that takes advantage of a
// known length.
length.store(emitConst(fn, intConst(arr.Len(), nil)))
} else {
// length = len(x).
var c Call
c.Call.Value = makeLen(x.Type())
c.Call.Args = []Value{x}
c.setType(tInt)
length.store(fn.emit(&c, source))
}
index := emitLocal(fn, tInt, source, "rangeindex")
emitStore(fn, index, emitConst(fn, intConst(-1, nil)), source)
loop = fn.newBasicBlock("rangeindex.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
incr := &BinOp{
Op: token.ADD,
X: emitLoad(fn, index, source),
Y: emitConst(fn, intConst(1, nil)),
}
incr.setType(tInt)
emitStore(fn, index, fn.emit(incr, source), source)
body := fn.newBasicBlock("rangeindex.body")
done = fn.newBasicBlock("rangeindex.done")
emitIf(fn, emitCompare(fn, token.LSS, incr, length.load(), source), body, done, source)
fn.currentBlock = body
k = emitLoad(fn, index, source)
if tv != nil {
x := xAlloc.load()
switch t := typeutil.CoreType(x.Type()).Underlying().(type) {
case *types.Array:
instr := &Index{
X: x,
Index: k,
}
instr.setType(t.Elem())
v = fn.emit(instr, source)
case *types.Pointer: // *array
instr := &IndexAddr{
X: x,
Index: k,
}
instr.setType(types.NewPointer(t.Elem().Underlying().(*types.Array).Elem()))
v = emitLoad(fn, fn.emit(instr, source), source)
case *types.Slice:
instr := &IndexAddr{
X: x,
Index: k,
}
instr.setType(types.NewPointer(t.Elem()))
v = emitLoad(fn, fn.emit(instr, source), source)
default:
panic("rangeIndexed x:" + t.String())
}
}
return
}
// rangeIter emits to fn the header for a loop using
// Range/Next/Extract to iterate over map or string value x.
// tk and tv are the types of the key/value results k and v, or nil
// if the respective component is not wanted.
func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) {
//
// it = range x
// loop: (target of continue)
// okv = next it (ok, key, value)
// ok = extract okv #0
// if ok goto body else done
// body:
// k = extract okv #1
// v = extract okv #2
// ...body...
// jump loop
// done: (target of break)
//
if tk == nil {
tk = tInvalid
}
if tv == nil {
tv = tInvalid
}
rng := &Range{X: x}
rng.setType(typeutil.NewIterator(types.NewTuple(
varOk,
newVar("k", tk),
newVar("v", tv),
)))
it := newVariable(fn, rng.typ, source)
it.store(fn.emit(rng, source))
loop = fn.newBasicBlock("rangeiter.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
// Go doesn't currently allow ranging over string|[]byte, so isString is decidable.
_, isString := typeutil.CoreType(x.Type()).Underlying().(*types.Basic)
okvInstr := &Next{
Iter: it.load(),
IsString: isString,
}
okvInstr.setType(rng.typ.(*typeutil.Iterator).Elem())
fn.emit(okvInstr, source)
okv := newVariable(fn, okvInstr.Type(), source)
okv.store(okvInstr)
body := fn.newBasicBlock("rangeiter.body")
done = fn.newBasicBlock("rangeiter.done")
emitIf(fn, emitExtract(fn, okv.load(), 0, source), body, done, source)
fn.currentBlock = body
if tk != tInvalid {
k = emitExtract(fn, okv.load(), 1, source)
}
if tv != tInvalid {
v = emitExtract(fn, okv.load(), 2, source)
}
return
}
// rangeChan emits to fn the header for a loop that receives from
// channel x until it fails.
// tk is the channel's element type, or nil if the k result is
// not wanted
// pos is the position of the '=' or ':=' token.
func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) {
//
// loop: (target of continue)
// ko = <-x (key, ok)
// ok = extract ko #1
// if ok goto body else done
// body:
// k = extract ko #0
// ...
// goto loop
// done: (target of break)
loop = fn.newBasicBlock("rangechan.loop")
emitJump(fn, loop, source)
fn.currentBlock = loop
recv := emitRecv(fn, x, true, types.NewTuple(newVar("k", typeutil.CoreType(x.Type()).Underlying().(*types.Chan).Elem()), varOk), source)
retv := newVariable(fn, recv.Type(), source)
retv.store(recv)
body := fn.newBasicBlock("rangechan.body")
done = fn.newBasicBlock("rangechan.done")
emitIf(fn, emitExtract(fn, retv.load(), 1, source), body, done, source)
fn.currentBlock = body
if tk != nil {
k = emitExtract(fn, retv.load(), 0, source)
}
return
}
// rangeInt emits to fn the header for a range loop with an integer operand.
// tk is the key value's type, or nil if the k result is not wanted.
// pos is the position of the "for" token.
func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) {
//
// iter = 0
// if 0 < x goto body else done
// loop: (target of continue)
// iter++
// if iter < x goto body else done
// body:
// k = x
// ...body...
// jump loop
// done: (target of break)
if b, ok := x.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
x = emitConv(fn, x, tInt, source)
}
T := x.Type()
iter := emitLocal(fn, T, source, "rangeint.iter")
// x may be unsigned. Avoid initializing x to -1.
body := fn.newBasicBlock("rangeint.body")
done = fn.newBasicBlock("rangeint.done")
emitIf(fn, emitCompare(fn, token.LSS, emitConst(fn, zeroConst(T, source)), x, source), body, done, source)
loop = fn.newBasicBlock("rangeint.loop")
fn.currentBlock = loop
incr := &BinOp{
Op: token.ADD,
X: emitLoad(fn, iter, source),
Y: emitConv(fn, emitConst(fn, intConst(1, source)), T, source),
}
incr.setType(T)
emitStore(fn, iter, fn.emit(incr, source), source)
emitIf(fn, emitCompare(fn, token.LSS, incr, x, source), body, done, source)
fn.currentBlock = body
if tk != nil {
// Integer types (int, uint8, etc.) are named and
// we know that k is assignable to x when tk != nil.
// This implies tk and T are identical so no conversion is needed.
k = emitLoad(fn, iter, source)
}
return
}
type variable struct {
alloc *Alloc
fn *Function
source ast.Node
}
func newVariable(fn *Function, typ types.Type, source ast.Node) *variable {
alloc := &Alloc{}
alloc.setType(types.NewPointer(typ))
fn.emit(alloc, source)
fn.Locals = append(fn.Locals, alloc)
return &variable{
alloc: alloc,
fn: fn,
source: source,
}
}
func (v *variable) store(sv Value) {
emitStore(v.fn, v.alloc, sv, v.source)
}
func (v *variable) load() Value {
return emitLoad(v.fn, v.alloc, v.source)
}
// rangeStmt emits to fn code for the range statement s, optionally
// labelled by label.
func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, source ast.Node) {
var tk, tv types.Type
if s.Key != nil && !isBlankIdent(s.Key) {
tk = fn.Pkg.typeOf(s.Key)
}
if s.Value != nil && !isBlankIdent(s.Value) {
tv = fn.Pkg.typeOf(s.Value)
}
// create locals for s.Key and s.Value
createVars := func() {
// Unlike a short variable declaration, a RangeStmt
// using := never redeclares an existing variable; it
// always creates a new one.
if tk != nil {
id := s.Key.(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
if tv != nil {
id := s.Value.(*ast.Ident)
emitLocalVar(fn, identVar(fn, id), id)
}
}
afterGo122 := version.Compare(fn.goversion, "go1.22") >= 0
if s.Tok == token.DEFINE && !afterGo122 {
// pre-go1.22: If iteration variables are defined (:=), this
// occurs once outside the loop.
createVars()
}
x := b.expr(fn, s.X)
var k, v Value
var loop, done *BasicBlock
switch rt := typeutil.CoreType(x.Type()).Underlying().(type) {
case *types.Slice, *types.Array, *types.Pointer: // *array
k, v, loop, done = b.rangeIndexed(fn, x, tv, source)
case *types.Chan:
k, loop, done = b.rangeChan(fn, x, tk, source)
case *types.Map:
k, v, loop, done = b.rangeIter(fn, x, tk, tv, source)
case *types.Basic:
switch {
case rt.Info()&types.IsString != 0:
k, v, loop, done = b.rangeIter(fn, x, tk, tv, source)
case rt.Info()&types.IsInteger != 0:
k, loop, done = b.rangeInt(fn, x, tk, source)
default:
panic("Cannot range over basic type: " + rt.String())
}
case *types.Signature:
// Special case rewrite (fn.goversion >= go1.23):
// for x := range f { ... }
// into
// f(func(x T) bool { ... })
b.rangeFunc(fn, x, tk, tv, s, label)
return
default:
panic("Cannot range over: " + rt.String())
}
if s.Tok == token.DEFINE && afterGo122 {
// go1.22: If iteration variables are defined (:=), this occurs inside the loop.
createVars()
}
// Evaluate both LHS expressions before we update either.
var kl, vl lvalue
if tk != nil {
kl = b.addr(fn, s.Key, false) // non-escaping
}
if tv != nil {
vl = b.addr(fn, s.Value, false) // non-escaping
}
if tk != nil {
kl.store(fn, k, s)
}
if tv != nil {
vl.store(fn, v, s)
}
if label != nil {
label._break = done
label._continue = loop
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: loop,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, loop, source) // back-edge
fn.currentBlock = done
}
// rangeFunc emits to fn code for the range-over-func rng.Body of the iterator
// function x, optionally labelled by label. It creates a new anonymous function
// yield for rng and builds the function.
func (b *builder) rangeFunc(fn *Function, x Value, tk, tv types.Type, rng *ast.RangeStmt, label *lblock) {
// Consider the SSA code for the outermost range-over-func in fn:
//
// func fn(...) (ret R) {
// ...
// for k, v = range x {
// ...
// }
// ...
// }
//
// The code emitted into fn will look something like this.
//
// loop:
// jump := READY
// y := make closure yield [ret, deferstack, jump, k, v]
// x(y)
// switch jump {
// [see resuming execution]
// }
// goto done
// done:
// ...
//
// where yield is a new synthetic yield function:
//
// func yield(_k tk, _v tv) bool
// free variables: [ret, stack, jump, k, v]
// {
// entry:
// if jump != READY then goto invalid else valid
// invalid:
// panic("iterator called when it is not in a ready state")
// valid:
// jump = BUSY
// k = _k
// v = _v
// ...
// cont:
// jump = READY
// return true
// }
//
// Yield state:
//
// Each range loop has an associated jump variable that records
// the state of the iterator. A yield function is initially
// in a READY (0) and callable state. If the yield function is called
// and is not in READY state, it panics. When it is called in a callable
// state, it becomes BUSY. When execution reaches the end of the body
// of the loop (or a continue statement targeting the loop is executed),
// the yield function returns true and resumes being in a READY state.
// After the iterator function x(y) returns, then if the yield function
// is in a READY state, the yield enters the DONE state.
//
// Each lowered control statement (break X, continue X, goto Z, or return)
// that exits the loop sets the variable to a unique positive EXIT value,
// before returning false from the yield function.
//
// If the yield function returns abruptly due to a panic or GoExit,
// it remains in a BUSY state. The generated code asserts that, after
// the iterator call x(y) returns normally, the jump variable state
// is DONE.
//
// Resuming execution:
//
// The code generated for the range statement checks the jump
// variable to determine how to resume execution.
//
// switch jump {
// case BUSY: panic("...")
// case DONE: goto done
// case READY: state = DONE; goto done
// case 123: ... // action for exit 123.
// case 456: ... // action for exit 456.
// ...
// }
//
// Forward goto statements within a yield are jumps to labels that
// have not yet been traversed in fn. They may be in the Body of the
// function. What we emit for these is:
//
// goto target
// target:
// ...
//
// We leave an unresolved exit in yield.exits to check at the end
// of building yield if it encountered target in the body. If it
// encountered target, no additional work is required. Otherwise,
// the yield emits a new early exit in the basic block for target.
// We expect that blockopt will fuse the early exit into the case
// block later. The unresolved exit is then added to yield.parent.exits.
loop := fn.newBasicBlock("rangefunc.loop")
done := fn.newBasicBlock("rangefunc.done")
// These are targets within y.
fn.targets = &targets{
tail: fn.targets,
_break: done,
// _continue is within y.
}
if label != nil {
label._break = done
// _continue is within y
}
emitJump(fn, loop, nil)
fn.currentBlock = loop
// loop:
// jump := READY
anonIdx := len(fn.AnonFuncs)
jump := newVar(fmt.Sprintf("jump$%d", anonIdx+1), tInt)
emitLocalVar(fn, jump, nil) // zero value is READY
xsig := typeutil.CoreType(x.Type()).(*types.Signature)
ysig := typeutil.CoreType(xsig.Params().At(0).Type()).(*types.Signature)
/* synthetic yield function for body of range-over-func loop */
y := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), anonIdx+1),
Signature: ysig,
Synthetic: SyntheticRangeOverFuncYield,
parent: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
functionBody: new(functionBody),
}
y.source = rng
y.goversion = fn.goversion
y.jump = jump
y.deferstack = fn.deferstack
y.returnVars = fn.returnVars // use the parent's return variables
y.uniq = fn.uniq // start from parent's unique values
// If the RangeStmt has a label, this is how it is passed to buildYieldFunc.
if label != nil {
y.lblocks = map[*types.Label]*lblock{label.label: nil}
}
fn.AnonFuncs = append(fn.AnonFuncs, y)
// Build y immediately. It may:
// * cause fn's locals to escape, and
// * create new exit nodes in exits.
// (y is not marked 'built' until the end of the enclosing FuncDecl.)
unresolved := len(fn.exits)
b.buildYieldFunc(y)
fn.uniq = y.uniq // resume after y's unique values
// Emit the call of y.
// c := MakeClosure y
// x(c)
c := &MakeClosure{Fn: y}
c.setType(ysig)
c.comment = "yield"
for _, fv := range y.FreeVars {
c.Bindings = append(c.Bindings, fv.outer)
fv.outer = nil
}
fn.emit(c, nil)
call := Call{
Call: CallCommon{
Value: x,
Args: []Value{c},
},
}
call.setType(xsig.Results())
fn.emit(&call, nil)
exits := fn.exits[unresolved:]
b.buildYieldResume(fn, jump, exits, done)
fn.currentBlock = done
// pop the stack for the range-over-func
fn.targets = fn.targets.tail
}
// buildYieldResume emits to fn code for how to resume execution once a call to
// the iterator function over the yield function returns x(y). It does this by building
// a switch over the value of jump for when it is READY, BUSY, or EXIT(id).
func (b *builder) buildYieldResume(fn *Function, jump *types.Var, exits []*exit, done *BasicBlock) {
// v := *jump
// switch v {
// case BUSY: panic("...")
// case READY: jump = DONE; goto done
// case EXIT(a): ...
// case EXIT(b): ...
// ...
// }
v := emitLoad(fn, fn.lookup(jump, false), nil)
entry := fn.currentBlock
bodies := make([]*BasicBlock, 2, 2+len(exits))
bodies[0] = fn.newBasicBlock("rangefunc.resume.busy")
bodies[1] = fn.newBasicBlock("rangefunc.resume.ready")
conds := make([]Value, 2, 2+len(exits))
conds[0] = emitConst(fn, jBusy())
conds[1] = emitConst(fn, jReady())
fn.currentBlock = bodies[0]
fn.emit(
&Panic{
X: emitConv(fn, emitConst(fn, stringConst("iterator call did not preserve panic", nil)), tEface, nil),
},
nil,
)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = bodies[1]
storeVar(fn, jump, emitConst(fn, jDone()), nil)
emitJump(fn, done, nil)
for _, e := range exits {
body := fn.newBasicBlock(fmt.Sprintf("rangefunc.resume.exit.%d", e.id))
bodies = append(bodies, body)
id_ := intConst(e.id, nil)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
conds = append(conds, id)
fn.currentBlock = body
switch {
case e.label != nil: // forward goto?
// case EXIT(id): goto lb // label
lb := fn.lblockOf(e.label)
// Do not mark lb as resolved.
// If fn does not contain label, lb remains unresolved and
// fn must itself be a range-over-func function. lb will be:
// lb:
// fn.jump = id
// return false
emitJump(fn, lb._goto, e.source)
case e.to != fn: // e jumps to an ancestor of fn?
// case EXIT(id): { fn.jump = id; return false }
// fn is a range-over-func function.
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
case e.block == nil && e.label == nil: // return from fn?
// case EXIT(id): { return ... }
// The results have already been stored to variables in fn.results, so
// emitReturn doesn't have to do it again.
emitReturn(fn, nil, e.source)
case e.block != nil:
// case EXIT(id): goto block
emitJump(fn, e.block, e.source)
default:
panic("unreachable")
}
}
fn.currentBlock = entry
// Note that this switch does not have an implicit default case. This wouldn't be
// valid for a user-provided switch statement, but for range-over-func we know all
// possible values and we can avoid the impossible branch.
swtch := &ConstantSwitch{
Tag: v,
Conds: conds,
}
fn.emit(swtch, nil)
for _, body := range bodies {
addEdge(entry, body)
}
}
// stmt lowers statement s to IR form, emitting code to fn.
func (b *builder) stmt(fn *Function, _s ast.Stmt) {
// The label of the current statement. If non-nil, its _goto
// target is always set; its _break and _continue are set only
// within the body of switch/typeswitch/select/for/range.
// It is effectively an additional default-nil parameter of stmt().
var label *lblock
start:
switch s := _s.(type) {
case *ast.EmptyStmt:
// ignore. (Usually removed by gofmt.)
case *ast.DeclStmt: // Con, Var or Typ
d := s.Decl.(*ast.GenDecl)
if d.Tok == token.VAR {
for _, spec := range d.Specs {
if vs, ok := spec.(*ast.ValueSpec); ok {
b.localValueSpec(fn, vs)
}
}
}
case *ast.LabeledStmt:
if s.Label.Name == "_" {
// Blank labels can't be the target of a goto, break,
// or continue statement, so we don't need a new block.
_s = s.Stmt
goto start
}
label = fn.lblockOf(fn.label(s.Label))
label.resolved = true
emitJump(fn, label._goto, s)
fn.currentBlock = label._goto
_s = s.Stmt
goto start // effectively: tailcall stmt(fn, s.Stmt, label)
case *ast.ExprStmt:
b.expr(fn, s.X)
case *ast.SendStmt:
instr := &Send{
Chan: b.expr(fn, s.Chan),
X: emitConv(fn, b.expr(fn, s.Value),
typeutil.CoreType(fn.Pkg.typeOf(s.Chan)).Underlying().(*types.Chan).Elem(), s),
}
fn.emit(instr, s)
case *ast.IncDecStmt:
op := token.ADD
if s.Tok == token.DEC {
op = token.SUB
}
loc := b.addr(fn, s.X, false)
b.assignOp(fn, loc, emitConst(fn, NewConst(constant.MakeInt64(1), loc.typ(), s)), op, s)
case *ast.AssignStmt:
switch s.Tok {
case token.ASSIGN, token.DEFINE:
b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE, _s)
default: // +=, etc.
op := s.Tok + token.ADD - token.ADD_ASSIGN
b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op, s)
}
case *ast.GoStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic is treated like an ordinary function call.
v := Go{}
b.setCall(fn, s.Call, &v.Call)
fn.emit(&v, s)
case *ast.DeferStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic is treated like an ordinary function call.
deferstack := emitLoad(fn, fn.lookup(fn.deferstack, false), s)
v := Defer{_DeferStack: deferstack}
b.setCall(fn, s.Call, &v.Call)
fn.hasDefer = true
fn.emit(&v, s)
case *ast.ReturnStmt:
b.returnStmt(fn, s)
case *ast.BranchStmt:
b.branchStmt(fn, s)
case *ast.BlockStmt:
b.stmtList(fn, s.List)
case *ast.IfStmt:
if s.Init != nil {
b.stmt(fn, s.Init)
}
then := fn.newBasicBlock("if.then")
done := fn.newBasicBlock("if.done")
els := done
if s.Else != nil {
els = fn.newBasicBlock("if.else")
}
instr := b.cond(fn, s.Cond, then, els)
instr.source = s
fn.currentBlock = then
b.stmt(fn, s.Body)
emitJump(fn, done, s)
if s.Else != nil {
fn.currentBlock = els
b.stmt(fn, s.Else)
emitJump(fn, done, s)
}
fn.currentBlock = done
case *ast.SwitchStmt:
b.switchStmt(fn, s, label)
case *ast.TypeSwitchStmt:
b.typeSwitchStmt(fn, s, label)
case *ast.SelectStmt:
if b.selectStmt(fn, s, label) {
// the select has no cases, it blocks forever
fn.currentBlock = fn.newBasicBlock("unreachable")
}
case *ast.ForStmt:
b.forStmt(fn, s, label)
case *ast.RangeStmt:
b.rangeStmt(fn, s, label, s)
default:
panic(fmt.Sprintf("unexpected statement kind: %T", s))
}
}
func (b *builder) branchStmt(fn *Function, s *ast.BranchStmt) {
var block *BasicBlock
if s.Label == nil {
block = targetedBlock(fn, s.Tok)
} else {
target := fn.label(s.Label)
block = labelledBlock(fn, target, s.Tok)
if block == nil { // forward goto
lb := fn.lblockOf(target)
block = lb._goto // jump to lb._goto
if fn.jump != nil {
// fn is a range-over-func and the goto may exit fn.
// Create an exit and resolve it at the end of
// builder.buildYieldFunc.
labelExit(fn, target, s)
}
}
}
to := block.parent
if to == fn {
emitJump(fn, block, s)
} else { // break outside of fn.
// fn must be a range-over-func
e := blockExit(fn, block, s)
id_ := intConst(e.id, s)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, s)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, s))
emitReturn(fn, []Value{vFalse}, s)
}
fn.currentBlock = fn.newBasicBlock("unreachable")
}
func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) {
// TODO(dh): we could emit tighter position information by
// using the ith returned expression
var results []Value
sig := fn.sourceFn.Signature // signature of the enclosing source function
// Convert return operands to result type.
if len(s.Results) == 1 && sig.Results().Len() > 1 {
// Return of one expression in a multi-valued function.
tuple := b.exprN(fn, s.Results[0])
ttuple := tuple.Type().(*types.Tuple)
for i, n := 0, ttuple.Len(); i < n; i++ {
results = append(results,
emitConv(fn, emitExtract(fn, tuple, i, s),
sig.Results().At(i).Type(), s))
}
} else {
// 1:1 return, or no-arg return in non-void function.
for i, r := range s.Results {
v := emitConv(fn, b.expr(fn, r), sig.Results().At(i).Type(), s)
results = append(results, v)
}
}
// Store the results.
for i, r := range results {
var result Value // fn.sourceFn.result[i] conceptually
if fn == fn.sourceFn {
result = fn.results[i]
} else { // lookup needed?
result = fn.lookup(fn.returnVars[i], false)
}
emitStore(fn, result, r, s)
}
if fn.jump != nil {
// Return from body of a range-over-func.
// The return statement is syntactically within the loop,
// but the generated code is in the 'switch jump {...}' after it.
e := returnExit(fn, s)
id_ := intConst(e.id, e.source)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
return
}
// The results have already been stored to variables in fn.results, so
// emitReturn doesn't have to do it again.
emitReturn(fn, nil, s)
}
func emitReturn(fn *Function, results []Value, source ast.Node) {
for i, r := range results {
emitStore(fn, fn.results[i], r, source)
}
emitJump(fn, fn.Exit, source)
fn.currentBlock = fn.newBasicBlock("unreachable")
}
// buildFunction builds IR code for the body of function fn. Idempotent.
func (b *builder) buildFunction(fn *Function) {
if fn.Blocks != nil {
return // building already started
}
var recvField *ast.FieldList
var body *ast.BlockStmt
var functype *ast.FuncType
switch n := fn.source.(type) {
case nil:
return // not a Go source function. (Synthetic, or from object file.)
case *ast.FuncDecl:
functype = n.Type
recvField = n.Recv
body = n.Body
case *ast.FuncLit:
functype = n.Type
body = n.Body
default:
panic(n)
}
if body == nil {
// External function.
if fn.Params == nil {
// This condition ensures we add a non-empty
// params list once only, but we may attempt
// the degenerate empty case repeatedly.
// TODO(adonovan): opt: don't do that.
// We set Function.Params even though there is no body
// code to reference them. This simplifies clients.
if recv := fn.Signature.Recv(); recv != nil {
// XXX synthesize an ast.Node
fn.addParamVar(recv, nil)
}
params := fn.Signature.Params()
for i, n := 0, params.Len(); i < n; i++ {
// XXX synthesize an ast.Node
fn.addParamVar(params.At(i), nil)
}
}
return
}
if fn.Prog.mode&LogSource != 0 {
defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.Pos()))()
}
fn.blocksets = b.blocksets
fn.Blocks = make([]*BasicBlock, 0, avgBlocks)
fn.sourceFn = fn
fn.startBody()
fn.createSyntacticParams(recvField, functype)
fn.createDeferStack()
fn.exitBlock()
b.stmt(fn, body)
if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) {
// Control fell off the end of the function's body block.
//
// Block optimizations eliminate the current block, if
// unreachable. It is a builder invariant that
// if this no-arg return is ill-typed for
// fn.Signature.Results, this block must be
// unreachable. The sanity checker checks this.
// fn.emit(new(RunDefers))
// fn.emit(new(Return))
emitJump(fn, fn.Exit, nil)
}
optimizeBlocks(fn)
buildFakeExits(fn)
fn.finishBody()
b.blocksets = fn.blocksets
fn.functionBody = nil
}
// buildYieldFunc builds the body of the yield function created
// from a range-over-func *ast.RangeStmt.
func (b *builder) buildYieldFunc(fn *Function) {
// See builder.rangeFunc for detailed documentation on how fn is set up.
//
// In pseudo-Go this roughly builds:
// func yield(_k tk, _v tv) bool {
// if jump != READY { panic("yield function called after range loop exit") }
// jump = BUSY
// k, v = _k, _v // assign the iterator variable (if needed)
// ... // rng.Body
// continue:
// jump = READY
// return true
// }
s := fn.source.(*ast.RangeStmt)
fn.sourceFn = fn.parent.sourceFn
fn.startBody()
params := fn.Signature.Params()
for v := range params.Variables() {
fn.addParamVar(v, nil)
}
fn.addResultVar(fn.Signature.Results().At(0), nil)
fn.exitBlock()
// Initial targets
ycont := fn.newBasicBlock("yield-continue")
// lblocks is either {} or is {label: nil} where label is the label of syntax.
for label := range fn.lblocks {
fn.lblocks[label] = &lblock{
label: label,
resolved: true,
_goto: ycont,
_continue: ycont,
// `break label` statement targets fn.parent.targets._break
}
}
fn.targets = &targets{
tail: fn.targets,
_continue: ycont,
// `break` statement targets fn.parent.targets._break.
}
// continue:
// jump = READY
// return true
saved := fn.currentBlock
fn.currentBlock = ycont
storeVar(fn, fn.jump, emitConst(fn, jReady()), s.Body)
vTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool, nil))
emitReturn(fn, []Value{vTrue}, nil)
// Emit header:
//
// if jump != READY { panic("yield iterator accessed after exit") }
// jump = BUSY
// k, v = _k, _v
fn.currentBlock = saved
yloop := fn.newBasicBlock("yield-loop")
invalid := fn.newBasicBlock("yield-invalid")
jumpVal := emitLoad(fn, fn.lookup(fn.jump, true), nil)
emitIf(fn, emitCompare(fn, token.EQL, jumpVal, emitConst(fn, jReady()), nil), yloop, invalid, nil)
fn.currentBlock = invalid
fn.emit(
&Panic{
X: emitConv(fn, emitConst(fn, stringConst("yield function called after range loop exit", nil)), tEface, nil),
},
nil,
)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = yloop
storeVar(fn, fn.jump, emitConst(fn, jBusy()), s.Body)
// Initialize k and v from params.
var tk, tv types.Type
if s.Key != nil && !isBlankIdent(s.Key) {
tk = fn.Pkg.typeOf(s.Key) // fn.parent.typeOf is identical
}
if s.Value != nil && !isBlankIdent(s.Value) {
tv = fn.Pkg.typeOf(s.Value)
}
if s.Tok == token.DEFINE {
if tk != nil {
emitLocalVar(fn, identVar(fn, s.Key.(*ast.Ident)), s.Key)
}
if tv != nil {
emitLocalVar(fn, identVar(fn, s.Value.(*ast.Ident)), s.Value)
}
}
var k, v Value
if len(fn.Params) > 0 {
k = fn.Params[0]
}
if len(fn.Params) > 1 {
v = fn.Params[1]
}
var kl, vl lvalue
if tk != nil {
kl = b.addr(fn, s.Key, false) // non-escaping
}
if tv != nil {
vl = b.addr(fn, s.Value, false) // non-escaping
}
if tk != nil {
kl.store(fn, k, s.Key)
}
if tv != nil {
vl.store(fn, v, s.Value)
}
// Build the body of the range loop.
b.stmt(fn, s.Body)
if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) {
// Control fell off the end of the function's body block.
// Block optimizations eliminate the current block, if
// unreachable.
emitJump(fn, ycont, nil)
}
fn.targets = fn.targets.tail
// Clean up exits and promote any unresolved exits to fn.parent.
for _, e := range fn.exits {
if e.label != nil {
lb := fn.lblocks[e.label]
if lb.resolved {
// label was resolved. Do not turn lb into an exit.
// e does not need to be handled by the parent.
continue
}
// _goto becomes an exit.
// _goto:
// jump = id
// return false
fn.currentBlock = lb._goto
id_ := intConst(e.id, e.source)
id_.comment = fmt.Sprintf("rangefunc.exit.%d", e.id)
id := emitConst(fn, id_)
storeVar(fn, fn.jump, id, e.source)
vFalse := emitConst(fn, NewConst(constant.MakeBool(false), tBool, e.source))
emitReturn(fn, []Value{vFalse}, e.source)
}
if e.to != fn { // e needs to be handled by the parent too.
fn.parent.exits = append(fn.parent.exits, e)
}
}
fn.finishBody()
}
// buildFuncDecl builds IR code for the function or method declared
// by decl in package pkg.
func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
id := decl.Name
fn := pkg.values[pkg.info.Defs[id]].(*Function)
if decl.Recv == nil && id.Name == "init" {
var v Call
v.Call.Value = fn
v.setType(types.NewTuple())
pkg.init.emit(&v, decl)
}
fn.source = decl
b.buildFunction(fn)
}
// Build calls Package.Build for each package in prog.
//
// Build is intended for whole-program analysis; a typical compiler
// need only build a single package.
//
// Build is idempotent and thread-safe.
func (prog *Program) Build() {
for _, p := range prog.packages {
p.Build()
}
}
// Build builds IR code for all functions and vars in package p.
//
// Precondition: CreatePackage must have been called for all of p's
// direct imports (and hence its direct imports must have been
// error-free).
//
// Build is idempotent and thread-safe.
func (p *Package) Build() { p.buildOnce.Do(p.build) }
func (p *Package) build() {
if p.info == nil {
return // synthetic package, e.g. "testmain"
}
// Ensure we have runtime type info for all exported members.
// TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order.
for name, mem := range p.Members {
if ast.IsExported(name) {
p.Prog.needMethodsOf(mem.Type())
}
}
if p.Prog.mode&LogSource != 0 {
defer logStack("build %s", p)()
}
init := p.init
init.startBody()
init.exitBlock()
var done *BasicBlock
// Make init() skip if package is already initialized.
initguard := p.Var("init$guard")
doinit := init.newBasicBlock("init.start")
done = init.Exit
emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil)
init.currentBlock = doinit
emitStore(init, initguard, emitConst(init, NewConst(constant.MakeBool(true), tBool, nil)), nil)
// Call the init() function of each package we import.
for _, pkg := range p.Pkg.Imports() {
prereq := p.Prog.packages[pkg]
if prereq == nil {
panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path()))
}
var v Call
v.Call.Value = prereq.init
v.setType(types.NewTuple())
init.emit(&v, nil)
}
b := builder{
printFunc: p.printFunc,
}
// Initialize package-level vars in correct order.
for _, varinit := range p.info.InitOrder {
if init.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n",
varinit.Lhs, p.Prog.Fset.Position(varinit.Rhs.Pos()))
}
// Initializers for global vars are evaluated in dependency
// order, but may come from arbitrary files of the package
// with different versions, so we transiently update
// init.goversion for each one. (Since init is a synthetic
// function it has no syntax of its own that needs a version.)
init.goversion = p.initVersion[varinit.Rhs]
if len(varinit.Lhs) == 1 {
// 1:1 initialization: var x, y = a(), b()
var lval lvalue
if v := varinit.Lhs[0]; v.Name() != "_" {
lval = &address{addr: p.values[v].(*Global)}
} else {
lval = blank{}
}
// TODO(dh): do emit position information
b.assign(init, lval, varinit.Rhs, true, nil, nil)
} else {
// n:1 initialization: var x, y := f()
tuple := b.exprN(init, varinit.Rhs)
for i, v := range varinit.Lhs {
if v.Name() == "_" {
continue
}
emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i, nil), nil)
}
}
}
init.goversion = "" // The rest of the init function is synthetic. No syntax => no goversion.
// Build all package-level functions, init functions
// and methods, including unreachable/blank ones.
// We build them in source order, but it's not significant.
for _, file := range p.files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
b.buildFuncDecl(p, decl)
}
}
}
// Finish up init().
emitJump(init, done, nil)
init.finishBody()
// We no longer need ASTs or go/types deductions.
p.info = nil
p.initVersion = nil
if p.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckPackage(p)
}
}
// Like ObjectOf, but panics instead of returning nil.
// Only valid during p's create and build phases.
func (p *Package) objectOf(id *ast.Ident) types.Object {
if o := p.info.ObjectOf(id); o != nil {
return o
}
panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s",
id.Name, p.Prog.Fset.Position(id.Pos())))
}
// Like TypeOf, but panics instead of returning nil.
// Only valid during p's create and build phases.
func (p *Package) typeOf(e ast.Expr) types.Type {
if T := p.info.TypeOf(e); T != nil {
return T
}
panic(fmt.Sprintf("no type for %T @ %s",
e, p.Prog.Fset.Position(e.Pos())))
}
================================================
FILE: go/ir/builder_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
package ir_test
import (
"bytes"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"reflect"
"sort"
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/loader"
)
func isEmpty(f *ir.Function) bool { return f.Blocks == nil }
// Tests that programs partially loaded from gc object files contain
// functions with no code for the external portions, but are otherwise ok.
func TestBuildPackage(t *testing.T) {
input := `
package main
import (
"bytes"
"io"
"testing"
)
func main() {
var t testing.T
t.Parallel() // static call to external declared method
t.Fail() // static call to promoted external declared method
testing.Short() // static call to external package-level function
var w io.Writer = new(bytes.Buffer)
w.Write(nil) // interface invoke of external declared method
}
`
// Parse the file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "input.go", input, parser.SkipObjectResolution)
if err != nil {
t.Error(err)
return
}
// Build an IR program from the parsed file.
// Load its dependencies from gc binary export data.
mainPkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
types.NewPackage("main", ""), []*ast.File{f}, ir.SanityCheckFunctions)
if err != nil {
t.Error(err)
return
}
// The main package, its direct and indirect dependencies are loaded.
deps := []string{
// directly imported dependencies:
"bytes", "io", "testing",
// indirect dependencies mentioned by
// the direct imports' export data
"sync", "unicode", "time",
}
prog := mainPkg.Prog
all := prog.AllPackages()
if len(all) <= len(deps) {
t.Errorf("unexpected set of loaded packages: %q", all)
}
for _, path := range deps {
pkg := prog.ImportedPackage(path)
if pkg == nil {
t.Errorf("package not loaded: %q", path)
continue
}
// External packages should have no function bodies (except for wrappers).
isExt := pkg != mainPkg
// init()
if isExt && !isEmpty(pkg.Func("init")) {
t.Errorf("external package %s has non-empty init", pkg)
} else if !isExt && isEmpty(pkg.Func("init")) {
t.Errorf("main package %s has empty init", pkg)
}
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *ir.Function:
// Functions at package level.
if isExt && !isEmpty(mem) {
t.Errorf("external function %s is non-empty", mem)
} else if !isExt && isEmpty(mem) {
t.Errorf("function %s is empty", mem)
}
case *ir.Type:
// Methods of named types T.
// (In this test, all exported methods belong to *T not T.)
if !isExt {
t.Fatalf("unexpected name type in main package: %s", mem)
}
mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
for i, n := 0, mset.Len(); i < n; i++ {
m := prog.MethodValue(mset.At(i))
// For external types, only synthetic wrappers have code.
expExt := m.Synthetic != ir.SyntheticWrapper
if expExt && !isEmpty(m) {
t.Errorf("external method %s is non-empty: %s",
m, m.Synthetic)
} else if !expExt && isEmpty(m) {
t.Errorf("method function %s is empty: %s",
m, m.Synthetic)
}
}
}
}
}
expectedCallee := []string{
"(*testing.T).Parallel",
"(*testing.common).Fail",
"testing.Short",
"N/A",
}
callNum := 0
for _, b := range mainPkg.Func("main").Blocks {
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case ir.CallInstruction:
call := instr.Common()
if want := expectedCallee[callNum]; want != "N/A" {
got := call.StaticCallee().String()
if want != got {
t.Errorf("call #%d from main.main: got callee %s, want %s",
callNum, got, want)
}
}
callNum++
}
}
}
if callNum != 4 {
t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
}
}
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
func TestRuntimeTypes(t *testing.T) {
tests := []struct {
input string
want []string
}{
// An exported package-level type is needed.
{`package A; type T struct{}; func (T) f() {}`,
[]string{"*p.T", "p.T"},
},
// An unexported package-level type is not needed.
{`package B; type t struct{}; func (t) f() {}`,
nil,
},
// Subcomponents of type of exported package-level var are needed.
{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported package-level var are not needed.
{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
nil,
},
// Subcomponents of type of exported package-level function are needed.
{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported package-level function are not needed.
{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
nil,
},
// Subcomponents of type of exported method of uninstantiated unexported type are not needed.
{`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`,
nil,
},
// ...unless used by MakeInterface.
{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
},
// Subcomponents of type of unexported method are not needed.
{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
},
// Local types aren't needed.
{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
nil,
},
// ...unless used by MakeInterface.
{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
[]string{"*bytes.Buffer", "*p.T", "p.T"},
},
// Types used as operand of MakeInterface are needed.
{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
},
// MakeInterface is optimized away when storing to a blank.
{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
nil,
},
}
for _, test := range tests {
// Parse the file.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "input.go", test.input, 0)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
// Create a single-file main package.
// Load dependencies from gc binary export data.
irpkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
types.NewPackage("p", ""), []*ast.File{f}, ir.SanityCheckFunctions)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
var typstrs []string
for _, T := range irpkg.Prog.RuntimeTypes() {
typstrs = append(typstrs, T.String())
}
sort.Strings(typstrs)
if !reflect.DeepEqual(typstrs, test.want) {
t.Errorf("test 'package %s': got %q, want %q",
f.Name.Name, typstrs, test.want)
}
}
}
// TestInit tests that synthesized init functions are correctly formed.
func TestInit(t *testing.T) {
tests := []struct {
mode ir.BuilderMode
input, want string
}{
{0, `package A; import _ "errors"; var i int = 42`,
`# Name: A.init
# Package: A
# Synthetic: package initializer
func init():
b0: # entry
t1 = Const {true}
t2 = Const {42}
t3 = Load init$guard
If t3 → b1 b2
b1: ← b0 b2 # exit
Return
b2: ← b0 # init.start
Store {bool} init$guard t1
t7 = Call <()> errors.init
Store {int} i t2
Jump → b1
`},
}
for _, test := range tests {
// Create a single-file main package.
var conf loader.Config
f, err := conf.ParseFile(" ", test.input)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
}
conf.CreateFromFiles(f.Name.Name, f)
lprog, err := conf.Load()
if err != nil {
t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
continue
}
prog := irutil.CreateProgram(lprog, test.mode)
mainPkg := prog.Package(lprog.Created[0].Pkg)
prog.Build()
initFunc := mainPkg.Func("init")
if initFunc == nil {
t.Errorf("test 'package %s': no init function", f.Name.Name)
continue
}
var initbuf bytes.Buffer
_, err = initFunc.WriteTo(&initbuf)
if err != nil {
t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
continue
}
if initbuf.String() != test.want {
t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want)
}
}
}
// TestSyntheticFuncs checks that the expected synthetic functions are
// created, reachable, and not duplicated.
func TestSyntheticFuncs(t *testing.T) {
const input = `package P
type T int
func (T) f() int
func (*T) g() int
var (
// thunks
a = T.f
b = T.f
c = (struct{T}).f
d = (struct{T}).f
e = (*T).g
f = (*T).g
g = (struct{*T}).g
h = (struct{*T}).g
// bounds
i = T(0).f
j = T(0).f
k = new(T).g
l = new(T).g
// wrappers
m interface{} = struct{T}{}
n interface{} = struct{T}{}
o interface{} = struct{*T}{}
p interface{} = struct{*T}{}
q interface{} = new(struct{T})
r interface{} = new(struct{T})
s interface{} = new(struct{*T})
t interface{} = new(struct{*T})
)
`
// Parse
var conf loader.Config
f, err := conf.ParseFile(" ", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles(f.Name.Name, f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build IR
prog := irutil.CreateProgram(lprog, 0)
prog.Build()
// Enumerate reachable synthetic functions
want := map[string]ir.Synthetic{
"(*P.T).g$bound": ir.SyntheticBound,
"(P.T).f$bound": ir.SyntheticBound,
"(*P.T).g$thunk": ir.SyntheticThunk,
"(P.T).f$thunk": ir.SyntheticThunk,
"(struct{*P.T}).g$thunk": ir.SyntheticThunk,
"(struct{P.T}).f$thunk": ir.SyntheticThunk,
"(*P.T).f": ir.SyntheticWrapper,
"(*struct{*P.T}).f": ir.SyntheticWrapper,
"(*struct{*P.T}).g": ir.SyntheticWrapper,
"(*struct{P.T}).f": ir.SyntheticWrapper,
"(*struct{P.T}).g": ir.SyntheticWrapper,
"(struct{*P.T}).f": ir.SyntheticWrapper,
"(struct{*P.T}).g": ir.SyntheticWrapper,
"(struct{P.T}).f": ir.SyntheticWrapper,
"P.init": ir.SyntheticPackageInitializer,
}
var seen []string // may contain dups
for fn := range irutil.AllFunctions(prog) {
if fn.Synthetic == 0 {
continue
}
name := fn.String()
wantDescr, ok := want[name]
if !ok {
t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
continue
}
seen = append(seen, name)
if wantDescr != fn.Synthetic {
t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
}
}
for _, name := range seen {
delete(want, name)
}
for fn, descr := range want {
t.Errorf("want func: %q: %q", fn, descr)
}
}
// TestPhiElimination ensures that dead phis, including those that
// participate in a cycle, are properly eliminated.
func TestPhiElimination(t *testing.T) {
const input = `
package p
func f() error
func g(slice []int) {
for {
for range slice {
// e should not be lifted to a dead φ-node.
e := f()
h(e)
}
}
}
func h(error)
`
// The IR code for this function should look something like this:
// 0:
// jump 1
// 1:
// t0 = len(slice)
// jump 2
// 2:
// t1 = phi [1: -1:int, 3: t2]
// t2 = t1 + 1:int
// t3 = t2 < t0
// if t3 goto 3 else 1
// 3:
// t4 = f()
// t5 = h(t4)
// jump 2
//
// But earlier versions of the IR construction algorithm would
// additionally generate this cycle of dead phis:
//
// 1:
// t7 = phi [0: nil:error, 2: t8] #e
// ...
// 2:
// t8 = phi [1: t7, 3: t4] #e
// ...
// Parse
var conf loader.Config
f, err := conf.ParseFile(" ", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build IR
prog := irutil.CreateProgram(lprog, 0)
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
g := p.Func("g")
phis := 0
for _, b := range g.Blocks {
for _, instr := range b.Instrs {
if _, ok := instr.(*ir.Phi); ok {
phis++
}
}
}
if expected := 4; phis != expected {
g.WriteTo(os.Stderr)
t.Errorf("expected %d Phi nodes (for the range index, slice length and slice), got %d", expected, phis)
}
}
func TestBuildPackageGo120(t *testing.T) {
tests := []struct {
name string
src string
importer types.Importer
}{
{"slice to array", "package p; var s []byte; var _ = ([4]byte)(s)", nil},
{"slice to zero length array", "package p; var s []byte; var _ = ([0]byte)(s)", nil},
{"slice to zero length array type parameter", "package p; var s []byte; func f[T ~[0]byte]() { tmp := (T)(s); var z T; _ = tmp == z}", nil},
{"slice to non-zero length array type parameter", "package p; var s []byte; func h[T ~[1]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{"slice to maybe-zero length array type parameter", "package p; var s []byte; func g[T ~[0]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{
"rune sequence to sequence cast patterns", `
package p
// Each of fXX functions describes a 1.20 legal cast between sequences of runes
// as []rune, pointers to rune arrays, rune arrays, or strings.
//
// Comments listed given the current emitted instructions [approximately].
// If multiple conversions are needed, these are separated by |.
// rune was selected as it leads to string casts (byte is similar).
// The length 2 is not significant.
// Multiple array lengths may occur in a cast in practice (including 0).
func f00[S string, D string](s S) { _ = D(s) } // ChangeType
func f01[S string, D []rune](s S) { _ = D(s) } // Convert
func f02[S string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert
func f03[S [2]rune, D [2]rune](s S) { _ = D(s) } // ChangeType
func f04[S *[2]rune, D *[2]rune](s S) { _ = D(s) } // ChangeType
func f05[S []rune, D string](s S) { _ = D(s) } // Convert
func f06[S []rune, D [2]rune](s S) { _ = D(s) } // SliceToArray
func f07[S []rune, D [2]rune | string](s S) { _ = D(s) } // SliceToArray | Convert
func f08[S []rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer
func f09[S []rune, D *[2]rune | string](s S) { _ = D(s) } // SliceToArray | Convert
func f10[S []rune, D *[2]rune | [2]rune](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArray
func f11[S []rune, D *[2]rune | [2]rune | string](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArray | Convert
func f12[S []rune, D []rune](s S) { _ = D(s) } // ChangeType
func f13[S []rune, D []rune | string](s S) { _ = D(s) } // Convert | ChangeType
func f14[S []rune, D []rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArray
func f15[S []rune, D []rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArray | Convert
func f16[S []rune, D []rune | *[2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer
func f17[S []rune, D []rune | *[2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | Convert
func f18[S []rune, D []rune | *[2]rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArray
func f19[S []rune, D []rune | *[2]rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArray | Convert
func f20[S []rune | string, D string](s S) { _ = D(s) } // Convert | ChangeType
func f21[S []rune | string, D []rune](s S) { _ = D(s) } // Convert | ChangeType
func f22[S []rune | string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert | Convert | ChangeType
func f23[S []rune | [2]rune, D [2]rune](s S) { _ = D(s) } // SliceToArray | ChangeType
func f24[S []rune | *[2]rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer | ChangeType
`, nil,
},
{
"matching named and underlying types", `
package p
type a string
type b string
func g0[S []rune | a | b, D []rune | a | b](s S) { _ = D(s) }
func g1[S []rune | ~string, D []rune | a | b](s S) { _ = D(s) }
func g2[S []rune | a | b, D []rune | ~string](s S) { _ = D(s) }
func g3[S []rune | ~string, D []rune |~string](s S) { _ = D(s) }
`, nil,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", tc.src, 0)
if err != nil {
t.Error(err)
}
files := []*ast.File{f}
pkg := types.NewPackage("p", "")
conf := &types.Config{Importer: tc.importer}
_, _, err = irutil.BuildPackage(conf, fset, pkg, files, ir.SanityCheckFunctions)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestGo117Builtins(t *testing.T) {
tests := []struct {
name string
src string
importer types.Importer
}{
{"slice to array pointer", "package p; var s []byte; var _ = (*[4]byte)(s)", nil},
{"unsafe slice", `package p; import "unsafe"; var _ = unsafe.Add(nil, 0)`, importer.Default()},
{"unsafe add", `package p; import "unsafe"; var _ = unsafe.Slice((*int)(nil), 0)`, importer.Default()},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", tc.src, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
t.Error(err)
}
files := []*ast.File{f}
pkg := types.NewPackage("p", "")
conf := &types.Config{Importer: tc.importer}
if _, _, err := irutil.BuildPackage(conf, fset, pkg, files, ir.SanityCheckFunctions); err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
// TestLabels just tests that anonymous labels are handled.
func TestLabels(t *testing.T) {
tests := []string{
`package main
func main() { _:println(1) }`,
`package main
func main() { _:println(1); _:println(2)}`,
}
for _, test := range tests {
conf := loader.Config{Fset: token.NewFileSet()}
f, err := parser.ParseFile(conf.Fset, " ", test, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, ir.BuilderMode(0))
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
}
}
func TestUnreachableExit(t *testing.T) {
tests := []string{
`
package pkg
func foo() (err error) {
if true {
println()
}
println()
for {
err = nil
}
}`,
}
for _, test := range tests {
conf := loader.Config{Fset: token.NewFileSet()}
f, err := parser.ParseFile(conf.Fset, " ", test, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
conf.CreateFromFiles("pkg", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, ir.BuilderMode(0))
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
}
}
================================================
FILE: go/ir/const.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines the Const SSA value type.
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"strconv"
"strings"
"golang.org/x/exp/typeparams"
"honnef.co/go/tools/go/types/typeutil"
)
// NewConst returns a new constant of the specified value and type.
// val must be valid according to the specification of Const.Value.
func NewConst(val constant.Value, typ types.Type, source ast.Node) *Const {
c := &Const{
register: register{
typ: typ,
},
Value: val,
}
c.setSource(source)
return c
}
// intConst returns an 'int' constant that evaluates to i.
// (i is an int64 in case the host is narrower than the target.)
func intConst(i int64, source ast.Node) *Const {
return NewConst(constant.MakeInt64(i), tInt, source)
}
// nilConst returns a nil constant of the specified type, which may
// be any reference type, including interfaces.
func nilConst(typ types.Type, source ast.Node) *Const {
return NewConst(nil, typ, source)
}
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string, source ast.Node) *Const {
return NewConst(constant.MakeString(s), tString, source)
}
// zeroConst returns a new "zero" constant of the specified type.
func zeroConst(t types.Type, source ast.Node) Constant {
if _, ok := t.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(t) {
// Handle non-generic interface early to simplify following code.
return nilConst(t, source)
}
tset := typeutil.NewTypeSet(t)
switch typ := tset.CoreType().(type) {
case *types.Struct:
values := make([]Value, typ.NumFields())
for i := 0; i < typ.NumFields(); i++ {
values[i] = zeroConst(typ.Field(i).Type(), source)
}
ac := &AggregateConst{
register: register{typ: t},
Values: values,
}
ac.setSource(source)
return ac
case *types.Tuple:
values := make([]Value, typ.Len())
for i := 0; i < typ.Len(); i++ {
values[i] = zeroConst(typ.At(i).Type(), source)
}
ac := &AggregateConst{
register: register{typ: t},
Values: values,
}
ac.setSource(source)
return ac
}
isNillable := func(term *types.Term) bool {
switch typ := term.Type().Underlying().(type) {
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature, *typeutil.Iterator:
return true
case *types.Basic:
switch typ.Kind() {
case types.UnsafePointer, types.UntypedNil:
return true
default:
return false
}
default:
return false
}
}
isInfo := func(info types.BasicInfo) func(*types.Term) bool {
return func(term *types.Term) bool {
basic, ok := term.Type().Underlying().(*types.Basic)
if !ok {
return false
}
return (basic.Info() & info) != 0
}
}
isArray := func(term *types.Term) bool {
_, ok := term.Type().Underlying().(*types.Array)
return ok
}
switch {
case tset.All(isInfo(types.IsNumeric)):
return NewConst(constant.MakeInt64(0), t, source)
case tset.All(isInfo(types.IsString)):
return NewConst(constant.MakeString(""), t, source)
case tset.All(isInfo(types.IsBoolean)):
return NewConst(constant.MakeBool(false), t, source)
case tset.All(isNillable):
return nilConst(t, source)
case tset.All(isArray):
var k ArrayConst
k.setType(t)
k.setSource(source)
return &k
default:
var k GenericConst
k.setType(t)
k.setSource(source)
return &k
}
}
func (c *Const) RelString(from *types.Package) string {
var p string
if c.Value == nil {
p = "nil"
} else if c.Value.Kind() == constant.String {
v := constant.StringVal(c.Value)
const max = 20
// TODO(adonovan): don't cut a rune in half.
if len(v) > max {
v = v[:max-3] + "..." // abbreviate
}
p = strconv.Quote(v)
} else {
p = c.Value.String()
}
return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p)
}
func (c *Const) String() string {
if c.block == nil {
// Constants don't have a block till late in the compilation process. But we want to print consts during
// debugging.
return c.RelString(nil)
}
return c.RelString(c.Parent().pkg())
}
func (v *ArrayConst) RelString(pkg *types.Package) string {
return fmt.Sprintf("ArrayConst <%s>", relType(v.Type(), pkg))
}
func (v *ArrayConst) String() string {
return v.RelString(v.Parent().pkg())
}
func (v *AggregateConst) RelString(pkg *types.Package) string {
values := make([]string, len(v.Values))
for i, v := range v.Values {
if v != nil {
values[i] = v.Name()
} else {
values[i] = "nil"
}
}
return fmt.Sprintf("AggregateConst <%s> (%s)", relType(v.Type(), pkg), strings.Join(values, ", "))
}
func (v *AggregateConst) String() string {
if v.block == nil {
return v.RelString(nil)
}
return v.RelString(v.Parent().pkg())
}
func (v *GenericConst) RelString(pkg *types.Package) string {
return fmt.Sprintf("GenericConst <%s>", relType(v.Type(), pkg))
}
func (v *GenericConst) String() string {
return v.RelString(v.Parent().pkg())
}
// IsNil returns true if this constant represents a typed or untyped nil value.
func (c *Const) IsNil() bool {
return c.Value == nil
}
// Int64 returns the numeric value of this constant truncated to fit
// a signed 64-bit integer.
func (c *Const) Int64() int64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if i, ok := constant.Int64Val(x); ok {
return i
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return int64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Uint64 returns the numeric value of this constant truncated to fit
// an unsigned 64-bit integer.
func (c *Const) Uint64() uint64 {
switch x := constant.ToInt(c.Value); x.Kind() {
case constant.Int:
if u, ok := constant.Uint64Val(x); ok {
return u
}
return 0
case constant.Float:
f, _ := constant.Float64Val(x)
return uint64(f)
}
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
}
// Float64 returns the numeric value of this constant truncated to fit
// a float64.
func (c *Const) Float64() float64 {
f, _ := constant.Float64Val(c.Value)
return f
}
// Complex128 returns the complex value of this constant truncated to
// fit a complex128.
func (c *Const) Complex128() complex128 {
re, _ := constant.Float64Val(constant.Real(c.Value))
im, _ := constant.Float64Val(constant.Imag(c.Value))
return complex(re, im)
}
func (c *Const) equal(o Constant) bool {
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
oc, ok := o.(*Const)
if !ok {
return false
}
return c.typ == oc.typ && c.Value == oc.Value && c.source == oc.source
}
func (c *AggregateConst) equal(o Constant) bool {
oc, ok := o.(*AggregateConst)
if !ok {
return false
}
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
if c.typ != oc.typ {
return false
}
if c.source != oc.source {
return false
}
for i, v := range c.Values {
if !v.(Constant).equal(oc.Values[i].(Constant)) {
return false
}
}
return true
}
func (c *ArrayConst) equal(o Constant) bool {
oc, ok := o.(*ArrayConst)
if !ok {
return false
}
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
return c.typ == oc.typ && c.source == oc.source
}
func (c *GenericConst) equal(o Constant) bool {
oc, ok := o.(*GenericConst)
if !ok {
return false
}
// TODO(dh): don't use == for types, this will miss identical pointer types, among others
return c.typ == oc.typ && c.source == oc.source
}
================================================
FILE: go/ir/create.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the CREATE phase of IR construction.
// See builder.go for explanation.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"go/version"
"os"
"sync"
"honnef.co/go/tools/go/types/typeutil"
)
// measured on the standard library and rounded up to powers of two,
// on average there are 8 blocks and 16 instructions per block in a
// function.
const avgBlocks = 8
const avgInstructionsPerBlock = 16
// NewProgram returns a new IR Program.
//
// mode controls diagnostics and checking during IR construction.
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
Fset: fset,
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
mode: mode,
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h)
return prog
}
// memberFromObject populates package pkg with a member for the
// typechecker object obj.
//
// For objects from Go source code, syntax is the associated syntax tree
// (for funcs and vars only) and goversion defines the appropriate
// interpretation; they will be used during the build phase.
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) {
name := obj.Name()
switch obj := obj.(type) {
case *types.Builtin:
if pkg.Pkg != types.Unsafe {
panic("unexpected builtin object: " + obj.String())
}
case *types.TypeName:
if name != "_" {
pkg.Members[name] = &Type{
object: obj,
pkg: pkg,
}
}
case *types.Const:
c := &NamedConst{
object: obj,
Value: NewConst(obj.Val(), obj.Type(), syntax),
pkg: pkg,
}
pkg.values[obj] = c.Value
if name != "_" {
pkg.Members[name] = c
}
case *types.Var:
g := &Global{
Pkg: pkg,
name: name,
object: obj,
typ: types.NewPointer(obj.Type()), // address
}
pkg.values[obj] = g
if name != "_" {
pkg.Members[name] = g
}
case *types.Func:
sig := obj.Type().(*types.Signature)
if sig.Recv() == nil && name == "init" {
pkg.ninit++
name = fmt.Sprintf("init#%d", pkg.ninit)
}
fn := &Function{
name: name,
object: obj,
Signature: sig,
Pkg: pkg,
Prog: pkg.Prog,
goversion: goversion,
}
fn.source = syntax
fn.initHTML(pkg.printFunc)
if syntax == nil {
fn.Synthetic = SyntheticLoadedFromExportData
} else {
// Note: we initialize fn.Blocks in
// (*builder).buildFunction and not here because Blocks
// being nil is used to indicate that building of the
// function hasn't started yet.
fn.functionBody = &functionBody{
scratchInstructions: make([]Instruction, avgBlocks*avgInstructionsPerBlock),
}
}
pkg.values[obj] = fn
pkg.Functions = append(pkg.Functions, fn)
if name != "_" && sig.Recv() == nil {
pkg.Members[name] = fn // package-level function
}
default: // (incl. *types.Package)
panic("unexpected Object type: " + obj.String())
}
}
// membersFromDecl populates package pkg with members for each
// typechecker object (var, func, const or type) associated with the
// specified decl.
func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) {
switch decl := decl.(type) {
case *ast.GenDecl: // import, const, type or var
switch decl.Tok {
case token.CONST:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
}
}
case token.VAR:
for _, spec := range decl.Specs {
for _, rhs := range spec.(*ast.ValueSpec).Values {
pkg.initVersion[rhs] = goversion
}
for _, id := range spec.(*ast.ValueSpec).Names {
memberFromObject(pkg, pkg.info.Defs[id], spec, goversion)
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
memberFromObject(pkg, pkg.info.Defs[id], nil, "")
}
}
case *ast.FuncDecl:
id := decl.Name
obj, ok := pkg.info.Defs[id]
if !ok {
panic(fmt.Sprintf("couldn't find object for id %q at %s",
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
}
if obj == nil {
panic(fmt.Sprintf("found nil object for id %q at %s",
id.Name, pkg.Prog.Fset.PositionFor(id.Pos(), false)))
}
memberFromObject(pkg, obj, decl, goversion)
}
}
// CreatePackage constructs and returns an IR Package from the
// specified type-checked, error-free file ASTs, and populates its
// Members mapping.
//
// importable determines whether this package should be returned by a
// subsequent call to ImportedPackage(pkg.Path()).
//
// The real work of building IR form for each function is not done
// until a subsequent call to Package.Build().
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
p := &Package{
Prog: prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Pkg: pkg,
// transient values (CREATE and BUILD phases)
info: info,
files: files,
printFunc: prog.PrintFunc,
initVersion: make(map[ast.Expr]string),
}
// Add init() function.
p.init = &Function{
name: "init",
Signature: new(types.Signature),
Synthetic: SyntheticPackageInitializer,
Pkg: p,
Prog: prog,
functionBody: new(functionBody),
goversion: "", // See Package.build for details.
}
p.init.initHTML(prog.PrintFunc)
p.Members[p.init.name] = p.init
p.Functions = append(p.Functions, p.init)
// CREATE phase.
// Allocate all package members: vars, funcs, consts and types.
if len(files) > 0 {
// Go source package.
for _, file := range files {
goversion := version.Lang(p.info.FileVersions[file])
for _, decl := range file.Decls {
membersFromDecl(p, decl, goversion)
}
}
} else {
// GC-compiled binary package (or "unsafe")
// No code.
// No position information.
scope := p.Pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
memberFromObject(p, obj, nil, "")
if obj, ok := obj.(*types.TypeName); ok {
if named, ok := obj.Type().(*types.Named); ok {
for i, n := 0, named.NumMethods(); i < n; i++ {
memberFromObject(p, named.Method(i), nil, "")
}
}
}
}
}
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
name: "init$guard",
typ: types.NewPointer(tBool),
}
p.Members[initguard.Name()] = initguard
if prog.mode&GlobalDebug != 0 {
p.SetDebugMode(true)
}
if prog.mode&PrintPackages != 0 {
printMu.Lock()
p.WriteTo(os.Stdout)
printMu.Unlock()
}
if importable {
prog.imported[p.Pkg.Path()] = p
}
prog.packages[p.Pkg] = p
return p
}
// printMu serializes printing of Packages/Functions to stdout.
var printMu sync.Mutex
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
func (prog *Program) AllPackages() []*Package {
pkgs := make([]*Package, 0, len(prog.packages))
for _, pkg := range prog.packages {
pkgs = append(pkgs, pkg)
}
return pkgs
}
// ImportedPackage returns the importable Package whose PkgPath
// is path, or nil if no such Package has been created.
//
// A parameter to CreatePackage determines whether a package should be
// considered importable. For example, no import declaration can resolve
// to the ad-hoc main package created by 'go build foo.go'.
//
// TODO(adonovan): rethink this function and the "importable" concept;
// most packages are importable. This function assumes that all
// types.Package.Path values are unique within the ir.Program, which is
// false---yet this function remains very convenient.
// Clients should use (*Program).Package instead where possible.
// IR doesn't really need a string-keyed map of packages.
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]
}
func (prog *Program) SetNoReturn(fn func(*types.Func) bool) {
prog.noReturn = fn
}
================================================
FILE: go/ir/doc.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ir defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-information (SSI) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, upon which SSI builds, see
// https://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// https://www.dcs.gla.ac.uk/~jsinger/ssa.html.
//
// For an introduction to SSI form, see The static single information
// form by C. Scott Ananian.
//
// The level of abstraction of the IR form is intentionally close to
// the source language to facilitate construction of source analysis
// tools. It is not intended for machine code generation.
//
// The simplest way to create the IR of a package is
// to load typed syntax trees using golang.org/x/tools/go/packages, then
// invoke the irutil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// The resulting ir.Program contains all the packages and their
// members, but IR code is not created for function bodies until a
// subsequent call to (*Package).Build or (*Program).Build.
//
// The builder initially builds a naive IR form in which all local
// variables are addresses of stack locations with explicit loads and
// stores. Registerization of eligible locals and φ-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
// - Member: a named member of a Go package.
// - Value: an expression that yields a value.
// - Instruction: a statement that consumes values and performs computation.
// - Node: a Value or Instruction (emphasizing its membership in the IR value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces. The following table shows for each
// concrete type which of these interfaces it implements.
//
// Value? Instruction? Member?
// *Alloc ✔ ✔
// *BinOp ✔ ✔
// *BlankStore ✔
// *Builtin ✔
// *Call ✔ ✔
// *ChangeInterface ✔ ✔
// *ChangeType ✔ ✔
// *Const ✔ ✔
// *Convert ✔ ✔
// *DebugRef ✔
// *Defer ✔ ✔
// *Extract ✔ ✔
// *Field ✔ ✔
// *FieldAddr ✔ ✔
// *FreeVar ✔
// *Function ✔ ✔ (func)
// *Global ✔ ✔ (var)
// *Go ✔ ✔
// *If ✔
// *Index ✔ ✔
// *IndexAddr ✔ ✔
// *Jump ✔
// *Load ✔ ✔
// *MakeChan ✔ ✔
// *MakeClosure ✔ ✔
// *MakeInterface ✔ ✔
// *MakeMap ✔ ✔
// *MakeSlice ✔ ✔
// *MapLookup ✔ ✔
// *MapUpdate ✔ ✔
// *MultiConvert ✔ ✔
// *NamedConst ✔ (const)
// *Next ✔ ✔
// *Panic ✔
// *Parameter ✔ ✔
// *Phi ✔ ✔
// *Range ✔ ✔
// *Recv ✔ ✔
// *Return ✔
// *RunDefers ✔
// *Select ✔ ✔
// *Send ✔ ✔
// *Sigma ✔ ✔
// *Slice ✔ ✔
// *SliceToArrayPointer ✔ ✔
// *SliceToArray ✔ ✔
// *Store ✔ ✔
// *StringLookup ✔ ✔
// *Type ✔ (type)
// *TypeAssert ✔ ✔
// *UnOp ✔ ✔
// *Unreachable ✔
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
//
// The program representation constructed by this package is fully
// resolved internally, i.e. it does not rely on the names of Values,
// Packages, Functions, Types or BasicBlocks for the correct
// interpretation of the program. Only the identities of objects and
// the topology of the IR and type graphs are semantically
// significant. (There is one exception: Ids, used to identify field
// and method names, contain strings.) Avoidance of name-based
// operations simplifies the implementation of subsequent passes and
// can make them very efficient. Many objects are nonetheless named
// to aid in debugging, but it is not essential that the names be
// either accurate or unambiguous. The public API exposes a number of
// name-based maps for client convenience.
//
// The ir/irutil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ir.Values/Instructions.
package ir
================================================
FILE: go/ir/dom.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines algorithms related to dominance.
// Dominator tree construction ----------------------------------------
//
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
// algorithm for finding dominators in a flowgraph.
// https://doi.acm.org/10.1145/357062.357071
//
// We also apply the optimizations to SLT described in Georgiadis et
// al, Finding Dominators in Practice, JGAA 2006,
// https://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
// to avoid the need for buckets of size > 1.
import (
"bytes"
"fmt"
"io"
"math/big"
"os"
"sort"
)
// Idom returns the block that immediately dominates b:
// its parent in the dominator tree, if any.
// The entry node (b.Index==0) does not have a parent.
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
// Dominees returns the list of blocks that b immediately dominates:
// its children in the dominator tree.
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
// Dominates reports whether b dominates c.
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
}
type byDomPreorder []*BasicBlock
func (a byDomPreorder) Len() int { return len(a) }
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
// DomPreorder returns a new slice containing the blocks of f in
// dominator tree preorder.
func (f *Function) DomPreorder() []*BasicBlock {
n := len(f.Blocks)
order := make(byDomPreorder, n)
copy(order, f.Blocks)
sort.Sort(order)
return order
}
// domInfo contains a BasicBlock's dominance information.
type domInfo struct {
idom *BasicBlock // immediate dominator (parent in domtree)
children []*BasicBlock // nodes immediately dominated by this one
pre, post int32 // pre- and post-order numbering within domtree
}
// buildDomTree computes the dominator tree of f using the LT algorithm.
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
func buildDomTree(fn *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range fn.Blocks {
b.dom = domInfo{}
}
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, succ := range b.Succs {
dfs(succ)
}
if fn.fakeExits.Has(b) {
dfs(fn.Exit)
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Blocks[0])
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
idoms[fn.Blocks[0].Index] = fn.Blocks[0]
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// entry node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Preds {
do(p)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
do(p)
}
}
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].dom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.dom.children = append(b.dom.children, fn.Blocks[i])
}
numberDomTree(fn.Blocks[0], 0, 0)
// printDomTreeDot(os.Stderr, fn) // debugging
// printDomTreeText(os.Stderr, root, 0) // debugging
if fn.Prog.mode&SanityCheckFunctions != 0 {
sanityCheckDomTree(fn)
}
}
// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead.
func buildPostDomTree(fn *Function) {
// The step numbers refer to the original LT paper; the
// reordering is due to Georgiadis.
// Clear any previous domInfo.
for _, b := range fn.Blocks {
b.pdom = domInfo{}
}
idoms := make([]*BasicBlock, len(fn.Blocks))
order := make([]*BasicBlock, 0, len(fn.Blocks))
seen := fn.blockset(0)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, pred := range b.Preds {
dfs(pred)
}
if b == fn.Exit {
for _, p := range fn.Blocks {
if fn.fakeExits.Has(p) {
dfs(p)
}
}
}
order = append(order, b)
b.post = len(order) - 1
}
dfs(fn.Exit)
for i := 0; i < len(order)/2; i++ {
o := len(order) - i - 1
order[i], order[o] = order[o], order[i]
}
idoms[fn.Exit.Index] = fn.Exit
changed := true
for changed {
changed = false
// iterate over all nodes in reverse postorder, except for the
// exit node
for _, b := range order[1:] {
var newIdom *BasicBlock
do := func(p *BasicBlock) {
if idoms[p.Index] == nil {
return
}
if newIdom == nil {
newIdom = p
} else {
finger1 := p
finger2 := newIdom
for finger1 != finger2 {
for finger1.post < finger2.post {
finger1 = idoms[finger1.Index]
}
for finger2.post < finger1.post {
finger2 = idoms[finger2.Index]
}
}
newIdom = finger1
}
}
for _, p := range b.Succs {
do(p)
}
if fn.fakeExits.Has(b) {
do(fn.Exit)
}
if idoms[b.Index] != newIdom {
idoms[b.Index] = newIdom
changed = true
}
}
}
for i, b := range idoms {
fn.Blocks[i].pdom.idom = b
if b == nil {
// malformed CFG
continue
}
if i == b.Index {
continue
}
b.pdom.children = append(b.pdom.children, fn.Blocks[i])
}
numberPostDomTree(fn.Exit, 0, 0)
// printPostDomTreeDot(os.Stderr, fn) // debugging
// printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging
if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX
sanityCheckDomTree(fn) // XXX
}
}
// numberDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the dominator tree rooted at v. These are used to
// answer dominance queries in constant time.
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.dom.pre = pre
pre++
for _, child := range v.dom.children {
pre, post = numberDomTree(child, pre, post)
}
v.dom.post = post
post++
return pre, post
}
// numberPostDomTree sets the pre- and post-order numbers of a depth-first
// traversal of the post-dominator tree rooted at v. These are used to
// answer post-dominance queries in constant time.
func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
v.pdom.pre = pre
pre++
for _, child := range v.pdom.children {
pre, post = numberPostDomTree(child, pre, post)
}
v.pdom.post = post
post++
return pre, post
}
// Testing utilities ----------------------------------------
// sanityCheckDomTree checks the correctness of the dominator tree
// computed by the LT algorithm by comparing against the dominance
// relation computed by a naive Kildall-style forward dataflow
// analysis (Algorithm 10.16 from the "Dragon" book).
func sanityCheckDomTree(f *Function) {
n := len(f.Blocks)
// D[i] is the set of blocks that dominate f.Blocks[i],
// represented as a bit-set of block indices.
D := make([]big.Int, n)
one := big.NewInt(1)
// all is the set of all blocks; constant.
var all big.Int
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
// Initialization.
for i := range f.Blocks {
if i == 0 {
// A root is dominated only by itself.
D[i].SetBit(&D[0], 0, 1)
} else {
// All other blocks are (initially) dominated
// by every block.
D[i].Set(&all)
}
}
// Iteration until fixed point.
for changed := true; changed; {
changed = false
for i, b := range f.Blocks {
if i == 0 {
continue
}
// Compute intersection across predecessors.
var x big.Int
x.Set(&all)
for _, pred := range b.Preds {
x.And(&x, &D[pred.Index])
}
if b == f.Exit {
for _, p := range f.Blocks {
if f.fakeExits.Has(p) {
x.And(&x, &D[p.Index])
}
}
}
x.SetBit(&x, i, 1) // a block always dominates itself.
if D[i].Cmp(&x) != 0 {
D[i].Set(&x)
changed = true
}
}
}
// Check the entire relation. O(n^2).
ok := true
for i := range n {
for j := range n {
b, c := f.Blocks[i], f.Blocks[j]
actual := b.Dominates(c)
expected := D[j].Bit(i) == 1
if actual != expected {
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
ok = false
}
}
}
preorder := f.DomPreorder()
for _, b := range f.Blocks {
if got := preorder[b.dom.pre]; got != b {
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
ok = false
}
}
if !ok {
panic("sanityCheckDomTree failed for " + f.String())
}
}
// Printing functions ----------------------------------------
// printDomTree prints the dominator tree as text, using indentation.
//
//lint:ignore U1000 used during debugging
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.dom.children {
printDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//
//lint:ignore U1000 used during debugging
func printDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph domtree {")
for i, b := range f.Blocks {
v := b.dom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if i != 0 {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
fmt.Fprintln(buf, "}")
}
// printDomTree prints the dominator tree as text, using indentation.
//
//lint:ignore U1000 used during debugging
func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) {
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
for _, child := range v.pdom.children {
printPostDomTreeText(buf, child, indent+1)
}
}
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
// (.dot) format.
//
//lint:ignore U1000 used during debugging
func printPostDomTreeDot(buf io.Writer, f *Function) {
fmt.Fprintln(buf, "//", f)
fmt.Fprintln(buf, "digraph pdomtree {")
for _, b := range f.Blocks {
v := b.pdom
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
// TODO(adonovan): improve appearance of edges
// belonging to both dominator tree and CFG.
// Dominator tree edge.
if b != f.Exit {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre)
}
// CFG edges.
for _, pred := range b.Preds {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre)
}
if f.fakeExits.Has(b) {
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0,color=red];\n", b.dom.pre, f.Exit.dom.pre)
}
}
fmt.Fprintln(buf, "}")
}
================================================
FILE: go/ir/emit.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// Helpers for emitting IR instructions.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/exp/typeparams"
)
// emitAlloc emits to f a new Alloc instruction allocating a variable
// of type typ.
//
// The caller must set Alloc.Heap=true (for a heap-allocated variable)
// or add the Alloc to f.Locals (for a frame-allocated variable).
//
// During building, a variable in f.Locals may have its Heap flag
// set when it is discovered that its address is taken.
// These Allocs are removed from f.Locals at the end.
//
// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead.
func emitAlloc(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
v := &Alloc{}
v.comment = comment
v.setType(types.NewPointer(typ))
f.emit(v, source)
return v
}
// emitNew emits to f a new Alloc instruction heap-allocating a
// variable of type typ.
func emitNew(f *Function, typ types.Type, source ast.Node, comment string) *Alloc {
alloc := emitAlloc(f, typ, source, comment)
alloc.Heap = true
return alloc
}
// emitLocal creates a local var for (t, source, comment) and
// emits an Alloc instruction for it.
//
// (Use this function or emitNew for synthetic variables;
// for source-level variables, use emitLocalVar.)
func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Alloc {
local := emitAlloc(f, t, source, comment)
f.Locals = append(f.Locals, local)
return local
}
// emitLocalVar creates a local var for v and emits an Alloc instruction for it.
// Subsequent calls to f.lookup(v) return it.
func emitLocalVar(f *Function, v *types.Var, source ast.Node) *Alloc {
alloc := emitLocal(f, v.Type(), source, v.Name())
f.vars[v] = alloc
return alloc
}
// emitLoad emits to f an instruction to load the address addr into a
// new temporary, and returns the value so defined.
func emitLoad(f *Function, addr Value, source ast.Node) *Load {
v := &Load{X: addr}
v.setType(deref(addr.Type()))
f.emit(v, source)
return v
}
func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value {
recv := &Recv{
Chan: ch,
CommaOk: commaOk,
}
recv.setType(typ)
return f.emit(recv, source)
}
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
// expression e with value v.
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
ref := makeDebugRef(f, e, v, isAddr)
if ref == nil {
return
}
f.emit(ref, nil)
}
func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *DebugRef {
if !f.debugInfo() {
return nil // debugging not enabled
}
if v == nil || e == nil {
panic("nil")
}
var obj types.Object
e = unparen(e)
if id, ok := e.(*ast.Ident); ok {
if isBlankIdent(id) {
return nil
}
obj = f.Pkg.objectOf(id)
switch obj.(type) {
case *types.Nil, *types.Const, *types.Builtin:
return nil
}
}
return &DebugRef{
X: v,
Expr: e,
IsAddr: isAddr,
object: obj,
}
}
// emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
// non-eager operations.)
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value {
switch op {
case token.SHL, token.SHR:
x = emitConv(f, x, t, source)
// y may be signed or an 'untyped' constant.
// There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0
// and converting to an unsigned value (like the compiler) leave y as is.
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
// Untyped conversion:
// Spec https://go.dev/ref/spec#Operators:
// The right operand in a shift expression must have integer type or be an untyped constant
// representable by a value of type uint.
y = emitConv(f, y, types.Typ[types.Uint], source)
}
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
x = emitConv(f, x, t, source)
y = emitConv(f, y, t, source)
default:
panic("illegal op in emitArith: " + op.String())
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(t)
return f.emit(v, source)
}
// emitCompare emits to f code compute the boolean result of
// comparison 'x op y'.
func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value {
xt := x.Type().Underlying()
yt := y.Type().Underlying()
// Special case to optimise a tagless SwitchStmt so that
// these are equivalent
// switch { case e: ...}
// switch true { case e: ... }
// if e==true { ... }
// even in the case when e's type is an interface.
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
if x, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) {
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
return y
}
}
if types.Identical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok && !typeparams.IsTypeParam(x.Type()) {
y = emitConv(f, y, x.Type(), source)
} else if _, ok := yt.(*types.Interface); ok && !typeparams.IsTypeParam(y.Type()) {
x = emitConv(f, x, y.Type(), source)
} else if _, ok := x.(*Const); ok {
x = emitConv(f, x, y.Type(), source)
} else if _, ok := y.(*Const); ok {
y = emitConv(f, y, x.Type(), source)
//lint:ignore SA9003 no-op
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(tBool)
return f.emit(v, source)
}
// isValuePreserving returns true if a conversion from ut_src to
// ut_dst is value-preserving, i.e. just a change of type.
// Precondition: neither argument is a named type.
func isValuePreserving(ut_src, ut_dst types.Type) bool {
// Identical underlying types?
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
return true
}
switch ut_dst.(type) {
case *types.Chan:
// Conversion between channel types?
_, ok := ut_src.(*types.Chan)
return ok
case *types.Pointer:
// Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer)
return ok
}
return false
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are required
// by language assignability rules in assignments, parameter passing,
// etc.
func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value {
t_src := val.Type()
// Identical types? Conversion is a no-op.
if types.Identical(t_src, t_dst) {
return val
}
ut_dst := t_dst.Underlying()
ut_src := t_src.Underlying()
// Conversion to, or construction of a value of, an interface type?
if isNonTypeParamInterface(t_dst) {
// Interface name change?
if isValuePreserving(ut_src, ut_dst) {
c := &ChangeType{X: val}
c.setType(t_dst)
return f.emit(c, source)
}
// Assignment from one interface type to another?
if isNonTypeParamInterface(t_src) {
c := &ChangeInterface{X: val}
c.setType(t_dst)
return f.emit(c, source)
}
// Untyped nil constant? Return interface-typed nil constant.
if ut_src == tUntypedNil {
return emitConst(f, zeroConst(t_dst, source))
}
// Convert (non-nil) "untyped" literals to their default type.
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
val = emitConv(f, val, types.Default(ut_src), source)
}
f.Pkg.Prog.needMethodsOf(val.Type())
mi := &MakeInterface{X: val}
mi.setType(t_dst)
return f.emit(mi, source)
}
// In the common case, the typesets of src and dst are singletons
// and we emit an appropriate conversion. But if either contains
// a type parameter, the conversion may represent a cross product,
// in which case which we emit a MultiConvert.
tset_dst := typeutil.NewTypeSet(ut_dst)
tset_src := typeutil.NewTypeSet(ut_src)
// conversionCase describes an instruction pattern that may be emitted to
// model d <- s for d in dst_terms and s in src_terms.
// Multiple conversions can match the same pattern.
type conversionCase uint8
const (
changeType conversionCase = 1 << iota
sliceToArray
sliceToArrayPtr
sliceTo0Array
sliceTo0ArrayPtr
convert
)
classify := func(s, d types.Type) conversionCase {
// Just a change of type, but not value or representation?
if isValuePreserving(s, d) {
return changeType
}
// Conversion from slice to array or slice to array pointer?
if slice, ok := s.(*types.Slice); ok {
var arr *types.Array
var ptr bool
// Conversion from slice to array pointer?
switch d := d.(type) {
case *types.Array:
arr = d
case *types.Pointer:
arr, _ = d.Elem().Underlying().(*types.Array)
ptr = true
}
if arr != nil && types.Identical(slice.Elem(), arr.Elem()) {
if arr.Len() == 0 {
if ptr {
return sliceTo0ArrayPtr
} else {
return sliceTo0Array
}
}
if ptr {
return sliceToArrayPtr
} else {
return sliceToArray
}
}
}
// The only remaining case in well-typed code is a representation-
// changing conversion of basic types (possibly with []byte/[]rune).
if !isBasic(s) && !isBasic(d) {
panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, t_dst, d))
}
return convert
}
var classifications conversionCase
for _, s := range tset_src.Terms {
us := s.Type().Underlying()
for _, d := range tset_dst.Terms {
ud := d.Type().Underlying()
classifications |= classify(us, ud)
}
}
if classifications == 0 {
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst))
}
// Conversion of a compile-time constant value?
if c, ok := val.(*Const); ok {
// Conversion to a basic type?
if isBasic(ut_dst) {
// Conversion of a compile-time constant to
// another constant type results in a new
// constant of the destination type and
// (initially) the same abstract value.
// We don't truncate the value yet.
return emitConst(f, NewConst(c.Value, t_dst, source))
}
// Can we always convert from zero value without panicking?
const mayPanic = sliceToArray | sliceToArrayPtr
if c.Value == nil && classifications&mayPanic == 0 {
return emitConst(f, NewConst(nil, t_dst, source))
}
// We're converting from constant to non-constant type,
// e.g. string -> []byte/[]rune.
}
switch classifications {
case changeType: // representation-preserving change
c := &ChangeType{X: val}
c.setType(t_dst)
return f.emit(c, source)
case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer
c := &SliceToArrayPointer{X: val}
c.setType(t_dst)
return f.emit(c, source)
case sliceToArray: // slice to arrays (not zero-length)
p := &SliceToArray{X: val}
p.setType(t_dst)
return f.emit(p, source)
case sliceTo0Array: // slice to zero-length arrays (constant)
return emitConst(f, zeroConst(t_dst, source))
case convert: // representation-changing conversion
c := &Convert{X: val}
c.setType(t_dst)
return f.emit(c, source)
default: // multiple conversion
c := &MultiConvert{X: val, from: tset_src, to: tset_dst}
c.setType(t_dst)
return f.emit(c, source)
}
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignability rules.
func emitStore(f *Function, addr, val Value, source ast.Node) *Store {
s := &Store{
Addr: addr,
Val: emitConv(f, val, deref(addr.Type()), source),
}
f.emit(s, source)
return s
}
// emitJump emits to f a jump to target, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
func emitJump(f *Function, target *BasicBlock, source ast.Node) *Jump {
b := f.currentBlock
j := new(Jump)
b.emit(j, source)
addEdge(b, target)
f.currentBlock = nil
return j
}
// emitIf emits to f a conditional jump to tblock or fblock based on
// cond, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock, source ast.Node) *If {
b := f.currentBlock
stmt := &If{Cond: cond}
b.emit(stmt, source)
addEdge(b, tblock)
addEdge(b, fblock)
f.currentBlock = nil
return stmt
}
// emitExtract emits to f an instruction to extract the index'th
// component of tuple. It returns the extracted value.
func emitExtract(f *Function, tuple Value, index int, source ast.Node) Value {
e := &Extract{Tuple: tuple, Index: index}
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
return f.emit(e, source)
}
// emitTypeAssert emits to f a type assertion value := x.(t) and
// returns the value. x.Type() must be an interface.
func emitTypeAssert(f *Function, x Value, t types.Type, source ast.Node) Value {
a := &TypeAssert{X: x, AssertedType: t}
a.setType(t)
return f.emit(a, source)
}
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
// a (value, ok) tuple. x.Type() must be an interface.
func emitTypeTest(f *Function, x Value, t types.Type, source ast.Node) Value {
a := &TypeAssert{
X: x,
AssertedType: t,
CommaOk: true,
}
a.setType(types.NewTuple(
newVar("value", t),
varOk,
))
return f.emit(a, source)
}
// emitTailCall emits to f a function call in tail position. The
// caller is responsible for all fields of 'call' except its type.
// Intended for wrapper methods.
// Precondition: f does/will not use deferred procedure calls.
// Postcondition: f.currentBlock is nil.
func emitTailCall(f *Function, call *Call, source ast.Node) {
tresults := f.Signature.Results()
nr := tresults.Len()
if nr == 1 {
call.typ = tresults.At(0).Type()
} else {
call.typ = tresults
}
tuple := f.emit(call, source)
var ret Return
switch nr {
case 0:
// no-op
case 1:
ret.Results = []Value{tuple}
default:
for i := range nr {
v := emitExtract(f, tuple, i, source)
// TODO(adonovan): in principle, this is required:
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
// but in practice emitTailCall is only used when
// the types exactly match.
ret.Results = append(ret.Results, v)
}
}
f.Exit = f.newBasicBlock("exit")
emitJump(f, f.Exit, source)
f.currentBlock = f.Exit
f.emit(&ret, source)
f.currentBlock = nil
}
func emitCall(fn *Function, call *Call, source ast.Node) Value {
res := fn.emit(call, source)
callee := call.Call.StaticCallee()
if callee != nil &&
callee.object != nil &&
fn.Prog.noReturn != nil &&
fn.Prog.noReturn(callee.object) {
// Call doesn't return normally. Either it doesn't return at all
// (infinitely blocked or exitting the process), or it unwinds the stack
// (panic, runtime.Goexit). In case it unwinds, jump to the exit block.
fn.emit(new(Jump), source)
addEdge(fn.currentBlock, fn.Exit)
fn.currentBlock = fn.newBasicBlock("unreachable")
}
return res
}
// emitImplicitSelections emits to f code to apply the sequence of
// implicit field selections specified by indices to base value v, and
// returns the selected value.
//
// If v is the address of a struct, the result will be the address of
// a field; if it is the value of a struct, the result will be the
// value of a field.
func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value {
for _, index := range indices {
// We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A
// pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets
// dereferenced implicitly before we get here.
fld := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr, source)
// Load the field's value iff indirectly embedded.
if isPointer(fld.Type()) {
v = emitLoad(f, v, source)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setType(fld.Type())
v = f.emit(instr, source)
}
}
return v
}
// emitFieldSelection emits to f code to select the index'th field of v.
//
// If wantAddr, the input must be a pointer-to-struct and the result
// will be the field's address; otherwise the result will be the
// field's value.
// Ident id is used for position and debug info.
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
// We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A
// pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets
// dereferenced implicitly before we get here.
vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct)
fld := vut.Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
Field: index,
}
instr.setSource(id)
instr.setType(types.NewPointer(fld.Type()))
v = f.emit(instr, id)
// Load the field's value iff we don't want its address.
if !wantAddr {
v = emitLoad(f, v, id)
}
} else {
instr := &Field{
X: v,
Field: index,
}
instr.setSource(id)
instr.setType(fld.Type())
v = f.emit(instr, id)
}
emitDebugRef(f, id, v, wantAddr)
return v
}
// zeroValue emits to f code to produce a zero value of type t,
// and returns it.
func zeroValue(f *Function, t types.Type, source ast.Node) Value {
return emitConst(f, zeroConst(t, source))
}
type constKey struct {
typ types.Type
value constant.Value
source ast.Node
}
func emitConst(f *Function, c Constant) Constant {
if f.consts == nil {
f.consts = map[constKey]constValue{}
}
typ := c.Type()
var val constant.Value
switch c := c.(type) {
case *Const:
val = c.Value
case *ArrayConst, *GenericConst:
// These can only represent zero values, so all we need is the type
case *AggregateConst:
candidates, _ := f.aggregateConsts.At(c.typ)
for _, candidate := range candidates {
if c.equal(candidate) {
return candidate
}
}
for i := range c.Values {
c.Values[i] = emitConst(f, c.Values[i].(Constant))
}
c.setBlock(f.Blocks[0])
rands := c.Operands(nil)
updateOperandsReferrers(c, rands)
candidates = append(candidates, c)
f.aggregateConsts.Set(c.typ, candidates)
return c
default:
panic(fmt.Sprintf("unexpected type %T", c))
}
k := constKey{
typ: typ,
value: val,
source: c.Source(),
}
dup, ok := f.consts[k]
if ok {
return dup.c
} else {
c.setBlock(f.Blocks[0])
f.consts[k] = constValue{
c: c,
idx: len(f.consts),
}
rands := c.Operands(nil)
updateOperandsReferrers(c, rands)
return c
}
}
================================================
FILE: go/ir/example_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir_test
import (
"bytes"
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"strings"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/packages"
)
const hello = `
package main
import "fmt"
const message = "Hello, World!"
func main() {
fmt.Println(message)
}
`
// This program demonstrates how to run the IR builder on a single
// package of one or more already-parsed files. Its dependencies are
// loaded from compiler export data. This is what you'd typically use
// for a compiler; it does not depend on golang.org/x/tools/go/loader.
//
// It shows the printed representation of packages, functions, and
// instructions. Within the function listing, the name of each
// BasicBlock such as ".0.entry" is printed left-aligned, followed by
// the block's Instructions.
//
// For each instruction that defines an IR virtual register
// (i.e. implements Value), the type of that value is shown in the
// right column.
//
// Build and run the irdump.go program if you want a standalone tool
// with similar functionality. It is located at
// honnef.co/go/tools/internal/cmd/irdump.
func Example_buildPackage() {
// Parse the source files.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
fmt.Print(err) // parse error
return
}
files := []*ast.File{f}
// Create the type-checker's package.
pkg := types.NewPackage("hello", "")
// Type-check the package, load dependencies.
// Create and build the IR program.
hello, _, err := irutil.BuildPackage(
&types.Config{Importer: importer.Default()}, fset, pkg, files, ir.SanityCheckFunctions)
if err != nil {
fmt.Print(err) // type error in some package
return
}
// Print out the package.
hello.WriteTo(os.Stdout)
// Print out the package-level functions.
// Replace interface{} with any so the tests work for Go 1.17 and Go 1.18.
{
var buf bytes.Buffer
ir.WriteFunction(&buf, hello.Func("init"))
fmt.Print(strings.ReplaceAll(buf.String(), "interface{}", "any"))
}
{
var buf bytes.Buffer
ir.WriteFunction(&buf, hello.Func("main"))
fmt.Print(strings.ReplaceAll(buf.String(), "interface{}", "any"))
}
// Output:
// package hello:
// func init func()
// var init$guard bool
// func main func()
// const message message = Const {"Hello, World!"}
//
// # Name: hello.init
// # Package: hello
// # Synthetic: package initializer
// func init():
// b0: # entry
// t1 = Const {true}
// t2 = Load init$guard
// If t2 → b1 b2
//
// b1: ← b0 b2 # exit
// Return
//
// b2: ← b0 # init.start
// Store {bool} init$guard t1
// t6 = Call <()> fmt.init
// Jump → b1
//
// # Name: hello.main
// # Package: hello
// # Location: hello.go:8:1
// func main():
// b0: # entry
// t1 = Const {"Hello, World!"}
// t2 = Const {0}
// t3 = HeapAlloc <*[1]any> # varargs
// t4 = IndexAddr <*any> t3 t2
// t5 = MakeInterface t1
// Store {any} t4 t5
// t7 = Slice <[]any> t3
// t8 = Call <(n int, err error)> fmt.Println t7
// Jump → b1
//
// b1: ← b0 # exit
// Return
}
// This example builds IR code for a set of packages using the
// x/tools/go/packages API. This is what you would typically use for a
// analysis capable of operating on a single package.
func Example_loadPackages() {
// Load, parse, and type-check the initial packages.
cfg := &packages.Config{Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo}
initial, err := packages.Load(cfg, "fmt", "net/http")
if err != nil {
log.Fatal(err)
}
// Stop if any package had errors.
// This step is optional; without it, the next step
// will create IR for only a subset of packages.
if packages.PrintErrors(initial) > 0 {
log.Fatalf("packages contain errors")
}
// Create IR packages for all well-typed packages.
prog, pkgs := irutil.Packages(initial, ir.PrintPackages, nil)
_ = prog
// Build IR code for the well-typed initial packages.
for _, p := range pkgs {
if p != nil {
p.Build()
}
}
}
// This example builds IR code for a set of packages plus all their dependencies,
// using the x/tools/go/packages API.
// This is what you'd typically use for a whole-program analysis.
func Example_loadWholeProgram() {
// Load, parse, and type-check the whole program.
cfg := packages.Config{Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps}
initial, err := packages.Load(&cfg, "fmt", "net/http")
if err != nil {
log.Fatal(err)
}
// Create IR packages for well-typed packages and their dependencies.
prog, pkgs := irutil.AllPackages(initial, ir.PrintPackages, nil)
_ = pkgs
// Build IR code for the whole program.
prog.Build()
}
================================================
FILE: go/ir/func.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the Function and BasicBlock types.
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
"io"
"os"
"sort"
"strings"
"honnef.co/go/tools/go/types/typeutil"
)
// addEdge adds a control-flow graph edge from from to to.
func addEdge(from, to *BasicBlock) {
from.Succs = append(from.Succs, to)
to.Preds = append(to.Preds, from)
}
// Control returns the last instruction in the block.
func (b *BasicBlock) Control() Instruction {
if len(b.Instrs) == 0 {
return nil
}
return b.Instrs[len(b.Instrs)-1]
}
// SigmaFor returns the sigma node for v coming from pred.
func (b *BasicBlock) SigmaFor(v Value, pred *BasicBlock) *Sigma {
for _, instr := range b.Instrs {
sigma, ok := instr.(*Sigma)
if !ok {
// no more sigmas
return nil
}
if sigma.From == pred && sigma.X == v {
return sigma
}
}
return nil
}
// Parent returns the function that contains block b.
func (b *BasicBlock) Parent() *Function { return b.parent }
// String returns a human-readable label of this block.
// It is not guaranteed unique within the function.
func (b *BasicBlock) String() string {
return fmt.Sprintf("%d", b.Index)
}
// emit appends an instruction to the current basic block.
// If the instruction defines a Value, it is returned.
func (b *BasicBlock) emit(i Instruction, source ast.Node) Value {
i.setSource(source)
i.setBlock(b)
b.Instrs = append(b.Instrs, i)
v, _ := i.(Value)
return v
}
// predIndex returns the i such that b.Preds[i] == c or panics if
// there is none.
func (b *BasicBlock) predIndex(c *BasicBlock) int {
for i, pred := range b.Preds {
if pred == c {
return i
}
}
panic(fmt.Sprintf("no edge %s -> %s", c, b))
}
// succIndex returns the i such that b.Succs[i] == c or -1 if there is none.
func (b *BasicBlock) succIndex(c *BasicBlock) int {
for i, succ := range b.Succs {
if succ == c {
return i
}
}
return -1
}
// hasPhi returns true if b.Instrs contains φ-nodes.
func (b *BasicBlock) hasPhi() bool {
_, ok := b.Instrs[0].(*Phi)
return ok
}
func (b *BasicBlock) Phis() []Instruction {
return b.phis()
}
// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
func (b *BasicBlock) phis() []Instruction {
for i, instr := range b.Instrs {
if _, ok := instr.(*Phi); !ok {
return b.Instrs[:i]
}
}
return nil // unreachable in well-formed blocks
}
// replacePred replaces all occurrences of p in b's predecessor list with q.
// Ordinarily there should be at most one.
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
for i, pred := range b.Preds {
if pred == p {
b.Preds[i] = q
}
}
}
// replaceSucc replaces all occurrences of p in b's successor list with q.
// Ordinarily there should be at most one.
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
for i, succ := range b.Succs {
if succ == p {
b.Succs[i] = q
}
}
}
// removePred removes all occurrences of p in b's
// predecessor list and φ-nodes.
// Ordinarily there should be at most one.
func (b *BasicBlock) removePred(p *BasicBlock) {
phis := b.phis()
// We must preserve edge order for φ-nodes.
j := 0
for i, pred := range b.Preds {
if pred != p {
b.Preds[j] = b.Preds[i]
// Strike out φ-edge too.
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges[j] = phi.Edges[i]
}
j++
}
}
// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
for i := j; i < len(b.Preds); i++ {
b.Preds[i] = nil
for _, instr := range phis {
instr.(*Phi).Edges[i] = nil
}
}
b.Preds = b.Preds[:j]
for _, instr := range phis {
phi := instr.(*Phi)
phi.Edges = phi.Edges[:j]
}
}
// Destinations associated with unlabelled for/switch/select stmts.
// We push/pop one of these as we enter/leave each construct and for
// each BranchStmt we scan for the innermost target of the right type.
type targets struct {
tail *targets // rest of stack
_break *BasicBlock
_continue *BasicBlock
_fallthrough *BasicBlock
}
// Destinations associated with a labelled block.
// We populate these as labels are encountered in forward gotos or
// labelled statements.
// Forward gotos are resolved once it is known which statement they
// are associated with inside the Function.
type lblock struct {
label *types.Label // Label targeted by the blocks.
resolved bool // _goto block encountered (back jump or resolved fwd jump)
_goto *BasicBlock
_break *BasicBlock
_continue *BasicBlock
}
// label returns the symbol denoted by a label identifier.
//
// label should be a non-blank identifier (label.Name != "_").
func (f *Function) label(label *ast.Ident) *types.Label {
return f.Pkg.objectOf(label).(*types.Label)
}
// lblockOf returns the branch target associated with the
// specified label, creating it if needed.
func (f *Function) lblockOf(label *types.Label) *lblock {
lb := f.lblocks[label]
if lb == nil {
lb = &lblock{
label: label,
_goto: f.newBasicBlock(label.Name()),
}
if f.lblocks == nil {
f.lblocks = make(map[*types.Label]*lblock)
}
f.lblocks[label] = lb
}
return lb
}
// labelledBlock searches f for the block of the specified label.
//
// If f is a yield function, it additionally searches ancestor Functions
// corresponding to enclosing range-over-func statements within the
// same source function, so the returned block may belong to a different Function.
func labelledBlock(f *Function, label *types.Label, tok token.Token) *BasicBlock {
if lb := f.lblocks[label]; lb != nil {
var block *BasicBlock
switch tok {
case token.BREAK:
block = lb._break
case token.CONTINUE:
block = lb._continue
case token.GOTO:
block = lb._goto
}
if block != nil {
return block
}
}
// Search ancestors if this is a yield function.
if f.jump != nil {
return labelledBlock(f.parent, label, tok)
}
return nil
}
// targetedBlock looks for the nearest block in f.targets
// (and f's ancestors) that matches tok's type, and returns
// the block and function it was found in.
func targetedBlock(f *Function, tok token.Token) *BasicBlock {
if f == nil {
return nil
}
for t := f.targets; t != nil; t = t.tail {
var block *BasicBlock
switch tok {
case token.BREAK:
block = t._break
case token.CONTINUE:
block = t._continue
case token.FALLTHROUGH:
block = t._fallthrough
}
if block != nil {
return block
}
}
// Search f's ancestors (in case f is a yield function).
return targetedBlock(f.parent, tok)
}
// addResultVar adds a result for a variable v to f.results and v to f.returnVars.
func (f *Function) addResultVar(v *types.Var, source ast.Node) {
name := v.Name()
if name == "" {
name = fmt.Sprintf("res.%d", len(f.results))
}
result := emitLocalVar(f, v, source)
result.comment = name
f.results = append(f.results, result)
f.returnVars = append(f.returnVars, v)
}
func (f *Function) addParamVar(v *types.Var, source ast.Node) *Parameter {
name := v.Name()
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
var b *BasicBlock
if len(f.Blocks) > 0 {
b = f.Blocks[0]
}
param := &Parameter{name: name}
param.setBlock(b)
param.setType(v.Type())
param.setSource(source)
param.object = v
f.Params = append(f.Params, param)
if b != nil {
f.Blocks[0].Instrs = append(f.Blocks[0].Instrs, param)
}
return param
}
// addSpilledParam declares a parameter that is pre-spilled to the
// stack; the function body will load/store the spilled location.
// Subsequent lifting will eliminate spills where possible.
func (f *Function) addSpilledParam(obj *types.Var, source ast.Node) {
param := f.addParamVar(obj, source)
spill := emitLocalVar(f, obj, source)
emitStore(f, spill, param, source)
// f.emit(&Store{Addr: spill, Val: param})
}
// startBody initializes the function prior to generating IR code for its body.
// Precondition: f.Type() already set.
func (f *Function) startBody() {
entry := f.newBasicBlock("entry")
f.currentBlock = entry
f.vars = make(map[*types.Var]Value) // needed for some synthetics, e.g. init
}
func (f *Function) blockset(i int) *BlockSet {
bs := &f.blocksets[i]
if len(bs.values) != len(f.Blocks) {
if cap(bs.values) >= len(f.Blocks) {
bs.values = bs.values[:len(f.Blocks)]
bs.Clear()
} else {
bs.values = make([]bool, len(f.Blocks))
}
} else {
bs.Clear()
}
return bs
}
func (f *Function) exitBlock() {
old := f.currentBlock
f.Exit = f.newBasicBlock("exit")
f.currentBlock = f.Exit
results := make([]Value, len(f.results))
// Run function calls deferred in this
// function when explicitly returning from it.
f.emit(new(RunDefers), nil)
for i, r := range f.results {
results[i] = emitLoad(f, r, nil)
}
f.emit(&Return{Results: results}, nil)
f.currentBlock = old
}
// createSyntacticParams populates f.Params and generates code (spills
// and named result locals) for all the parameters declared in the
// syntax. In addition it populates the f.objects mapping.
//
// Preconditions:
// f.startBody() was called.
// Postcondition:
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
// Receiver (at most one inner iteration).
if recv != nil {
for _, field := range recv.List {
for _, n := range field.Names {
f.addSpilledParam(identVar(f, n), n)
}
// Anonymous receiver? No need to spill.
if field.Names == nil {
f.addParamVar(f.Signature.Recv(), field)
}
}
}
// Parameters.
if functype.Params != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range functype.Params.List {
for _, n := range field.Names {
f.addSpilledParam(identVar(f, n), n)
}
// Anonymous parameter? No need to spill.
if field.Names == nil {
f.addParamVar(f.Signature.Params().At(len(f.Params)-n), field)
}
}
}
// Results.
if functype.Results != nil {
for _, field := range functype.Results.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
v := identVar(f, n)
f.addResultVar(v, n)
}
// Implicit "var" decl of local for an unnamed result.
if field.Names == nil {
v := f.Signature.Results().At(len(f.results))
f.addResultVar(v, field.Type)
}
}
}
}
// createDeferStack initializes fn.deferstack to a local variable
// initialized to a ssa:deferstack() call.
func (fn *Function) createDeferStack() {
// Each syntactic function makes a call to ssa:deferstack,
// which is spilled to a local. Unused ones are later removed.
fn.deferstack = newVar("defer$stack", tDeferStack)
call := &Call{Call: CallCommon{Value: vDeferStack}}
call.setType(tDeferStack)
deferstack := fn.emit(call, nil)
spill := emitLocalVar(fn, fn.deferstack, nil)
emitStore(fn, spill, deferstack, nil)
}
func numberNodes(f *Function) {
var base ID
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if instr == nil {
continue
}
base++
instr.setID(base)
}
}
}
func updateOperandsReferrers(instr Instruction, ops []*Value) {
for _, op := range ops {
if r := *op; r != nil {
if refs := (*op).Referrers(); refs != nil {
if len(*refs) == 0 {
// per median, each value has two referrers, so we can avoid one call into growslice
//
// Note: we experimented with allocating
// sequential scratch space, but we
// couldn't find a value that gave better
// performance than making many individual
// allocations
*refs = make([]Instruction, 1, 2)
(*refs)[0] = instr
} else {
*refs = append(*refs, instr)
}
}
}
}
}
// buildReferrers populates the def/use information in all non-nil
// Value.Referrers slice.
// Precondition: all such slices are initially empty.
func buildReferrers(f *Function) {
var rands []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
rands = instr.Operands(rands[:0]) // recycle storage
updateOperandsReferrers(instr, rands)
}
}
for _, c := range f.consts {
rands = c.c.Operands(rands[:0])
updateOperandsReferrers(c.c, rands)
}
}
func (f *Function) emitConsts() {
defer func() {
f.consts = nil
f.aggregateConsts = typeutil.Map[[]*AggregateConst]{}
}()
if len(f.Blocks) == 0 {
return
}
// TODO(dh): our deduplication only works on booleans and
// integers. other constants are represented as pointers to
// things.
head := make([]constValue, 0, len(f.consts))
for _, c := range f.consts {
if len(*c.c.Referrers()) == 0 {
// TODO(dh): killing a const may make other consts dead, too
killInstruction(c.c)
} else {
head = append(head, c)
}
}
sort.Slice(head, func(i, j int) bool {
return head[i].idx < head[j].idx
})
entry := f.Blocks[0]
instrs := make([]Instruction, 0, len(entry.Instrs)+len(head))
for _, c := range head {
instrs = append(instrs, c.c)
}
f.aggregateConsts.Iterate(func(key types.Type, value []*AggregateConst) {
for _, c := range value {
instrs = append(instrs, c)
}
})
instrs = append(instrs, entry.Instrs...)
entry.Instrs = instrs
}
// buildFakeExits ensures that every block in the function is
// reachable in reverse from the Exit block. This is required to build
// a full post-dominator tree, and to ensure the exit block's
// inclusion in the dominator tree.
func buildFakeExits(fn *Function) {
// Find back-edges via forward DFS
fn.fakeExits = BlockSet{values: make([]bool, len(fn.Blocks))}
seen := fn.blockset(0)
backEdges := fn.blockset(1)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
backEdges.Add(b)
return
}
for _, pred := range b.Succs {
dfs(pred)
}
}
dfs(fn.Blocks[0])
buildLoop:
for {
seen := fn.blockset(2)
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if !seen.Add(b) {
return
}
for _, pred := range b.Preds {
dfs(pred)
}
if b == fn.Exit {
for _, b := range fn.Blocks {
if fn.fakeExits.Has(b) {
dfs(b)
}
}
}
}
dfs(fn.Exit)
for _, b := range fn.Blocks {
if !seen.Has(b) && backEdges.Has(b) {
// Block b is not reachable from the exit block. Add a
// fake jump from b to exit, then try again. Note that we
// only add one fake edge at a time, as it may make
// multiple blocks reachable.
//
// We only consider those blocks that have back edges.
// Any unreachable block that doesn't have a back edge
// must flow into a loop, which by definition has a
// back edge. Thus, by looking for loops, we should
// need fewer fake edges overall.
fn.fakeExits.Add(b)
continue buildLoop
}
}
break
}
}
// finishBody() finalizes the function after IR code generation of its body.
func (f *Function) finishBody() {
f.currentBlock = nil
f.lblocks = nil
// Remove from f.Locals any Allocs that escape to the heap.
j := 0
for _, l := range f.Locals {
if !l.Heap {
f.Locals[j] = l
j++
}
}
// Nil out f.Locals[j:] to aid GC.
for i := j; i < len(f.Locals); i++ {
f.Locals[i] = nil
}
f.Locals = f.Locals[:j]
optimizeBlocks(f)
buildFakeExits(f)
buildReferrers(f)
buildDomTree(f)
buildPostDomTree(f)
if f.Prog.mode&NaiveForm == 0 {
for lift(f) {
}
if doSimplifyConstantCompositeValues {
for simplifyConstantCompositeValues(f) {
}
}
}
// emit constants after lifting, because lifting may produce new constants, but before other variable splitting,
// because it expects constants to have been deduplicated.
f.emitConsts()
if f.Prog.mode&SplitAfterNewInformation != 0 {
splitOnNewInformation(f.Blocks[0], &StackMap{})
}
// clear remaining builder state
f.results = nil // (used by lifting)
f.deferstack = nil // (used by lifting)
f.vars = nil // (used by lifting)
f.goversion = ""
numberNodes(f)
defer f.wr.Close()
f.wr.WriteFunc("start", "start", f)
if f.Prog.mode&PrintFunctions != 0 {
printMu.Lock()
f.WriteTo(os.Stdout)
printMu.Unlock()
}
if f.Prog.mode&SanityCheckFunctions != 0 {
mustSanityCheck(f, nil)
}
}
func isUselessPhi(phi *Phi) (Value, bool) {
var v0 Value
for _, e := range phi.Edges {
if e == phi {
continue
}
if v0 == nil {
v0 = e
}
if v0 != e {
if v0, ok := v0.(*Const); ok {
if e, ok := e.(*Const); ok {
if v0.typ == e.typ && v0.Value == e.Value {
continue
}
}
}
return nil, false
}
}
return v0, true
}
func (f *Function) RemoveNilBlocks() {
f.removeNilBlocks()
}
// removeNilBlocks eliminates nils from f.Blocks and updates each
// BasicBlock.Index. Use this after any pass that may delete blocks.
func (f *Function) removeNilBlocks() {
j := 0
for _, b := range f.Blocks {
if b != nil {
b.Index = j
f.Blocks[j] = b
j++
}
}
// Nil out f.Blocks[j:] to aid GC.
for i := j; i < len(f.Blocks); i++ {
f.Blocks[i] = nil
}
f.Blocks = f.Blocks[:j]
}
// SetDebugMode sets the debug mode for package pkg. If true, all its
// functions will include full debug info. This greatly increases the
// size of the instruction stream, and causes Functions to depend upon
// the ASTs, potentially keeping them live in memory for longer.
func (pkg *Package) SetDebugMode(debug bool) {
// TODO(adonovan): do we want ast.File granularity?
pkg.debug = debug
}
// debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool {
return f.Pkg != nil && f.Pkg.debug
}
// lookup returns the address of the named variable identified by obj
// that is local to function f or one of its enclosing functions.
// If escaping, the reference comes from a potentially escaping pointer
// expression and the referent must be heap-allocated.
// We assume the referent is a *Alloc or *Phi.
// (The only Phis at this stage are those created directly by go1.22 "for" loops.)
func (f *Function) lookup(obj *types.Var, escaping bool) Value {
if v, ok := f.vars[obj]; ok {
if escaping {
switch v := v.(type) {
case *Alloc:
v.Heap = true
case *Phi:
for _, edge := range v.Edges {
if alloc, ok := edge.(*Alloc); ok {
alloc.Heap = true
}
}
}
}
return v // function-local var (address)
}
// Definition must be in an enclosing function;
// plumb it through intervening closures.
if f.parent == nil {
panic("no ir.Value for " + obj.String())
}
outer := f.parent.lookup(obj, true) // escaping
v := &FreeVar{
name: obj.Name(),
typ: outer.Type(),
outer: outer,
parent: f,
}
f.vars[obj] = v
f.FreeVars = append(f.FreeVars, v)
return v
}
// emit emits the specified instruction to function f.
func (f *Function) emit(instr Instruction, source ast.Node) Value {
return f.currentBlock.emit(instr, source)
}
// RelString returns the full name of this function, qualified by
// package name, receiver type, etc.
//
// The specific formatting rules are not guaranteed and may change.
//
// Examples:
//
// "math.IsNaN" // a package-level function
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
// "main.main$1" // an anonymous function in main
// "main.init#1" // a declared init function
// "main.init" // the synthesized package initializer
//
// When these functions are referred to from within the same package
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
// For example: "IsNaN", "(*Buffer).Bytes", etc.
//
// All non-synthetic functions have distinct package-qualified names.
// (But two methods may have the same name "(T).f" if one is a synthetic
// wrapper promoting a non-exported method "f" from another package; in
// that case, the strings are equal but the identifiers "f" are distinct.)
func (f *Function) RelString(from *types.Package) string {
// Anonymous?
if f.parent != nil {
// An anonymous function's Name() looks like "parentName$1",
// but its String() should include the type/package/etc.
parent := f.parent.RelString(from)
for i, anon := range f.parent.AnonFuncs {
if anon == f {
return fmt.Sprintf("%s$%d", parent, 1+i)
}
}
return f.name // should never happen
}
// Method (declared or wrapper)?
if recv := f.Signature.Recv(); recv != nil {
return f.relMethod(from, recv.Type())
}
// Thunk?
if f.method != nil {
return f.relMethod(from, f.method.Recv())
}
// Bound?
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
return f.relMethod(from, f.FreeVars[0].Type())
}
// Package-level function?
// Prefix with package name for cross-package references only.
if p := f.pkg(); p != nil && p != from {
return fmt.Sprintf("%s.%s", p.Path(), f.name)
}
// Unknown.
return f.name
}
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
}
// writeSignature writes to buf the signature sig in declaration syntax.
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature) {
buf.WriteString("func ")
if recv := sig.Recv(); recv != nil {
buf.WriteString("(")
if name := recv.Name(); name != "" {
buf.WriteString(name)
buf.WriteString(" ")
}
types.WriteType(buf, recv.Type(), types.RelativeTo(from))
buf.WriteString(") ")
}
buf.WriteString(name)
types.WriteSignature(buf, sig, types.RelativeTo(from))
}
func (f *Function) pkg() *types.Package {
if f.Pkg != nil {
return f.Pkg.Pkg
}
return nil
}
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
func (f *Function) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
WriteFunction(&buf, f)
n, err := w.Write(buf.Bytes())
return int64(n), err
}
// WriteFunction writes to buf a human-readable "disassembly" of f.
func WriteFunction(buf *bytes.Buffer, f *Function) {
fmt.Fprintf(buf, "# Name: %s\n", f.String())
if f.Pkg != nil {
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path())
}
if syn := f.Synthetic; syn != 0 {
fmt.Fprintln(buf, "# Synthetic:", syn)
}
if pos := f.Pos(); pos.IsValid() {
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
}
if f.parent != nil {
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
}
from := f.pkg()
if f.FreeVars != nil {
buf.WriteString("# Free variables:\n")
for i, fv := range f.FreeVars {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
}
}
if len(f.Locals) > 0 {
buf.WriteString("# Locals:\n")
for i, l := range f.Locals {
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
}
}
writeSignature(buf, from, f.Name(), f.Signature)
buf.WriteString(":\n")
if f.Blocks == nil {
buf.WriteString("\t(external)\n")
}
for _, b := range f.Blocks {
if b == nil {
// Corrupt CFG.
fmt.Fprintf(buf, ".nil:\n")
continue
}
fmt.Fprintf(buf, "b%d:", b.Index)
if len(b.Preds) > 0 {
fmt.Fprint(buf, " ←")
for _, pred := range b.Preds {
fmt.Fprintf(buf, " b%d", pred.Index)
}
}
if b.Comment != "" {
fmt.Fprintf(buf, " # %s", b.Comment)
}
buf.WriteByte('\n')
if false { // CFG debugging
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
}
buf2 := &bytes.Buffer{}
for _, instr := range b.Instrs {
buf.WriteString("\t")
switch v := instr.(type) {
case Value:
// Left-align the instruction.
if name := v.Name(); name != "" {
fmt.Fprintf(buf, "%s = ", name)
}
buf.WriteString(instr.String())
case nil:
// Be robust against bad transforms.
buf.WriteString("")
default:
buf.WriteString(instr.String())
}
if instr != nil && instr.Comment() != "" {
buf.WriteString(" # ")
buf.WriteString(instr.Comment())
}
buf.WriteString("\n")
if f.Prog.mode&PrintSource != 0 {
if s := instr.Source(); s != nil {
buf2.Reset()
format.Node(buf2, f.Prog.Fset, s)
for {
line, err := buf2.ReadString('\n')
if len(line) == 0 {
break
}
buf.WriteString("\t\t> ")
buf.WriteString(line)
if line[len(line)-1] != '\n' {
buf.WriteString("\n")
}
if err != nil {
break
}
}
}
}
}
buf.WriteString("\n")
}
}
// newBasicBlock adds to f a new basic block and returns it. It does
// not automatically become the current block for subsequent calls to emit.
// comment is an optional string for more readable debugging output.
func (f *Function) newBasicBlock(comment string) *BasicBlock {
var instrs []Instruction
if len(f.functionBody.scratchInstructions) > 0 {
instrs = f.functionBody.scratchInstructions[0:0:avgInstructionsPerBlock]
f.functionBody.scratchInstructions = f.functionBody.scratchInstructions[avgInstructionsPerBlock:]
} else {
instrs = make([]Instruction, 0, avgInstructionsPerBlock)
}
b := &BasicBlock{
Index: len(f.Blocks),
Comment: comment,
parent: f,
Instrs: instrs,
}
b.Succs = b.succs2[:0]
f.Blocks = append(f.Blocks, b)
return b
}
// NewFunction returns a new synthetic Function instance belonging to
// prog, with its name and signature fields set as specified.
//
// The caller is responsible for initializing the remaining fields of
// the function object, e.g. Pkg, Params, Blocks.
//
// It is practically impossible for clients to construct well-formed
// IR functions/packages/programs directly, so we assume this is the
// job of the Builder alone. NewFunction exists to provide clients a
// little flexibility. For example, analysis tools may wish to
// construct fake Functions for the root of the callgraph, a fake
// "reflect" package, etc.
//
// TODO(adonovan): think harder about the API here.
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance Synthetic) *Function {
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
}
//lint:ignore U1000 we may make use of this for functions loaded from export data
type extentNode [2]token.Pos
func (n extentNode) Pos() token.Pos { return n[0] }
func (n extentNode) End() token.Pos { return n[1] }
func (f *Function) initHTML(name string) {
if name == "" {
return
}
if rel := f.RelString(nil); rel == name {
f.wr = NewHTMLWriter("ir.html", rel, "")
}
}
func killInstruction(instr Instruction) {
ops := instr.Operands(nil)
for _, op := range ops {
if refs := (*op).Referrers(); refs != nil {
*refs = removeInstr(*refs, instr)
}
}
}
// identVar returns the variable defined by id.
func identVar(fn *Function, id *ast.Ident) *types.Var {
return fn.Pkg.info.Defs[id].(*types.Var)
}
// unique returns a unique positive int within the source tree of f.
// The source tree of f includes all of f's ancestors by parent and all
// of the AnonFuncs contained within these.
func unique(f *Function) int64 {
f.uniq++
return f.uniq
}
// exit is a change of control flow going from a range-over-func
// yield function to an ancestor function caused by a break, continue,
// goto, or return statement.
//
// There are 3 types of exits:
// * return from the source function (from ReturnStmt),
// * jump to a block (from break and continue statements [labelled/unlabelled]),
// * go to a label (from goto statements).
//
// As the builder does one pass over the ast, it is unclear whether
// a forward goto statement will leave a range-over-func body.
// The function being exited to is unresolved until the end
// of building the range-over-func body.
type exit struct {
id int64 // unique value for exit within from and to
from *Function // the function the exit starts from
to *Function // the function being exited to (nil if unresolved)
source ast.Node
block *BasicBlock // basic block within to being jumped to.
label *types.Label // forward label being jumped to via goto.
// block == nil && label == nil => return
}
// storeVar emits to function f code to store a value v to a *types.Var x.
func storeVar(f *Function, x *types.Var, v Value, source ast.Node) {
emitStore(f, f.lookup(x, true), v, source)
}
// labelExit creates a new exit to a yield fn to exit the function using a label.
func labelExit(fn *Function, label *types.Label, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: nil,
source: source,
label: label,
}
fn.exits = append(fn.exits, e)
return e
}
// blockExit creates a new exit to a yield fn that jumps to a basic block.
func blockExit(fn *Function, block *BasicBlock, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: block.parent,
source: source,
block: block,
}
fn.exits = append(fn.exits, e)
return e
}
// returnExit creates a new exit to a yield fn that returns to the source function.
func returnExit(fn *Function, source ast.Node) *exit {
e := &exit{
id: unique(fn),
from: fn,
to: fn.sourceFn,
source: source,
}
fn.exits = append(fn.exits, e)
return e
}
================================================
FILE: go/ir/html.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package ir
import (
"bytes"
"fmt"
"go/types"
"html"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
)
func live(f *Function) []bool {
max := 0
var ops []*Value
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if int(instr.ID()) > max {
max = int(instr.ID())
}
}
}
out := make([]bool, max+1)
var q []Node
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable:
out[instr.ID()] = true
q = append(q, instr)
}
}
}
for len(q) > 0 {
v := q[len(q)-1]
q = q[:len(q)-1]
for _, op := range v.Operands(ops) {
if *op == nil {
continue
}
if !out[(*op).ID()] {
out[(*op).ID()] = true
q = append(q, *op)
}
}
}
return out
}
type funcPrinter interface {
startBlock(b *BasicBlock, reachable bool)
endBlock(b *BasicBlock)
value(v Node, live bool)
startDepCycle()
endDepCycle()
named(n string, vals []Value)
}
func namedValues(f *Function) map[types.Object][]Value {
names := map[types.Object][]Value{}
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if instr, ok := instr.(*DebugRef); ok {
if obj := instr.object; obj != nil {
names[obj] = append(names[obj], instr.X)
}
}
}
}
// XXX deduplicate values
return names
}
func fprintFunc(p funcPrinter, f *Function) {
// XXX does our IR form preserve unreachable blocks?
// reachable, live := findlive(f)
l := live(f)
for _, b := range f.Blocks {
// XXX
// p.startBlock(b, reachable[b.Index])
p.startBlock(b, true)
end := max(len(b.Instrs)-1, 0)
for _, v := range b.Instrs[:end] {
if _, ok := v.(*DebugRef); !ok {
p.value(v, l[v.ID()])
}
}
p.endBlock(b)
}
names := namedValues(f)
keys := make([]types.Object, 0, len(names))
for key := range names {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return keys[i].Pos() < keys[j].Pos()
})
for _, key := range keys {
p.named(key.Name(), names[key])
}
}
func opName(v Node) string {
switch v := v.(type) {
case *Call:
if v.Common().IsInvoke() {
return "Invoke"
}
return "Call"
case *Alloc:
if v.Heap {
return "HeapAlloc"
}
return "StackAlloc"
case *Select:
if v.Blocking {
return "SelectBlocking"
}
return "SelectNonBlocking"
default:
return reflect.ValueOf(v).Type().Elem().Name()
}
}
type HTMLWriter struct {
w io.WriteCloser
path string
dot *dotWriter
}
func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter {
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("%v", err)
}
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("%v", err)
}
html := HTMLWriter{w: out, path: filepath.Join(pwd, path)}
html.dot = newDotWriter()
html.start(funcname)
return &html
}
func (w *HTMLWriter) start(name string) {
if w == nil {
return
}
w.WriteString("")
w.WriteString(`
`)
w.WriteString("")
w.WriteString("")
w.WriteString(html.EscapeString(name))
w.WriteString(" ")
w.WriteString(`
help
Click on a value or block to toggle highlighting of that value/block
and its uses. (Values and blocks are highlighted by ID, and IDs of
dead items may be reused, so not all highlights necessarily correspond
to the clicked item.)
Faded out values and blocks are dead code that has not been eliminated.
Values printed in italics have a dependency cycle.
CFG : Dashed edge is for unlikely branches. Blue color is for backward edges.
Edge with a dot means that this edge follows the order in which blocks were laid out.
`)
w.WriteString("")
w.WriteString("")
}
func (w *HTMLWriter) Close() {
if w == nil {
return
}
io.WriteString(w.w, " ")
io.WriteString(w.w, "
")
io.WriteString(w.w, "")
io.WriteString(w.w, "")
w.w.Close()
fmt.Printf("dumped IR to %v\n", w.path)
}
// WriteFunc writes f in a column headed by title.
// phase is used for collapsing columns and should be unique across the table.
func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) {
if w == nil {
return
}
w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot))
}
// WriteColumn writes raw HTML in a column headed by title.
// It is intended for pre- and post-compilation log output.
func (w *HTMLWriter) WriteColumn(phase, title, class, html string) {
if w == nil {
return
}
id := strings.Replace(phase, " ", "-", -1)
// collapsed column
w.Printf("%v
", id, phase)
if class == "" {
w.Printf("", id)
} else {
w.Printf(" ", id, class)
}
w.WriteString("" + title + " ")
w.WriteString(html)
w.WriteString(" ")
}
func (w *HTMLWriter) Printf(msg string, v ...any) {
if _, err := fmt.Fprintf(w.w, msg, v...); err != nil {
log.Fatalf("%v", err)
}
}
func (w *HTMLWriter) WriteString(s string) {
if _, err := io.WriteString(w.w, s); err != nil {
log.Fatalf("%v", err)
}
}
func valueHTML(v Node) string {
if v == nil {
return "<nil>"
}
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
class := fmt.Sprintf("t%d", v.ID())
var label string
switch v := v.(type) {
case *Function:
label = v.RelString(nil)
case *Builtin:
label = v.Name()
default:
label = class
}
return fmt.Sprintf("%s ", class, label)
}
func valueLongHTML(v Node) string {
// TODO: Any intra-value formatting?
// I'm wary of adding too much visual noise,
// but a little bit might be valuable.
// We already have visual noise in the form of punctuation
// maybe we could replace some of that with formatting.
var s strings.Builder
s.WriteString(fmt.Sprintf("", v.ID()))
linenumber := "(?) "
if v.Pos().IsValid() {
line := v.Parent().Prog.Fset.Position(v.Pos()).Line
linenumber = fmt.Sprintf("(%d) ", line, line)
}
s.WriteString(fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v)))
if v, ok := v.(Value); ok {
s.WriteString(" <" + html.EscapeString(v.Type().String()) + ">")
}
switch v := v.(type) {
case *Parameter:
s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.name)))
case *BinOp:
s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())))
case *UnOp:
s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())))
case *Extract:
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Index, name))
case *Field:
st := v.X.Type().Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Field, name))
case *FieldAddr:
st := deref(v.X.Type()).Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
s.WriteString(fmt.Sprintf(" [%d] (%s)", v.Field, name))
case *Recv:
s.WriteString(fmt.Sprintf(" {%t}", v.CommaOk))
case *Call:
if v.Common().IsInvoke() {
s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName())))
}
case *Const:
if v.Value == nil {
s.WriteString(" {<nil>}")
} else {
s.WriteString(fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String())))
}
case *Sigma:
s.WriteString(fmt.Sprintf(" [#%s]", v.From))
}
for _, a := range v.Operands(nil) {
s.WriteString(fmt.Sprintf(" %s", valueHTML(*a)))
}
if v, ok := v.(Instruction); ok {
s.WriteString(fmt.Sprintf(" (%s)", v.Comment()))
}
// OPT(dh): we're calling namedValues many times on the same function.
allNames := namedValues(v.Parent())
var names []string
for name, values := range allNames {
for _, value := range values {
if v == value {
names = append(names, name.Name())
break
}
}
}
if len(names) != 0 {
s.WriteString(" (" + strings.Join(names, ", ") + ")")
}
s.WriteString(" ")
return s.String()
}
func blockHTML(b *BasicBlock) string {
// TODO: Using the value ID as the class ignores the fact
// that value IDs get recycled and that some values
// are transmuted into other values.
s := html.EscapeString(b.String())
return fmt.Sprintf("%s ", s, s)
}
func blockLongHTML(b *BasicBlock) string {
var kind string
var term Instruction
if len(b.Instrs) > 0 {
term = b.Control()
kind = opName(term)
}
// TODO: improve this for HTML?
var s strings.Builder
s.WriteString(fmt.Sprintf("%s ", b.Index, kind))
if term != nil {
ops := term.Operands(nil)
if len(ops) > 0 {
var ss []string
for _, op := range ops {
ss = append(ss, valueHTML(*op))
}
s.WriteString(" " + strings.Join(ss, ", "))
}
}
if len(b.Succs) > 0 {
s.WriteString(" →") // right arrow
for _, c := range b.Succs {
s.WriteString(" " + blockHTML(c))
}
}
return s.String()
}
func funcHTML(f *Function, phase string, dot *dotWriter) string {
buf := new(bytes.Buffer)
if dot != nil {
dot.writeFuncSVG(buf, phase, f)
}
fmt.Fprint(buf, "")
p := htmlFuncPrinter{w: buf}
fprintFunc(p, f)
// fprintFunc(&buf, f) // TODO: HTML, not text, for line breaks, etc.
fmt.Fprint(buf, "")
return buf.String()
}
type htmlFuncPrinter struct {
w io.Writer
}
func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) {
var dead string
if !reachable {
dead = "dead-block"
}
fmt.Fprintf(p.w, "", b, dead)
fmt.Fprintf(p.w, "%s:", blockHTML(b))
if len(b.Preds) > 0 {
io.WriteString(p.w, " ←") // left arrow
for _, pred := range b.Preds {
fmt.Fprintf(p.w, " %s", blockHTML(pred))
}
}
if len(b.Instrs) > 0 {
io.WriteString(p.w, `- `)
}
io.WriteString(p.w, " ")
if len(b.Instrs) > 0 { // start list of values
io.WriteString(p.w, "")
io.WriteString(p.w, "")
}
}
func (p htmlFuncPrinter) endBlock(b *BasicBlock) {
if len(b.Instrs) > 0 { // end list of values
io.WriteString(p.w, " ")
io.WriteString(p.w, " ")
}
io.WriteString(p.w, "")
fmt.Fprint(p.w, blockLongHTML(b))
io.WriteString(p.w, " ")
io.WriteString(p.w, " ")
}
func (p htmlFuncPrinter) value(v Node, live bool) {
var dead string
if !live {
dead = "dead-value"
}
fmt.Fprintf(p.w, "", dead)
fmt.Fprint(p.w, valueLongHTML(v))
io.WriteString(p.w, " ")
}
func (p htmlFuncPrinter) startDepCycle() {
fmt.Fprintln(p.w, "")
}
func (p htmlFuncPrinter) endDepCycle() {
fmt.Fprintln(p.w, " ")
}
func (p htmlFuncPrinter) named(n string, vals []Value) {
fmt.Fprintf(p.w, "name %s: ", n)
for _, val := range vals {
fmt.Fprintf(p.w, "%s ", valueHTML(val))
}
fmt.Fprintf(p.w, " ")
}
type dotWriter struct {
path string
broken bool
}
// newDotWriter returns non-nil value when mask is valid.
// dotWriter will generate SVGs only for the phases specified in the mask.
// mask can contain following patterns and combinations of them:
// * - all of them;
// x-y - x through y, inclusive;
// x,y - x and y, but not the passes between.
func newDotWriter() *dotWriter {
path, err := exec.LookPath("dot")
if err != nil {
fmt.Println(err)
return nil
}
return &dotWriter{path: path}
}
func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) {
if d.broken {
return
}
cmd := exec.Command(d.path, "-Tsvg")
pipe, err := cmd.StdinPipe()
if err != nil {
d.broken = true
fmt.Println(err)
return
}
buf := new(bytes.Buffer)
cmd.Stdout = buf
bufErr := new(bytes.Buffer)
cmd.Stderr = bufErr
err = cmd.Start()
if err != nil {
d.broken = true
fmt.Println(err)
return
}
fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `)
id := strings.Replace(phase, " ", "-", -1)
fmt.Fprintf(pipe, `id="g_graph_%s";`, id)
fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`)
fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`)
for _, b := range f.Blocks {
layout := ""
fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b)
}
indexOf := make([]int, len(f.Blocks))
for i, b := range f.Blocks {
indexOf[b.Index] = i
}
// XXX
/*
ponums := make([]int32, len(f.Blocks))
_ = postorderWithNumbering(f, ponums)
isBackEdge := func(from, to int) bool {
return ponums[from] <= ponums[to]
}
*/
isBackEdge := func(from, to int) bool { return false }
for _, b := range f.Blocks {
for i, s := range b.Succs {
style := "solid"
color := "black"
arrow := "vee"
if isBackEdge(b.Index, s.Index) {
color = "blue"
}
fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow)
}
}
fmt.Fprint(pipe, "}")
pipe.Close()
err = cmd.Wait()
if err != nil {
d.broken = true
fmt.Printf("dot: %v\n%v\n", err, bufErr.String())
return
}
svgID := "svg_graph_" + id
fmt.Fprintf(w, `- +
`, svgID, svgID)
// For now, an awful hack: edit the html as it passes through
// our fingers, finding ' 0 {
fset = initial[0].Fset
}
prog := ir.NewProgram(fset, mode)
if opts != nil {
prog.PrintFunc = opts.PrintFunc
}
isInitial := make(map[*packages.Package]bool, len(initial))
for _, p := range initial {
isInitial[p] = true
}
irmap := make(map[*packages.Package]*ir.Package)
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
var files []*ast.File
if deps || isInitial[p] {
files = p.Syntax
}
irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true)
}
})
var irpkgs []*ir.Package
for _, p := range initial {
irpkgs = append(irpkgs, irmap[p]) // may be nil
}
return prog, irpkgs
}
// CreateProgram returns a new program in IR form, given a program
// loaded from source. An IR package is created for each transitively
// error-free package of lprog.
//
// Code for bodies of functions is not built until Build is called
// on the result.
//
// The mode parameter controls diagnostics and checking during IR construction.
//
// Deprecated: use golang.org/x/tools/go/packages and the Packages
// function instead; see ir.ExampleLoadPackages.
func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program {
prog := ir.NewProgram(lprog.Fset, mode)
for _, info := range lprog.AllPackages {
if info.TransitivelyErrorFree {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
}
return prog
}
// BuildPackage builds an IR program with IR for a single package.
//
// It populates pkg by type-checking the specified file ASTs. All
// dependencies are loaded using the importer specified by tc, which
// typically loads compiler export data; IR code cannot be built for
// those packages. BuildPackage then constructs an ir.Program with all
// dependency packages created, and builds and returns the IR package
// corresponding to pkg.
//
// The caller must have set pkg.Path() to the import path.
//
// The operation fails if there were any type-checking or import errors.
//
// See ../ir/example_test.go for an example.
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.Package, *types.Info, error) {
if fset == nil {
panic("no token.FileSet")
}
if pkg.Path() == "" {
panic("package has no import path")
}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Instances: make(map[*ast.Ident]types.Instance),
FileVersions: make(map[*ast.File]string),
}
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
return nil, nil, err
}
prog := ir.NewProgram(fset, mode)
// Create IR packages for all imports.
// Order is not significant.
created := make(map[*types.Package]bool)
var createAll func(pkgs []*types.Package)
createAll = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !created[p] {
created[p] = true
prog.CreatePackage(p, nil, nil, true)
createAll(p.Imports())
}
}
}
createAll(pkg.Imports())
// Create and build the primary package.
irpkg := prog.CreatePackage(pkg, files, info, false)
irpkg.Build()
return irpkg, info, nil
}
================================================
FILE: go/ir/irutil/load_test.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package irutil_test
import (
"bytes"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"strings"
"testing"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/packages"
)
const hello = `package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
`
func TestBuildPackage(t *testing.T) {
// There is a more substantial test of BuildPackage and the
// IR program it builds in ../ir/builder_test.go.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", hello, parser.SkipObjectResolution)
if err != nil {
t.Fatal(err)
}
pkg := types.NewPackage("hello", "")
irpkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, pkg, []*ast.File{f}, 0)
if err != nil {
t.Fatal(err)
}
if pkg.Name() != "main" {
t.Errorf("pkg.Name() = %s, want main", pkg.Name())
}
if irpkg.Func("main") == nil {
irpkg.WriteTo(os.Stderr)
t.Errorf("irpkg has no main function")
}
}
func TestPackages(t *testing.T) {
cfg := &packages.Config{Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo}
initial, err := packages.Load(cfg, "bytes")
if err != nil {
t.Fatal(err)
}
if packages.PrintErrors(initial) > 0 {
t.Fatal("there were errors")
}
prog, pkgs := irutil.Packages(initial, 0, nil)
bytesNewBuffer := pkgs[0].Func("NewBuffer")
bytesNewBuffer.Pkg.Build()
// We'll dump the IR of bytes.NewBuffer because it is small and stable.
out := new(bytes.Buffer)
bytesNewBuffer.WriteTo(out)
// For determinism, sanitize the location.
location := prog.Fset.Position(bytesNewBuffer.Pos()).String()
got := strings.Replace(out.String(), location, "$GOROOT/src/bytes/buffer.go:1", -1)
want := `
# Name: bytes.NewBuffer
# Package: bytes
# Location: $GOROOT/src/bytes/buffer.go:1
func NewBuffer(buf []byte) *Buffer:
b0: # entry
t1 = Const {0}
t2 = Const {0}
t3 = Parameter <[]byte> {buf}
t4 = HeapAlloc <*Buffer> # complit
t5 = CompositeValue [100] t3 t1 t2
Store {bytes.Buffer} t4 t5
Jump → b1
b1: ← b0 # exit
Return t4
`[1:]
if got != want {
t.Errorf("bytes.NewBuffer IR = <<%s>>, want <<%s>>", got, want)
}
}
func TestBuildPackage_MissingImport(t *testing.T) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, parser.SkipObjectResolution)
if err != nil {
t.Fatal(err)
}
pkg := types.NewPackage("bad", "")
irpkg, _, err := irutil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0)
if err == nil || irpkg != nil {
t.Fatal("BuildPackage succeeded unexpectedly")
}
}
func TestIssue28106(t *testing.T) {
// In go1.10, go/packages loads all packages from source, not
// export data, but does not type check function bodies of
// imported packages. This test ensures that we do not attempt
// to run the IR builder on functions without type information.
cfg := &packages.Config{Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, "runtime")
if err != nil {
t.Fatal(err)
}
prog, _ := irutil.Packages(pkgs, 0, nil)
prog.Build() // no crash
}
================================================
FILE: go/ir/irutil/loops.go
================================================
package irutil
import "honnef.co/go/tools/go/ir"
type Loop struct{ *ir.BlockSet }
func FindLoops(fn *ir.Function) []Loop {
if fn.Blocks == nil {
return nil
}
tree := fn.DomPreorder()
var sets []Loop
for _, h := range tree {
for _, n := range h.Preds {
if !h.Dominates(n) {
continue
}
// n is a back-edge to h
// h is the loop header
if n == h {
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
set.Add(n)
sets = append(sets, set)
continue
}
set := Loop{ir.NewBlockSet(len(fn.Blocks))}
set.Add(h)
set.Add(n)
for _, b := range allPredsBut(n, h, nil) {
set.Add(b)
}
sets = append(sets, set)
}
}
return sets
}
func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock {
outer:
for _, pred := range b.Preds {
if pred == but {
continue
}
for _, p := range list {
// TODO improve big-o complexity of this function
if pred == p {
continue outer
}
}
list = append(list, pred)
list = allPredsBut(pred, but, list)
}
return list
}
================================================
FILE: go/ir/irutil/stub.go
================================================
package irutil
import (
"honnef.co/go/tools/go/ir"
)
// IsStub reports whether a function is a stub. A function is
// considered a stub if it has no instructions or if all it does is
// return a constant value.
func IsStub(fn *ir.Function) bool {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.Const:
// const naturally has no side-effects
case *ir.Panic:
// panic is a stub if it only uses constants
case *ir.Return:
// return is a stub if it only uses constants
case *ir.DebugRef:
case *ir.Jump:
// if there are no disallowed instructions, then we're
// only jumping to the exit block (or possibly
// somewhere else that's stubby?)
default:
// all other instructions are assumed to do actual work
return false
}
}
}
return true
}
================================================
FILE: go/ir/irutil/switch.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package irutil
// This file implements discovery of switch and type-switch constructs
// from low-level control flow.
//
// Many techniques exist for compiling a high-level switch with
// constant cases to efficient machine code. The optimal choice will
// depend on the data type, the specific case values, the code in the
// body of each case, and the hardware.
// Some examples:
// - a lookup table (for a switch that maps constants to constants)
// - a computed goto
// - a binary tree
// - a perfect hash
// - a two-level switch (to partition constant strings by their first byte).
import (
"bytes"
"fmt"
"go/token"
"go/types"
"honnef.co/go/tools/go/ir"
)
// A ConstCase represents a single constant comparison.
// It is part of a Switch.
type ConstCase struct {
Block *ir.BasicBlock // block performing the comparison
Body *ir.BasicBlock // body of the case
Value *ir.Const // case comparand
}
// A TypeCase represents a single type assertion.
// It is part of a Switch.
type TypeCase struct {
Block *ir.BasicBlock // block performing the type assert
Body *ir.BasicBlock // body of the case
Type types.Type // case type
Binding ir.Value // value bound by this case
}
// A Switch is a logical high-level control flow operation
// (a multiway branch) discovered by analysis of a CFG containing
// only if/else chains. It is not part of the ir.Instruction set.
//
// One of ConstCases and TypeCases has length >= 2;
// the other is nil.
//
// In a value switch, the list of cases may contain duplicate constants.
// A type switch may contain duplicate types, or types assignable
// to an interface type also in the list.
// TODO(adonovan): eliminate such duplicates.
type Switch struct {
Start *ir.BasicBlock // block containing start of if/else chain
X ir.Value // the switch operand
ConstCases []ConstCase // ordered list of constant comparisons
TypeCases []TypeCase // ordered list of type assertions
Default *ir.BasicBlock // successor if all comparisons fail
}
func (sw *Switch) String() string {
// We represent each block by the String() of its
// first Instruction, e.g. "print(42:int)".
var buf bytes.Buffer
if sw.ConstCases != nil {
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
for _, c := range sw.ConstCases {
fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0])
}
} else {
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
for _, c := range sw.TypeCases {
fmt.Fprintf(&buf, "case %s %s: %s\n",
c.Binding.Name(), c.Type, c.Body.Instrs[0])
}
}
if sw.Default != nil {
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
}
fmt.Fprintf(&buf, "}")
return buf.String()
}
// Switches examines the control-flow graph of fn and returns the
// set of inferred value and type switches. A value switch tests an
// ir.Value for equality against two or more compile-time constant
// values. Switches involving link-time constants (addresses) are
// ignored. A type switch type-asserts an ir.Value against two or
// more types.
//
// The switches are returned in dominance order.
//
// The resulting switches do not necessarily correspond to uses of the
// 'switch' keyword in the source: for example, a single source-level
// switch statement with non-constant cases may result in zero, one or
// many Switches, one per plural sequence of constant cases.
// Switches may even be inferred from if/else- or goto-based control flow.
// (In general, the control flow constructs of the source program
// cannot be faithfully reproduced from the IR.)
func Switches(fn *ir.Function) []Switch {
// Traverse the CFG in dominance order, so we don't
// enter an if/else-chain in the middle.
var switches []Switch
seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet
for _, b := range fn.DomPreorder() {
if x, k := isComparisonBlock(b); x != nil {
// Block b starts a switch.
sw := Switch{Start: b, X: x}
valueSwitch(&sw, k, seen)
if len(sw.ConstCases) > 1 {
switches = append(switches, sw)
}
}
if y, x, T := isTypeAssertBlock(b); y != nil {
// Block b starts a type switch.
sw := Switch{Start: b, X: x}
typeSwitch(&sw, y, T, seen)
if len(sw.TypeCases) > 1 {
switches = append(switches, sw)
}
}
}
return switches
}
func isSameX(x1 ir.Value, x2 ir.Value) bool {
if x1 == x2 {
return true
}
if x2, ok := x2.(*ir.Sigma); ok {
return isSameX(x1, x2.X)
}
return false
}
func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) {
b := sw.Start
x := sw.X
for isSameX(sw.X, x) {
if seen[b] {
break
}
seen[b] = true
sw.ConstCases = append(sw.ConstCases, ConstCase{
Block: b,
Body: b.Succs[0],
Value: k,
})
b = b.Succs[1]
n := 0
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.If, *ir.BinOp:
n++
case *ir.Sigma, *ir.Phi, *ir.DebugRef:
default:
n += 1000
}
}
if n != 2 {
// Block b contains not just 'if x == k' and σ/ϕ nodes,
// so it may have side effects that
// make it unsafe to elide.
break
}
if len(b.Preds) != 1 {
// Block b has multiple predecessors,
// so it cannot be treated as a case.
break
}
x, k = isComparisonBlock(b)
}
sw.Default = b
}
func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) {
b := sw.Start
x := sw.X
for isSameX(sw.X, x) {
if seen[b] {
break
}
seen[b] = true
sw.TypeCases = append(sw.TypeCases, TypeCase{
Block: b,
Body: b.Succs[0],
Type: T,
Binding: y,
})
b = b.Succs[1]
n := 0
for _, instr := range b.Instrs {
switch instr.(type) {
case *ir.TypeAssert, *ir.Extract, *ir.If:
n++
case *ir.Sigma, *ir.Phi:
default:
n += 1000
}
}
if n != 4 {
// Block b contains not just
// {TypeAssert; Extract #0; Extract #1; If}
// so it may have side effects that
// make it unsafe to elide.
break
}
if len(b.Preds) != 1 {
// Block b has multiple predecessors,
// so it cannot be treated as a case.
break
}
y, x, T = isTypeAssertBlock(b)
}
sw.Default = b
}
// isComparisonBlock returns the operands (v, k) if a block ends with
// a comparison v==k, where k is a compile-time constant.
func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) {
if n := len(b.Instrs); n >= 2 {
if i, ok := b.Instrs[n-1].(*ir.If); ok {
if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
if k, ok := binop.Y.(*ir.Const); ok {
return binop.X, k
}
if k, ok := binop.X.(*ir.Const); ok {
return binop.Y, k
}
}
}
}
return
}
// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
// a type assertion "if y, ok := x.(T); ok {".
func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) {
if n := len(b.Instrs); n >= 4 {
if i, ok := b.Instrs[n-1].(*ir.If); ok {
if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b {
// hack: relies upon instruction ordering.
if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok {
return ext0, ta.X, ta.AssertedType
}
}
}
}
}
return
}
================================================
FILE: go/ir/irutil/switch_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// No testdata on Android.
//go:build !android
package irutil
import (
"bytes"
"fmt"
"go/parser"
"path/filepath"
"strings"
"testing"
"honnef.co/go/tools/go/ir"
"golang.org/x/tools/go/analysis/analysistest"
//lint:ignore SA1019 go/loader is deprecated, but works fine for our tests
"golang.org/x/tools/go/loader"
)
func TestSwitches(t *testing.T) {
conf := loader.Config{ParserMode: parser.ParseComments}
f, err := conf.ParseFile(filepath.Join(analysistest.TestData(), "switches.go"), nil)
if err != nil {
t.Error(err)
return
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
prog := CreateProgram(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
mainPkg.Build()
for _, mem := range mainPkg.Members {
if fn, ok := mem.(*ir.Function); ok {
if fn.Synthetic != 0 {
continue // e.g. init()
}
// Each (multi-line) "switch" comment within
// this function must match the printed form
// of a ConstSwitch.
var wantSwitches []string
for _, c := range f.Comments {
if fn.Source().Pos() <= c.Pos() && c.Pos() < fn.Source().End() {
text := strings.TrimSpace(c.Text())
if strings.HasPrefix(text, "switch ") {
wantSwitches = append(wantSwitches, text)
}
}
}
switches := Switches(fn)
if len(switches) != len(wantSwitches) {
t.Errorf("in %s, found %d switches, want %d", fn, len(switches), len(wantSwitches))
}
for i, sw := range switches {
got := sw.testString()
if i >= len(wantSwitches) {
continue
}
want := wantSwitches[i]
if got != want {
t.Errorf("in %s, found switch %d: got <<%s>>, want <<%s>>", fn, i, got, want)
}
}
}
}
}
func (sw *Switch) testString() string {
// same as the actual String method, but use the second to last
// instruction instead, to skip over all the phi and sigma nodes
// that SSI produces.
var buf bytes.Buffer
if sw.ConstCases != nil {
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
for _, c := range sw.ConstCases {
n := max(len(c.Body.Instrs)-2, 0)
fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[n])
}
} else {
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
for _, c := range sw.TypeCases {
n := max(len(c.Body.Instrs)-2, 0)
fmt.Fprintf(&buf, "case %s %s: %s\n",
c.Binding.Name(), c.Type, c.Body.Instrs[n])
}
}
if sw.Default != nil {
n := max(len(sw.Default.Instrs)-2, 0)
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[n])
}
fmt.Fprintf(&buf, "}")
return buf.String()
}
================================================
FILE: go/ir/irutil/terminates.go
================================================
package irutil
import (
"go/types"
"honnef.co/go/tools/go/ir"
)
// Terminates reports whether fn is supposed to return, that is if it
// has at least one theoretic path that returns from the function.
// Explicit panics do not count as terminating.
func Terminates(fn *ir.Function) bool {
if fn.Blocks == nil {
// assuming that a function terminates is the conservative
// choice
return true
}
for _, block := range fn.Blocks {
if _, ok := block.Control().(*ir.Return); ok {
if len(block.Preds) == 0 {
return true
}
for _, pred := range block.Preds {
switch ctrl := pred.Control().(type) {
case *ir.Panic:
// explicit panics do not count as terminating
case *ir.If:
// Check if we got here by receiving from a closed
// time.Tick channel – this cannot happen at
// runtime and thus doesn't constitute termination
iff := ctrl
if !ok {
return true
}
ex, ok := iff.Cond.(*ir.Extract)
if !ok {
return true
}
if ex.Index != 1 {
return true
}
recv, ok := ex.Tuple.(*ir.Recv)
if !ok {
return true
}
call, ok := recv.Chan.(*ir.Call)
if !ok {
return true
}
fn, ok := call.Common().Value.(*ir.Function)
if !ok {
return true
}
fn2, ok := fn.Object().(*types.Func)
if !ok {
return true
}
if fn2.FullName() != "time.Tick" {
return true
}
default:
// we've reached the exit block
return true
}
}
}
}
return false
}
================================================
FILE: go/ir/irutil/testdata/switches.go
================================================
//go:build ignore
// +build ignore
package main
// This file is the input to TestSwitches in switch_test.go.
// Each multiway conditional with constant or type cases (Switch)
// discovered by Switches is printed, and compared with the
// comments.
//
// The body of each case is printed as the value of its first
// instruction.
// -------- Value switches --------
func four() int { return 4 }
// A non-constant case makes a switch "impure", but its pure
// cases form two separate switches.
func SwitchWithNonConstantCase(x int) {
// switch t12 {
// case t1: Call <()> print t2
// case t3: Call <()> print t5
// case t4: Call <()> print t5
// default: BinOp {==} t30 t31
// }
// switch t36 {
// case t7: Call <()> print t8
// case t9: Call <()> print t10
// default: Call <()> print t11
// }
switch x {
case 1:
print(1)
case 2, 3:
print(23)
case four():
print(3)
case 5:
print(5)
case 6:
print(6)
}
print("done")
}
// Switches may be found even where the source
// program doesn't have a switch statement.
func ImplicitSwitches(x, y int) {
// switch t13 {
// case t1: Call <()> print t4
// case t2: Call <()> print t4
// default: BinOp {<} t28 t3
// }
if x == 1 || 2 == x || x < 5 {
print(12)
}
// switch t25 {
// case t5: Call <()> print t7
// case t6: Call <()> print t7
// default: BinOp {==} t50 t51
// }
if x == 3 || 4 == x || x == y {
print(34)
}
// Not a switch: no consistent variable.
if x == 5 || y == 6 {
print(56)
}
// Not a switch: only one constant comparison.
if x == 7 || x == y {
print(78)
}
}
func IfElseBasedSwitch(x int) {
// switch t6 {
// case t1: Call <()> print t2
// case t3: Call <()> print t4
// default: Call <()> print t5
// }
if x == 1 {
print(1)
} else if x == 2 {
print(2)
} else {
print("else")
}
}
func GotoBasedSwitch(x int) {
// switch t6 {
// case t1: Call <()> print t4
// case t2: Call <()> print t5
// default: Call <()> print t3
// }
if x == 1 {
goto L1
}
if x == 2 {
goto L2
}
print("else")
L1:
print(1)
goto end
L2:
print(2)
end:
}
func SwitchInAForLoop(x, y int) {
// switch t13 {
// case t2: Call <()> print t3
// case t4: Call <()> print t5
// default: BinOp {==} t31 t30
// }
loop:
for {
print("head")
switch x {
case 1:
print(1)
break loop
case 2:
print(2)
break loop
case y:
print(3)
break loop
}
}
}
// This case is a switch in a for-loop, both constructed using goto.
// As before, the default case points back to the block containing the
// switch, but that's ok.
func SwitchInAForLoopUsingGoto(x int) {
// switch t10 {
// case t2: Call <()> print t4
// case t3: Call <()> print t5
// default: BinOp {==} t10 t2
// }
loop:
print("head")
if x == 1 {
goto L1
}
if x == 2 {
goto L2
}
goto loop
L1:
print(1)
goto end
L2:
print(2)
end:
}
func UnstructuredSwitchInAForLoop(x int) {
// switch t9 {
// case t1: Call <()> print t2
// case t3: BinOp {==} t9 t1
// default: Call <()> print t4
// }
for {
if x == 1 {
print(1)
return
}
if x == 2 {
continue
}
break
}
print("end")
}
func CaseWithMultiplePreds(x int) {
for {
if x == 1 {
print(1)
return
}
loop:
// This block has multiple predecessors,
// so can't be treated as a switch case.
if x == 2 {
goto loop
}
break
}
print("end")
}
func DuplicateConstantsAreNotEliminated(x int) {
// switch t7 {
// case t1: Call <()> print t2
// case t3: Call <()> print t4
// case t5: Call <()> print t6
// default: Return
// }
if x == 1 {
print(1)
} else if x == 1 { // duplicate => unreachable
print("1a")
} else if x == 2 {
print(2)
}
}
// Interface values (created by comparisons) are not constants,
// so ConstSwitch.X is never of interface type.
func MakeInterfaceIsNotAConstant(x interface{}) {
if x == "foo" {
print("foo")
} else if x == 1 {
print(1)
}
}
func ZeroInitializedVarsAreConstants(x int) {
// switch t6 {
// case t5: Call <()> print t1
// case t2: Call <()> print t3
// default: Call <()> print t4
// }
var zero int // SSA construction replaces zero with 0
if x == zero {
print(1)
} else if x == 2 {
print(2)
}
print("end")
}
// -------- Type switches --------
// NB, potentially fragile reliance on register number.
func AdHocTypeSwitch(x interface{}) {
// switch t2.(type) {
// case t4 int: Call <()> println t8
// case t13 string: Call <()> println t16
// default: Call <()> print t1
// }
if i, ok := x.(int); ok {
println(i)
} else if s, ok := x.(string); ok {
println(s)
} else {
print("default")
}
}
================================================
FILE: go/ir/irutil/util.go
================================================
package irutil
import (
"go/types"
"slices"
"strings"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
)
func Reachable(from, to *ir.BasicBlock) bool {
if from == to {
return true
}
if from.Dominates(to) {
return true
}
found := false
Walk(from, func(b *ir.BasicBlock) bool {
if b == to {
found = true
return false
}
return true
})
return found
}
func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) {
seen := map[*ir.BasicBlock]bool{}
wl := []*ir.BasicBlock{b}
for len(wl) > 0 {
b := wl[len(wl)-1]
wl = wl[:len(wl)-1]
if seen[b] {
continue
}
seen[b] = true
if !fn(b) {
continue
}
wl = append(wl, b.Succs...)
}
}
func Vararg(x *ir.Slice) ([]ir.Value, bool) {
var out []ir.Value
alloc, ok := ir.Unwrap(x.X).(*ir.Alloc)
if !ok {
return nil, false
}
var checkAlloc func(alloc ir.Value) bool
checkAlloc = func(alloc ir.Value) bool {
for _, ref := range *alloc.Referrers() {
if ref == x {
continue
}
if ref.Block() != x.Block() {
return false
}
switch ref := ref.(type) {
case *ir.IndexAddr:
idx := ref
if len(*idx.Referrers()) != 1 {
return false
}
store, ok := (*idx.Referrers())[0].(*ir.Store)
if !ok {
return false
}
out = append(out, store.Val)
case *ir.Copy:
if !checkAlloc(ref) {
return false
}
default:
return false
}
}
return true
}
if !checkAlloc(alloc) {
return nil, false
}
return out, true
}
func CallName(call *ir.CallCommon) string {
if call.IsInvoke() {
return ""
}
switch v := call.Value.(type) {
case *ir.Function:
fn, ok := v.Object().(*types.Func)
if !ok {
return ""
}
return typeutil.FuncName(fn)
case *ir.Builtin:
return v.Name()
}
return ""
}
func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
func IsCallToAny(call *ir.CallCommon, names ...string) bool {
q := CallName(call)
return slices.Contains(names, q)
}
func FilterDebug(instr []ir.Instruction) []ir.Instruction {
var out []ir.Instruction
for _, ins := range instr {
if _, ok := ins.(*ir.DebugRef); !ok {
out = append(out, ins)
}
}
return out
}
func IsExample(fn *ir.Function) bool {
if !strings.HasPrefix(fn.Name(), "Example") {
return false
}
f := fn.Prog.Fset.File(fn.Pos())
if f == nil {
return false
}
return strings.HasSuffix(f.Name(), "_test.go")
}
// Flatten recursively returns the underlying value of an ir.Sigma or
// ir.Phi node. If all edges in an ir.Phi node are the same (after
// flattening), the flattened edge will get returned. If flattening is
// not possible, nil is returned.
func Flatten(v ir.Value) ir.Value {
failed := false
seen := map[ir.Value]struct{}{}
var out ir.Value
var dfs func(v ir.Value)
dfs = func(v ir.Value) {
if failed {
return
}
if _, ok := seen[v]; ok {
return
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.Sigma:
dfs(v.X)
case *ir.Phi:
for _, e := range v.Edges {
dfs(e)
}
default:
if out == nil {
out = v
} else if out != v {
failed = true
}
}
}
dfs(v)
if failed {
return nil
}
return out
}
================================================
FILE: go/ir/irutil/visit.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package irutil
import "honnef.co/go/tools/go/ir"
// This file defines utilities for visiting the IR of
// a Program.
//
// TODO(adonovan): test coverage.
// AllFunctions finds and returns the set of functions potentially
// needed by program prog, as determined by a simple linker-style
// reachability algorithm starting from the members and method-sets of
// each package. The result may include anonymous functions and
// synthetic wrappers.
//
// Precondition: all packages are built.
func AllFunctions(prog *ir.Program) map[*ir.Function]bool {
visit := visitor{
prog: prog,
seen: make(map[*ir.Function]bool),
}
visit.program()
return visit.seen
}
type visitor struct {
prog *ir.Program
seen map[*ir.Function]bool
}
func (visit *visitor) program() {
for _, pkg := range visit.prog.AllPackages() {
for _, mem := range pkg.Members {
if fn, ok := mem.(*ir.Function); ok {
visit.function(fn)
}
}
}
for _, T := range visit.prog.RuntimeTypes() {
mset := visit.prog.MethodSets.MethodSet(T)
for i, n := 0, mset.Len(); i < n; i++ {
visit.function(visit.prog.MethodValue(mset.At(i)))
}
}
}
func (visit *visitor) function(fn *ir.Function) {
if !visit.seen[fn] {
visit.seen[fn] = true
var buf [10]*ir.Value // avoid alloc in common case
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range instr.Operands(buf[:0]) {
if fn, ok := (*op).(*ir.Function); ok {
visit.function(fn)
}
}
}
}
}
}
// MainPackages returns the subset of the specified packages
// named "main" that define a main function.
// The result may include synthetic "testmain" packages.
func MainPackages(pkgs []*ir.Package) []*ir.Package {
var mains []*ir.Package
for _, pkg := range pkgs {
if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
mains = append(mains, pkg)
}
}
return mains
}
================================================
FILE: go/ir/lift.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines the lifting pass which tries to "lift" Alloc
// cells (new/local variables) into SSA registers, replacing loads
// with the dominating stored value, eliminating loads and stores, and
// inserting φ- and σ-nodes as needed.
// Cited papers and resources:
//
// Ron Cytron et al. 1991. Efficiently computing SSA form...
// https://doi.acm.org/10.1145/115372.115320
//
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
// Software Practice and Experience 2001, 4:1-10.
// https://www.hipersoft.rice.edu/grads/publications/dom14.pdf
//
// Daniel Berlin, llvmdev mailing list, 2012.
// https://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
// (Be sure to expand the whole thread.)
//
// C. Scott Ananian. 1997. The static single information form.
//
// Jeremy Singer. 2006. Static program analysis based on virtual register renaming.
// TODO(adonovan): opt: there are many optimizations worth evaluating, and
// the conventional wisdom for SSA construction is that a simple
// algorithm well engineered often beats those of better asymptotic
// complexity on all but the most egregious inputs.
//
// Danny Berlin suggests that the Cooper et al. algorithm for
// computing the dominance frontier is superior to Cytron et al.
// Furthermore he recommends that rather than computing the DF for the
// whole function then renaming all alloc cells, it may be cheaper to
// compute the DF for each alloc cell separately and throw it away.
//
// Consider exploiting liveness information to avoid creating dead
// φ-nodes which we then immediately remove.
//
// Also see many other "TODO: opt" suggestions in the code.
import (
"encoding/binary"
"fmt"
"os"
"slices"
)
// If true, show diagnostic information at each step of lifting.
// Very verbose.
const debugLifting = false
// domFrontier maps each block to the set of blocks in its dominance
// frontier. The outer slice is conceptually a map keyed by
// Block.Index. The inner slice is conceptually a set, possibly
// containing duplicates.
//
// TODO(adonovan): opt: measure impact of dups; consider a packed bit
// representation, e.g. big.Int, and bitwise parallel operations for
// the union step in the Children loop.
//
// domFrontier's methods mutate the slice's elements but not its
// length, so their receivers needn't be pointers.
type domFrontier BlockMap[[]*BasicBlock]
func (df domFrontier) add(u, v *BasicBlock) {
df[u.Index] = append(df[u.Index], v)
}
// build builds the dominance frontier df for the dominator tree of
// fn, using the algorithm found in A Simple, Fast Dominance
// Algorithm, Figure 5.
//
// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
// by pruning the entire IDF computation, rather than merely pruning
// the DF -> IDF step.
func (df domFrontier) build(fn *Function) {
for _, b := range fn.Blocks {
preds := b.Preds[0:len(b.Preds):len(b.Preds)]
if b == fn.Exit {
for i, v := range fn.fakeExits.values {
if v {
preds = append(preds, fn.Blocks[i])
}
}
}
if len(preds) >= 2 {
for _, p := range preds {
runner := p
for runner != b.dom.idom {
df.add(runner, b)
runner = runner.dom.idom
}
}
}
}
}
func buildDomFrontier(fn *Function) domFrontier {
df := make(domFrontier, len(fn.Blocks))
df.build(fn)
return df
}
type postDomFrontier BlockMap[[]*BasicBlock]
func (rdf postDomFrontier) add(u, v *BasicBlock) {
rdf[u.Index] = append(rdf[u.Index], v)
}
func (rdf postDomFrontier) build(fn *Function) {
for _, b := range fn.Blocks {
succs := b.Succs[0:len(b.Succs):len(b.Succs)]
if fn.fakeExits.Has(b) {
succs = append(succs, fn.Exit)
}
if len(succs) >= 2 {
for _, s := range succs {
runner := s
for runner != b.pdom.idom {
rdf.add(runner, b)
runner = runner.pdom.idom
}
}
}
}
}
func buildPostDomFrontier(fn *Function) postDomFrontier {
rdf := make(postDomFrontier, len(fn.Blocks))
rdf.build(fn)
return rdf
}
func removeInstr(refs []Instruction, instr Instruction) []Instruction {
return removeInstrsIf(refs, func(i Instruction) bool { return i == instr })
}
func removeInstrsIf(refs []Instruction, p func(Instruction) bool) []Instruction {
return slices.DeleteFunc(refs, p)
}
func clearInstrs(instrs []Instruction) {
for i := range instrs {
instrs[i] = nil
}
}
func numberNodesPerBlock(f *Function) {
for _, b := range f.Blocks {
var base ID
for _, instr := range b.Instrs {
if instr == nil {
continue
}
instr.setID(base)
base++
}
}
}
// lift replaces local and new Allocs accessed only with
// load/store by IR registers, inserting φ- and σ-nodes where necessary.
// The result is a program in pruned SSI form.
//
// Preconditions:
// - fn has no dead blocks (blockopt has run).
// - Def/use info (Operands and Referrers) is up-to-date.
// - The dominator tree is up-to-date.
func lift(fn *Function) bool {
// TODO(adonovan): opt: lots of little optimizations may be
// worthwhile here, especially if they cause us to avoid
// buildDomFrontier. For example:
//
// - Alloc never loaded? Eliminate.
// - Alloc never stored? Replace all loads with a zero constant.
// - Alloc stored once? Replace loads with dominating store;
// don't forget that an Alloc is itself an effective store
// of zero.
// - Alloc used only within a single block?
// Use degenerate algorithm avoiding φ-nodes.
// - Consider synergy with scalar replacement of aggregates (SRA).
// e.g. *(&x.f) where x is an Alloc.
// Perhaps we'd get better results if we generated this as x.f
// i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
// Unclear.
//
// But we will start with the simplest correct code.
var df domFrontier
var rdf postDomFrontier
var closure *closure
var newPhis BlockMap[[]newPhi]
var newSigmas BlockMap[[]newSigma]
// During this pass we will replace some BasicBlock.Instrs
// (allocs, loads and stores) with nil, keeping a count in
// BasicBlock.gaps. At the end we will reset Instrs to the
// concatenation of all non-dead newPhis and non-nil Instrs
// for the block, reusing the original array if space permits.
// While we're here, we also eliminate 'rundefers'
// instructions and ssa:deferstack() in functions that contain no
// 'defer' instructions. Eliminate ssa:deferstack() if it does not
// escape.
usesDefer := false
deferstackAlloc, deferstackCall := deferstackPreamble(fn)
eliminateDeferStack := deferstackAlloc != nil && !deferstackAlloc.Heap
// Determine which allocs we can lift and number them densely.
// The renaming phase uses this numbering for compact maps.
numAllocs := 0
instructions := make(BlockMap[liftInstructions], len(fn.Blocks))
for i := range instructions {
instructions[i].insertInstructions = map[Instruction][]Instruction{}
}
// Number nodes, for liftable
numberNodesPerBlock(fn)
for _, b := range fn.Blocks {
b.gaps = 0
b.rundefers = 0
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case *Alloc:
if !liftable(instr, instructions) {
instr.index = -1
continue
}
if numAllocs == 0 {
df = buildDomFrontier(fn)
rdf = buildPostDomFrontier(fn)
if len(fn.Blocks) > 2 {
closure = transitiveClosure(fn)
}
newPhis = make(BlockMap[[]newPhi], len(fn.Blocks))
newSigmas = make(BlockMap[[]newSigma], len(fn.Blocks))
if debugLifting {
title := false
for i, blocks := range df {
if blocks != nil {
if !title {
fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
title = true
}
fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
}
}
}
}
instr.index = numAllocs
numAllocs++
case *Defer:
usesDefer = true
if eliminateDeferStack {
// Clear _DeferStack and remove references to loads
if instr._DeferStack != nil {
if refs := instr._DeferStack.Referrers(); refs != nil {
*refs = removeInstr(*refs, instr)
}
instr._DeferStack = nil
}
}
case *RunDefers:
b.rundefers++
}
}
}
if numAllocs > 0 {
for _, b := range fn.Blocks {
work := instructions[b.Index]
for _, rename := range work.renameAllocs {
for _, instr_ := range b.Instrs[rename.startingAt:] {
replace(instr_, rename.from, rename.to)
}
}
}
for _, b := range fn.Blocks {
work := instructions[b.Index]
if len(work.insertInstructions) != 0 {
newInstrs := make([]Instruction, 0, len(fn.Blocks)+len(work.insertInstructions)*3)
for _, instr := range b.Instrs {
if add, ok := work.insertInstructions[instr]; ok {
newInstrs = append(newInstrs, add...)
}
newInstrs = append(newInstrs, instr)
}
b.Instrs = newInstrs
}
}
// TODO(dh): remove inserted allocs that end up unused after lifting.
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if instr, ok := instr.(*Alloc); ok && instr.index >= 0 {
liftAlloc(closure, df, rdf, instr, newPhis, newSigmas)
}
}
}
// renaming maps an alloc (keyed by index) to its replacement
// value. Initially the renaming contains nil, signifying the
// zero constant of the appropriate type; we construct the
// Const lazily at most once on each path through the domtree.
// TODO(adonovan): opt: cache per-function not per subtree.
renaming := make([]Value, numAllocs)
// Renaming.
rename(fn.Blocks[0], renaming, newPhis, newSigmas)
simplifyPhisAndSigmas(newPhis, newSigmas)
// Eliminate dead φ- and σ-nodes.
markLiveNodes(fn.Blocks, newPhis, newSigmas)
// Eliminate ssa:deferstack() call.
if eliminateDeferStack {
b := deferstackCall.block
for i, instr := range b.Instrs {
if instr == deferstackCall {
b.Instrs[i] = nil
b.gaps++
break
}
}
}
}
// Prepend remaining live φ-nodes to each block and possibly kill rundefers.
for _, b := range fn.Blocks {
var head []Instruction
if numAllocs > 0 {
nps := newPhis[b.Index]
head = make([]Instruction, 0, len(nps))
for _, pred := range b.Preds {
nss := newSigmas[pred.Index]
idx := pred.succIndex(b)
for _, newSigma := range nss {
if sigma := newSigma.sigmas[idx]; sigma != nil && sigma.live {
head = append(head, sigma)
// we didn't populate referrers before, as most
// sigma nodes will be killed
if refs := sigma.X.Referrers(); refs != nil {
*refs = append(*refs, sigma)
}
} else if sigma != nil {
sigma.block = nil
}
}
}
for _, np := range nps {
if np.phi.live {
head = append(head, np.phi)
} else {
for _, edge := range np.phi.Edges {
if refs := edge.Referrers(); refs != nil {
*refs = removeInstr(*refs, np.phi)
}
}
np.phi.block = nil
}
}
}
rundefersToKill := b.rundefers
if usesDefer {
rundefersToKill = 0
}
j := len(head)
if j+b.gaps+rundefersToKill == 0 {
continue // fast path: no new phis or gaps
}
// We could do straight copies instead of element-wise copies
// when both b.gaps and rundefersToKill are zero. However,
// that seems to only be the case ~1% of the time, which
// doesn't seem worth the extra branch.
// Remove dead instructions, add phis and sigmas
ns := len(b.Instrs) + j - b.gaps - rundefersToKill
if ns <= cap(b.Instrs) {
// b.Instrs has enough capacity to store all instructions
// OPT(dh): check cap vs the actually required space; if
// there is a big enough difference, it may be worth
// allocating a new slice, to avoid pinning memory.
dst := b.Instrs[:cap(b.Instrs)]
i := len(dst) - 1
for n := len(b.Instrs) - 1; n >= 0; n-- {
instr := dst[n]
if instr == nil {
continue
}
if !usesDefer {
if _, ok := instr.(*RunDefers); ok {
continue
}
}
dst[i] = instr
i--
}
off := i + 1 - len(head)
// aid GC
clearInstrs(dst[:off])
dst = dst[off:]
copy(dst, head)
b.Instrs = dst
} else {
// not enough space, so allocate a new slice and copy
// over.
dst := make([]Instruction, ns)
copy(dst, head)
for _, instr := range b.Instrs {
if instr == nil {
continue
}
if !usesDefer {
if _, ok := instr.(*RunDefers); ok {
continue
}
}
dst[j] = instr
j++
}
b.Instrs = dst
}
}
// Remove any fn.Locals that were lifted.
j := 0
for _, l := range fn.Locals {
if l.index < 0 {
fn.Locals[j] = l
j++
}
}
// Nil out fn.Locals[j:] to aid GC.
for i := j; i < len(fn.Locals); i++ {
fn.Locals[i] = nil
}
fn.Locals = fn.Locals[:j]
return numAllocs > 0
}
func hasDirectReferrer(instr Instruction) bool {
for _, instr := range *instr.Referrers() {
switch instr.(type) {
case *Phi, *Sigma:
// ignore
default:
return true
}
}
return false
}
func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) {
// Phis and sigmas may become dead due to optimization passes. We may also insert more nodes than strictly
// necessary, e.g. sigma nodes for constants, which will never be used.
// Phi and sigma nodes are considered live if a non-phi, non-sigma
// node uses them. Once we find a node that is live, we mark all
// of its operands as used, too.
for _, npList := range newPhis {
for _, np := range npList {
phi := np.phi
if !phi.live && hasDirectReferrer(phi) {
markLivePhi(phi)
}
}
}
for _, npList := range newSigmas {
for _, np := range npList {
for _, sigma := range np.sigmas {
if sigma != nil && !sigma.live && hasDirectReferrer(sigma) {
markLiveSigma(sigma)
}
}
}
}
// Existing φ-nodes due to && and || operators
// are all considered live (see Go issue 19622).
for _, b := range blocks {
for _, phi := range b.phis() {
markLivePhi(phi.(*Phi))
}
}
}
func markLivePhi(phi *Phi) {
phi.live = true
for _, rand := range phi.Edges {
switch rand := rand.(type) {
case *Phi:
if !rand.live {
markLivePhi(rand)
}
case *Sigma:
if !rand.live {
markLiveSigma(rand)
}
}
}
}
func markLiveSigma(sigma *Sigma) {
sigma.live = true
switch rand := sigma.X.(type) {
case *Phi:
if !rand.live {
markLivePhi(rand)
}
case *Sigma:
if !rand.live {
markLiveSigma(rand)
}
}
}
// simplifyPhisAndSigmas removes duplicate phi and sigma nodes,
// and replaces trivial phis with non-phi alternatives. Phi
// nodes where all edges are identical, or consist of only the phi
// itself and one other value, may be replaced with the value.
func simplifyPhisAndSigmas(newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) {
// temporary numbering of values used in phis so that we can build map keys
var id ID
for _, npList := range newPhis {
for _, np := range npList {
for _, edge := range np.phi.Edges {
edge.setID(id)
id++
}
}
}
// find all phis that are trivial and can be replaced with a
// non-phi value. run until we reach a fixpoint, because replacing
// a phi may make other phis trivial.
for changed := true; changed; {
changed = false
for _, npList := range newPhis {
for _, np := range npList {
if np.phi.live {
// we're reusing 'live' to mean 'dead' in the context of simplifyPhisAndSigmas
continue
}
if r, ok := isUselessPhi(np.phi); ok {
// useless phi, replace its uses with the
// replacement value. the dead phi pass will clean
// up the phi afterwards.
replaceAll(np.phi, r)
np.phi.live = true
changed = true
}
}
}
// Replace duplicate sigma nodes with a single node. These nodes exist when multiple allocs get replaced with the
// same dominating store.
for _, sigmaList := range newSigmas {
primarySigmas := map[struct {
succ int
v Value
}]*Sigma{}
for _, sigmas := range sigmaList {
for succ, sigma := range sigmas.sigmas {
if sigma == nil {
continue
}
if sigma.live {
// we're reusing 'live' to mean 'dead' in the context of simplifyPhisAndSigmas
continue
}
key := struct {
succ int
v Value
}{succ, sigma.X}
if alt, ok := primarySigmas[key]; ok {
replaceAll(sigma, alt)
sigma.live = true
changed = true
} else {
primarySigmas[key] = sigma
}
}
}
}
// Replace duplicate phi nodes with a single node. As far as we know, these duplicate nodes only ever exist
// because of the previous sigma deduplication.
keyb := make([]byte, 0, 4*8)
for _, npList := range newPhis {
primaryPhis := map[string]*Phi{}
for _, np := range npList {
if np.phi.live {
continue
}
if n := len(np.phi.Edges) * 8; cap(keyb) >= n {
keyb = keyb[:n]
} else {
keyb = make([]byte, n, n*2)
}
for i, e := range np.phi.Edges {
binary.LittleEndian.PutUint64(keyb[i*8:i*8+8], uint64(e.ID()))
}
if alt, ok := primaryPhis[string(keyb)]; ok {
replaceAll(np.phi, alt)
np.phi.live = true
changed = true
} else {
primaryPhis[string(keyb)] = np.phi
}
}
}
}
for _, npList := range newPhis {
for _, np := range npList {
np.phi.live = false
for _, edge := range np.phi.Edges {
edge.setID(0)
}
}
}
for _, sigmaList := range newSigmas {
for _, sigmas := range sigmaList {
for _, sigma := range sigmas.sigmas {
if sigma != nil {
sigma.live = false
}
}
}
}
}
type BlockSet struct {
idx int
values []bool
count int
}
func NewBlockSet(size int) *BlockSet {
return &BlockSet{values: make([]bool, size)}
}
func (s *BlockSet) Set(s2 *BlockSet) {
copy(s.values, s2.values)
s.count = 0
for _, v := range s.values {
if v {
s.count++
}
}
}
func (s *BlockSet) Num() int {
return s.count
}
func (s *BlockSet) Has(b *BasicBlock) bool {
if b.Index >= len(s.values) {
return false
}
return s.values[b.Index]
}
// add adds b to the set and returns true if the set changed.
func (s *BlockSet) Add(b *BasicBlock) bool {
if s.values[b.Index] {
return false
}
s.count++
s.values[b.Index] = true
s.idx = b.Index
return true
}
func (s *BlockSet) Clear() {
for j := range s.values {
s.values[j] = false
}
s.count = 0
}
// take removes an arbitrary element from a set s and
// returns its index, or returns -1 if empty.
func (s *BlockSet) Take() int {
// [i, end]
for i := s.idx; i < len(s.values); i++ {
if s.values[i] {
s.values[i] = false
s.idx = i
s.count--
return i
}
}
// [start, i)
for i := 0; i < s.idx; i++ {
if s.values[i] {
s.values[i] = false
s.idx = i
s.count--
return i
}
}
return -1
}
type closure struct {
span []uint32
reachables BlockMap[interval]
}
type interval uint32
const (
flagMask = 1 << 31
numBits = 20
lengthBits = 32 - numBits - 1
lengthMask = (1<>numBits
} else {
// large interval
i++
start = uint32(inv & numMask)
end = uint32(r[i])
}
if idx >= start && idx <= end {
return true
}
}
return false
}
func (c closure) reachable(id int) []interval {
return c.reachables[c.span[id]:c.span[id+1]]
}
func (c closure) walk(current *BasicBlock, b *BasicBlock, visited []bool) {
// TODO(dh): the 'current' argument seems to be unused
// TODO(dh): there's no reason for this to be a method
visited[b.Index] = true
for _, succ := range b.Succs {
if visited[succ.Index] {
continue
}
visited[succ.Index] = true
c.walk(current, succ, visited)
}
}
func transitiveClosure(fn *Function) *closure {
reachable := make(BlockMap[bool], len(fn.Blocks))
c := &closure{}
c.span = make([]uint32, len(fn.Blocks)+1)
addInterval := func(start, end uint32) {
if l := end - start; l <= 1<= desc.firstUnliftable {
continue
}
hasLiftable := false
switch instr := instr.(type) {
case *Store:
if instr.Val != alloc {
desc.hasLiftableOther = true
hasLiftable = true
}
case *Load:
desc.hasLiftableLoad = true
hasLiftable = true
case *DebugRef:
desc.hasLiftableOther = true
}
if hasLiftable {
if int(instr.ID()) > desc.lastLiftable {
desc.lastLiftable = int(instr.ID())
}
}
}
for i := range blocks {
// Update firstUnliftable to be one after lastLiftable. We do this to include the unliftable's preceding
// DebugRefs in the renaming.
if blocks[i].lastLiftable == -1 && !blocks[i].storeInPreds {
// There are no liftable instructions (for this alloc) in this block. Set firstUnliftable to the
// first non-head instruction to avoid inserting the store before phi instructions, which would
// fail validation.
first := -1
instrLoop:
for i, instr := range fn.Blocks[i].Instrs {
switch instr.(type) {
case *Phi, *Sigma:
default:
first = i
break instrLoop
}
}
blocks[i].firstUnliftable = first
} else {
blocks[i].firstUnliftable = blocks[i].lastLiftable + 1
}
}
// If a block is reachable by a (partially) unliftable block, then the entirety of the block is unliftable. In that
// case, stores have to be inserted in the predecessors.
//
// TODO(dh): this isn't always necessary. If the block is reachable by itself, i.e. part of a loop, then if the
// Alloc instruction is itself part of that loop, then there is a subset of instructions in the loop that can be
// lifted. For example:
//
// for {
// x := 42
// println(x)
// escape(&x)
// }
//
// The x that escapes in one iteration of the loop isn't the same x that we read from on the next iteration.
seen := make(BlockMap[bool], len(fn.Blocks))
var dfs func(b *BasicBlock)
dfs = func(b *BasicBlock) {
if seen[b.Index] {
return
}
seen[b.Index] = true
desc := &blocks[b.Index]
desc.hasLiftableLoad = false
desc.hasLiftableOther = false
desc.isUnliftable = true
desc.firstUnliftable = 0
desc.storeInPreds = true
for _, succ := range b.Succs {
dfs(succ)
}
}
for _, b := range fn.Blocks {
if blocks[b.Index].isUnliftable {
for _, succ := range b.Succs {
dfs(succ)
}
}
}
hasLiftableLoad := false
hasLiftableOther := false
hasUnliftable := false
for _, b := range fn.Blocks {
desc := blocks[b.Index]
hasLiftableLoad = hasLiftableLoad || desc.hasLiftableLoad
hasLiftableOther = hasLiftableOther || desc.hasLiftableOther
if desc.isUnliftable {
hasUnliftable = true
}
}
if !hasLiftableLoad && !hasLiftableOther {
// There are no liftable uses
return false
} else if !hasUnliftable {
// The alloc is entirely liftable without splitting
return true
} else if !hasLiftableLoad {
// The alloc is not entirely liftable, and the only liftable uses are stores. While some of those stores could
// get lifted away, it would also lead to an infinite loop when lifting to a fixpoint, because the newly created
// allocs also get stored into repeatable and that's their only liftable uses.
return false
}
// We need to insert stores for the new alloc. If a (partially) unliftable block has no unliftable
// predecessors and the use isn't in a phi node, then the store can be inserted right before the unliftable use.
// Otherwise, stores have to be inserted at the end of all liftable predecessors.
newAlloc := &Alloc{Heap: true}
newAlloc.setBlock(alloc.block)
newAlloc.setType(alloc.typ)
newAlloc.setSource(alloc.source)
newAlloc.index = -1
newAlloc.comment = "split alloc"
{
work := instructions[alloc.block.Index]
work.insertInstructions[alloc] = append(work.insertInstructions[alloc], newAlloc)
}
predHasStore := make(BlockMap[bool], len(fn.Blocks))
for _, b := range fn.Blocks {
desc := &blocks[b.Index]
bWork := &instructions[b.Index]
if desc.isUnliftable {
bWork.renameAllocs = append(bWork.renameAllocs, struct {
from *Alloc
to *Alloc
startingAt int
}{
alloc, newAlloc, int(desc.firstUnliftable),
})
}
if !desc.isUnliftable {
continue
}
propagate := func(in *BasicBlock, before Instruction) {
load := &Load{
X: alloc,
}
store := &Store{
Addr: newAlloc,
Val: load,
}
load.setType(deref(alloc.typ))
load.setBlock(in)
load.comment = "split alloc"
store.setBlock(in)
updateOperandReferrers(load)
updateOperandReferrers(store)
store.comment = "split alloc"
entry := &instructions[in.Index]
entry.insertInstructions[before] = append(entry.insertInstructions[before], load, store)
}
if desc.storeInPreds {
// emit stores at the end of liftable preds
for _, pred := range b.Preds {
if blocks[pred.Index].isUnliftable {
continue
}
if !alloc.block.Dominates(pred) {
// Consider this cfg:
//
// 1
// /|
// / |
// ↙ ↓
// 2--→3
//
// with an Alloc in block 2. It doesn't make sense to insert a store in block 1 for the jump to
// block 3, because 1 can never see the Alloc in the first place.
//
// Ignoring phi nodes, an Alloc always dominates all of its uses, and phi nodes don't matter here,
// because for the incoming edges that do matter, we do emit the stores.
continue
}
if predHasStore[pred.Index] {
// Don't generate redundant propagations. Not only is it unnecessary, it can lead to infinite loops
// when trying to lift to a fix point, because redundant stores are liftable.
continue
}
predHasStore[pred.Index] = true
before := pred.Instrs[len(pred.Instrs)-1]
propagate(pred, before)
}
} else {
// emit store before the first unliftable use
before := b.Instrs[desc.firstUnliftable]
propagate(b, before)
}
}
return true
}
// liftAlloc lifts alloc into registers and populates newPhis and newSigmas with all the φ- and σ-nodes it may require.
func liftAlloc(closure *closure, df domFrontier, rdf postDomFrontier, alloc *Alloc, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) {
fn := alloc.Parent()
defblocks := fn.blockset(0)
useblocks := fn.blockset(1)
Aphi := fn.blockset(2)
Asigma := fn.blockset(3)
W := fn.blockset(4)
// Compute defblocks, the set of blocks containing a
// definition of the alloc cell.
for _, instr := range *alloc.Referrers() {
switch instr := instr.(type) {
case *Store:
defblocks.Add(instr.Block())
case *Load:
useblocks.Add(instr.Block())
for _, ref := range *instr.Referrers() {
useblocks.Add(ref.Block())
}
}
}
// The Alloc itself counts as a (zero) definition of the cell.
defblocks.Add(alloc.Block())
if debugLifting {
fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
}
// Φ-insertion.
//
// What follows is the body of the main loop of the insert-φ
// function described by Cytron et al, but instead of using
// counter tricks, we just reset the 'hasAlready' and 'work'
// sets each iteration. These are bitmaps so it's pretty cheap.
// Initialize W and work to defblocks.
for change := true; change; {
change = false
{
// Traverse iterated dominance frontier, inserting φ-nodes.
W.Set(defblocks)
for i := W.Take(); i != -1; i = W.Take() {
n := fn.Blocks[i]
for _, y := range df[n.Index] {
if Aphi.Add(y) {
if len(*alloc.Referrers()) == 0 {
continue
}
live := false
if closure == nil {
live = true
} else {
for _, ref := range *alloc.Referrers() {
if _, ok := ref.(*Load); ok {
if closure.has(y, ref.Block()) {
live = true
break
}
}
}
}
if !live {
continue
}
// Create φ-node.
// It will be prepended to v.Instrs later, if needed.
if len(y.Preds) == 0 {
// The exit block may be unreachable if the function doesn't
// return, e.g. due to an infinite loop. In that case we
// should not replace loads in the exit block with ϕ node that
// have no edges. Such loads exist when the function has named
// return parameters, as the exit block loads them to turn
// them into a Return instruction. By not replacing the loads
// with ϕ nodes, they will later be replaced by zero
// constants. This is arguably more correct, and more
// importantly, it doesn't break code that assumes that phis
// have at least one edge.
//
// For one instance of breakage see
// https://staticcheck.dev/issues/1533
continue
}
phi := &Phi{
Edges: make([]Value, len(y.Preds)),
}
phi.comment = alloc.comment
phi.source = alloc.source
phi.setType(deref(alloc.Type()))
phi.block = y
if debugLifting {
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, y)
}
newPhis[y.Index] = append(newPhis[y.Index], newPhi{phi, alloc})
for _, p := range y.Preds {
useblocks.Add(p)
}
change = true
if defblocks.Add(y) {
W.Add(y)
}
}
}
}
}
{
W.Set(useblocks)
for i := W.Take(); i != -1; i = W.Take() {
n := fn.Blocks[i]
for _, y := range rdf[n.Index] {
if Asigma.Add(y) {
sigmas := make([]*Sigma, 0, len(y.Succs))
anyLive := false
for _, succ := range y.Succs {
live := false
for _, ref := range *alloc.Referrers() {
if closure == nil || closure.has(succ, ref.Block()) {
live = true
anyLive = true
break
}
}
if live {
sigma := &Sigma{
From: y,
X: alloc,
}
sigma.comment = alloc.comment
sigma.source = alloc.source
sigma.setType(deref(alloc.Type()))
sigma.block = succ
sigmas = append(sigmas, sigma)
} else {
sigmas = append(sigmas, nil)
}
}
if anyLive {
newSigmas[y.Index] = append(newSigmas[y.Index], newSigma{alloc, sigmas})
for _, s := range y.Succs {
defblocks.Add(s)
}
change = true
if useblocks.Add(y) {
W.Add(y)
}
}
}
}
}
}
}
}
// replaceAll replaces all intraprocedural uses of x with y,
// updating x.Referrers and y.Referrers.
// Precondition: x.Referrers() != nil, i.e. x must be local to some function.
func replaceAll(x, y Value) {
var rands []*Value
pxrefs := x.Referrers()
pyrefs := y.Referrers()
for _, instr := range *pxrefs {
switch instr := instr.(type) {
case *CompositeValue:
// Special case CompositeValue because it might have very large lists of operands
//
// OPT(dh): this loop is still expensive for large composite values
for i, rand := range instr.Values {
if rand == x {
instr.Values[i] = y
}
}
default:
rands = instr.Operands(rands[:0]) // recycle storage
for _, rand := range rands {
if *rand != nil {
if *rand == x {
*rand = y
}
}
}
}
if pyrefs != nil {
*pyrefs = append(*pyrefs, instr) // dups ok
}
}
*pxrefs = nil // x is now unreferenced
}
func replace(instr Instruction, x, y Value) {
args := instr.Operands(nil)
matched := false
for _, arg := range args {
if *arg == x {
*arg = y
matched = true
}
}
if matched {
yrefs := y.Referrers()
if yrefs != nil {
*yrefs = append(*yrefs, instr)
}
xrefs := x.Referrers()
if xrefs != nil {
*xrefs = removeInstr(*xrefs, instr)
}
}
}
// renamed returns the value to which alloc is being renamed,
// constructing it lazily if it's the implicit zero initialization.
func renamed(fn *Function, renaming []Value, alloc *Alloc) Value {
v := renaming[alloc.index]
if v == nil {
v = emitConst(fn, zeroConst(deref(alloc.Type()), alloc.source))
renaming[alloc.index] = v
}
return v
}
func copyValue(v Value, why Instruction, info CopyInfo) *Copy {
c := &Copy{
X: v,
Why: why,
Info: info,
}
if refs := v.Referrers(); refs != nil {
*refs = append(*refs, c)
}
c.setType(v.Type())
c.setSource(v.Source())
return c
}
func splitOnNewInformation(u *BasicBlock, renaming *StackMap) {
renaming.Push()
defer renaming.Pop()
rename := func(v Value, why Instruction, info CopyInfo, i int) {
c := copyValue(v, why, info)
c.setBlock(u)
renaming.Set(v, c)
u.Instrs = append(u.Instrs, nil)
copy(u.Instrs[i+2:], u.Instrs[i+1:])
u.Instrs[i+1] = c
}
replacement := func(v Value) (Value, bool) {
r, ok := renaming.Get(v)
if !ok {
return nil, false
}
for {
rr, ok := renaming.Get(r)
if !ok {
// Store replacement in the map so that future calls to replacement(v) don't have to go through the
// iterative process again.
renaming.Set(v, r)
return r, true
}
r = rr
}
}
var hasInfo func(v Value, info CopyInfo) bool
hasInfo = func(v Value, info CopyInfo) bool {
switch v := v.(type) {
case *Copy:
return (v.Info&info) == info || hasInfo(v.X, info)
case *FieldAddr, *IndexAddr, *TypeAssert, *MakeChan, *MakeMap, *MakeSlice, *Alloc:
return info == CopyInfoNotNil
case Member, *Builtin:
return info == CopyInfoNotNil
case *Sigma:
return hasInfo(v.X, info)
default:
return false
}
}
var args []*Value
for i := 0; i < len(u.Instrs); i++ {
instr := u.Instrs[i]
if instr == nil {
continue
}
args = instr.Operands(args[:0])
for _, arg := range args {
if *arg == nil {
continue
}
if r, ok := replacement(*arg); ok {
*arg = r
replace(instr, *arg, r)
}
}
// TODO write some bits on why we copy values instead of encoding the actual control flow and panics
switch instr := instr.(type) {
case *IndexAddr:
// Note that we rename instr.Index and instr.X even if they're already copies, because unique combinations
// of X and Index may lead to unique information.
// OPT we should rename both variables at once and avoid one memmove
rename(instr.Index, instr, CopyInfoNotNegative, i)
rename(instr.X, instr, CopyInfoNotNil, i)
i += 2 // skip over instructions we just inserted
case *FieldAddr:
if !hasInfo(instr.X, CopyInfoNotNil) {
rename(instr.X, instr, CopyInfoNotNil, i)
i++
}
case *TypeAssert:
// If we've already type asserted instr.X without comma-ok before, then it can only contain a single type,
// and successive type assertions, no matter the type, don't tell us anything new.
if !hasInfo(instr.X, CopyInfoNotNil|CopyInfoSingleConcreteType) {
rename(instr.X, instr, CopyInfoNotNil|CopyInfoSingleConcreteType, i)
i++ // skip over instruction we just inserted
}
case *Load:
if !hasInfo(instr.X, CopyInfoNotNil) {
rename(instr.X, instr, CopyInfoNotNil, i)
i++
}
case *Store:
if !hasInfo(instr.Addr, CopyInfoNotNil) {
rename(instr.Addr, instr, CopyInfoNotNil, i)
i++
}
case *MapUpdate:
if !hasInfo(instr.Map, CopyInfoNotNil) {
rename(instr.Map, instr, CopyInfoNotNil, i)
i++
}
case CallInstruction:
off := 0
if !instr.Common().IsInvoke() && !hasInfo(instr.Common().Value, CopyInfoNotNil) {
rename(instr.Common().Value, instr, CopyInfoNotNil, i)
off++
}
if f, ok := instr.Common().Value.(*Builtin); ok {
switch f.name {
case "close":
arg := instr.Common().Args[0]
if !hasInfo(arg, CopyInfoNotNil|CopyInfoClosed) {
rename(arg, instr, CopyInfoNotNil|CopyInfoClosed, i)
off++
}
}
}
i += off
case *SliceToArrayPointer:
// A slice to array pointer conversion tells us the minimum length of the slice
rename(instr.X, instr, CopyInfoUnspecified, i)
i++
case *SliceToArray:
// A slice to array conversion tells us the minimum length of the slice
rename(instr.X, instr, CopyInfoUnspecified, i)
i++
case *Slice:
// Slicing tells us about some of the bounds
off := 0
if instr.Low == nil && instr.High == nil && instr.Max == nil {
// If all indices are unspecified, then we can only learn something about instr.X if it might've been
// nil.
if !hasInfo(instr.X, CopyInfoNotNil) {
rename(instr.X, instr, CopyInfoUnspecified, i)
off++
}
} else {
rename(instr.X, instr, CopyInfoUnspecified, i)
off++
}
// We copy the indices even if we already know they are not negative, because we can associate numeric
// ranges with them.
if instr.Low != nil {
rename(instr.Low, instr, CopyInfoNotNegative, i)
off++
}
if instr.High != nil {
rename(instr.High, instr, CopyInfoNotNegative, i)
off++
}
if instr.Max != nil {
rename(instr.Max, instr, CopyInfoNotNegative, i)
off++
}
i += off
case *StringLookup:
rename(instr.X, instr, CopyInfoUnspecified, i)
rename(instr.Index, instr, CopyInfoNotNegative, i)
i += 2
case *Recv:
if !hasInfo(instr.Chan, CopyInfoNotNil) {
// Receiving from a nil channel never completes
rename(instr.Chan, instr, CopyInfoNotNil, i)
i++
}
case *Send:
if !hasInfo(instr.Chan, CopyInfoNotNil) {
// Sending to a nil channel never completes. Sending to a closed channel panics, but whether a channel
// is closed isn't local to this function, so we didn't learn anything.
rename(instr.Chan, instr, CopyInfoNotNil, i)
i++
}
}
}
for _, v := range u.dom.children {
splitOnNewInformation(v, renaming)
}
}
// rename implements the Cytron et al-based SSI renaming algorithm, a
// preorder traversal of the dominator tree replacing all loads of
// Alloc cells with the value stored to that cell by the dominating
// store instruction.
//
// renaming is a map from *Alloc (keyed by index number) to its
// dominating stored value; newPhis[x] is the set of new φ-nodes to be
// prepended to block x.
func rename(u *BasicBlock, renaming []Value, newPhis BlockMap[[]newPhi], newSigmas BlockMap[[]newSigma]) {
// Each φ-node becomes the new name for its associated Alloc.
for _, np := range newPhis[u.Index] {
phi := np.phi
alloc := np.alloc
renaming[alloc.index] = phi
}
// Rename loads and stores of allocs.
for i, instr := range u.Instrs {
switch instr := instr.(type) {
case *Alloc:
if instr.index >= 0 { // store of zero to Alloc cell
// Replace dominated loads by the zero value.
renaming[instr.index] = nil
if debugLifting {
fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
}
// Delete the Alloc.
u.Instrs[i] = nil
u.gaps++
}
case *Store:
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
// Replace dominated loads by the stored value.
renaming[alloc.index] = instr.Val
if debugLifting {
fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
instr, instr.Val.Name())
}
if refs := instr.Addr.Referrers(); refs != nil {
*refs = removeInstr(*refs, instr)
}
if refs := instr.Val.Referrers(); refs != nil {
*refs = removeInstr(*refs, instr)
}
// Delete the Store.
u.Instrs[i] = nil
u.gaps++
}
case *Load:
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
// In theory, we wouldn't be able to replace loads directly, because a loaded value could be used in
// different branches, in which case it should be replaced with different sigma nodes. But we can't
// simply defer replacement, either, because then later stores might incorrectly affect this load.
//
// To avoid doing renaming on _all_ values (instead of just loads and stores like we're doing), we make
// sure during code generation that each load is only used in one block. For example, in constant switch
// statements, where the tag is only evaluated once, we store it in a temporary and load it for each
// comparison, so that we have individual loads to replace.
//
// Because we only rename stores and loads, the end result will not contain sigma nodes for all
// constants. Some constants may be used directly, e.g. in comparisons such as 'x == 5'. We may still
// end up inserting dead sigma nodes in branches, but these will never get used in renaming and will be
// cleaned up when we remove dead phis and sigmas.
newval := renamed(u.Parent(), renaming, alloc)
if debugLifting {
fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
instr.Name(), instr, newval)
}
replaceAll(instr, newval)
u.Instrs[i] = nil
u.gaps++
}
case *DebugRef:
if x, ok := instr.X.(*Alloc); ok && x.index >= 0 {
if instr.IsAddr {
instr.X = renamed(u.Parent(), renaming, x)
instr.IsAddr = false
// Add DebugRef to instr.X's referrers.
if refs := instr.X.Referrers(); refs != nil {
*refs = append(*refs, instr)
}
} else {
// A source expression denotes the address
// of an Alloc that was optimized away.
instr.X = nil
// Delete the DebugRef.
u.Instrs[i] = nil
u.gaps++
}
}
}
}
// update all outgoing sigma nodes with the dominating store
for _, sigmas := range newSigmas[u.Index] {
for _, sigma := range sigmas.sigmas {
if sigma == nil {
continue
}
sigma.X = renamed(u.Parent(), renaming, sigmas.alloc)
}
}
// For each φ-node in a CFG successor, rename the edge.
for succi, v := range u.Succs {
phis := newPhis[v.Index]
if len(phis) == 0 {
continue
}
i := v.predIndex(u)
for _, np := range phis {
phi := np.phi
alloc := np.alloc
// if there's a sigma node, use it, else use the dominating value
var newval Value
for _, sigmas := range newSigmas[u.Index] {
if sigmas.alloc == alloc && sigmas.sigmas[succi] != nil {
newval = sigmas.sigmas[succi]
break
}
}
if newval == nil {
newval = renamed(u.Parent(), renaming, alloc)
}
if debugLifting {
fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
phi.Name(), u, v, i, alloc.Name(), newval.Name())
}
phi.Edges[i] = newval
if prefs := newval.Referrers(); prefs != nil {
*prefs = append(*prefs, phi)
}
}
}
// Continue depth-first recursion over domtree, pushing a
// fresh copy of the renaming map for each subtree.
r := make([]Value, len(renaming))
for _, v := range u.dom.children {
copy(r, renaming)
// on entry to a block, the incoming sigma nodes become the new values for their alloc
if idx := u.succIndex(v); idx != -1 {
for _, sigma := range newSigmas[u.Index] {
if sigma.sigmas[idx] != nil {
r[sigma.alloc.index] = sigma.sigmas[idx]
}
}
}
rename(v, r, newPhis, newSigmas)
}
}
func simplifyConstantCompositeValues(fn *Function) bool {
changed := false
for _, b := range fn.Blocks {
n := 0
for _, instr := range b.Instrs {
replaced := false
if cv, ok := instr.(*CompositeValue); ok {
ac := &AggregateConst{}
ac.typ = cv.typ
replaced = true
for _, v := range cv.Values {
if c, ok := v.(Constant); ok {
ac.Values = append(ac.Values, c)
} else {
replaced = false
break
}
}
if replaced {
replaceAll(cv, emitConst(fn, ac))
killInstruction(cv)
}
}
if replaced {
changed = true
} else {
b.Instrs[n] = instr
n++
}
}
clearInstrs(b.Instrs[n:])
b.Instrs = b.Instrs[:n]
}
return changed
}
func updateOperandReferrers(instr Instruction) {
for _, op := range instr.Operands(nil) {
refs := (*op).Referrers()
if refs != nil {
*refs = append(*refs, instr)
}
}
}
// deferstackPreamble returns the *Alloc and ssa:deferstack() call for fn.deferstack.
func deferstackPreamble(fn *Function) (*Alloc, *Call) {
if alloc, _ := fn.vars[fn.deferstack].(*Alloc); alloc != nil {
for _, ref := range *alloc.Referrers() {
if ref, _ := ref.(*Store); ref != nil && ref.Addr == alloc {
if call, _ := ref.Val.(*Call); call != nil {
return alloc, call
}
}
}
}
return nil, nil
}
================================================
FILE: go/ir/lvalue.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"go/ast"
"go/types"
)
// An lvalue represents an assignable location that may appear on the
// left-hand side of an assignment. This is a generalization of a
// pointer to permit updates to elements of maps.
type lvalue interface {
store(fn *Function, v Value, source ast.Node) // stores v into the location
load(fn *Function, source ast.Node) Value // loads the contents of the location
address(fn *Function) Value // address of the location
typ() types.Type // returns the type of the location
}
// An address is an lvalue represented by a true pointer.
type address struct {
addr Value
expr ast.Expr // source syntax of the value (not address) [debug mode]
}
func (a *address) load(fn *Function, source ast.Node) Value {
return emitLoad(fn, a.addr, source)
}
func (a *address) store(fn *Function, v Value, source ast.Node) {
store := emitStore(fn, a.addr, v, source)
if a.expr != nil {
// store.Val is v, converted for assignability.
emitDebugRef(fn, a.expr, store.Val, false)
}
}
func (a *address) address(fn *Function) Value {
if a.expr != nil {
emitDebugRef(fn, a.expr, a.addr, true)
}
return a.addr
}
func (a *address) typ() types.Type {
return deref(a.addr.Type())
}
type compositeElement struct {
cv *CompositeValue
idx int
t types.Type
expr ast.Expr
}
func (ce *compositeElement) load(fn *Function, source ast.Node) Value {
panic("not implemented")
}
func (ce *compositeElement) store(fn *Function, v Value, source ast.Node) {
v = emitConv(fn, v, ce.t, source)
ce.cv.Values[ce.idx] = v
if ce.expr != nil {
// store.Val is v, converted for assignability.
emitDebugRef(fn, ce.expr, v, false)
}
}
func (ce *compositeElement) address(fn *Function) Value {
panic("not implemented")
}
func (ce *compositeElement) typ() types.Type {
return ce.t
}
// An element is an lvalue represented by m[k], the location of an
// element of a map. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load() and store().
type element struct {
m, k Value // map
t types.Type // map element type
}
func (e *element) load(fn *Function, source ast.Node) Value {
l := &MapLookup{
X: e.m,
Index: e.k,
}
l.setType(e.t)
return fn.emit(l, source)
}
func (e *element) store(fn *Function, v Value, source ast.Node) {
up := &MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t, source),
}
fn.emit(up, source)
}
func (e *element) address(fn *Function) Value {
panic("map elements are not addressable")
}
func (e *element) typ() types.Type {
return e.t
}
// A lazyAddress is an lvalue whose address is the result of an instruction.
// These work like an *address except a new address.address() Value
// is created on each load, store and address call.
// A lazyAddress can be used to control when a side effect (nil pointer
// dereference, index out of bounds) of using a location happens.
type lazyAddress struct {
addr func(fn *Function) Value // emit to fn the computation of the address
t types.Type // type of the location
expr ast.Expr // source syntax of the value (not address) [debug mode]
}
func (l *lazyAddress) load(fn *Function, source ast.Node) Value {
load := emitLoad(fn, l.addr(fn), source)
return load
}
func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) {
store := emitStore(fn, l.addr(fn), v, source)
if l.expr != nil {
// store.Val is v, converted for assignability.
emitDebugRef(fn, l.expr, store.Val, false)
}
}
func (l *lazyAddress) address(fn *Function) Value {
addr := l.addr(fn)
if l.expr != nil {
emitDebugRef(fn, l.expr, addr, true)
}
return addr
}
func (l *lazyAddress) typ() types.Type { return l.t }
// A blank is a dummy variable whose name is "_".
// It is not reified: loads are illegal and stores are ignored.
type blank struct{}
func (bl blank) load(fn *Function, source ast.Node) Value {
panic("blank.load is illegal")
}
func (bl blank) store(fn *Function, v Value, source ast.Node) {
s := &BlankStore{
Val: v,
}
fn.emit(s, source)
}
func (bl blank) address(fn *Function) Value {
panic("blank var is not addressable")
}
func (bl blank) typ() types.Type {
// This should be the type of the blank Ident; the typechecker
// doesn't provide this yet, but fortunately, we don't need it
// yet either.
panic("blank.typ is unimplemented")
}
================================================
FILE: go/ir/methods.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines utilities for population of method sets.
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/lint"
)
// MethodValue returns the Function implementing method sel, building
// wrapper methods on demand. It returns nil if sel denotes an
// abstract (interface) method.
//
// Precondition: sel.Kind() == MethodVal.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
func (prog *Program) MethodValue(sel *types.Selection) *Function {
if sel.Kind() != types.MethodVal {
panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel))
}
T := sel.Recv()
if types.IsInterface(T) {
return nil // abstract method
}
if prog.mode&LogSource != 0 {
defer logStack("MethodValue %s %v", T, sel)()
}
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
return prog.addMethod(prog.createMethodSet(T), sel)
}
// LookupMethod returns the implementation of the method of type T
// identified by (pkg, name). It returns nil if the method exists but
// is abstract, and panics if T has no such method.
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
if sel == nil {
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
}
return prog.MethodValue(sel)
}
// methodSet contains the (concrete) methods of a non-interface type.
type methodSet struct {
mapping map[string]*Function // populated lazily
complete bool // mapping contains all methods
}
// Precondition: !isInterface(T).
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) createMethodSet(T types.Type) *methodSet {
mset, ok := prog.methodSets.At(T)
if !ok {
mset = &methodSet{mapping: make(map[string]*Function)}
prog.methodSets.Set(T, mset)
}
return mset
}
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
if sel.Kind() == types.MethodExpr {
panic(sel)
}
id := sel.Obj().Id()
fn := mset.mapping[id]
if fn == nil {
obj := sel.Obj().(*types.Func)
needsPromotion := len(sel.Index()) > 1
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
if needsPromotion || needsIndirection {
fn = makeWrapper(prog, sel)
} else {
fn = prog.declaredFunc(obj)
}
if fn.Signature.Recv() == nil {
panic(fn) // missing receiver
}
mset.mapping[id] = fn
}
return fn
}
// RuntimeTypes returns a new unordered slice containing all
// concrete types in the program for which a complete (non-empty)
// method set is required at run-time.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
func (prog *Program) RuntimeTypes() []types.Type {
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
var res []types.Type
prog.methodSets.Iterate(func(T types.Type, v *methodSet) {
if v.complete {
res = append(res, T)
}
})
return res
}
// declaredFunc returns the concrete function/method denoted by obj.
// Panic ensues if there is none.
func (prog *Program) declaredFunc(obj *types.Func) *Function {
if origin := obj.Origin(); origin != obj {
// Calling method on instantiated type, create a wrapper that calls the generic type's method
base := prog.packageLevelValue(origin)
return makeInstance(prog, base.(*Function), obj.Type().(*types.Signature), nil)
} else {
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Function)
}
}
panic("no concrete method: " + obj.String())
}
// needMethodsOf ensures that runtime type information (including the
// complete method set) is available for the specified type T and all
// its subcomponents.
//
// needMethodsOf must be called for at least every type that is an
// operand of some MakeInterface instruction, and for the type of
// every exported package member.
//
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
//
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
//
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
func (prog *Program) needMethodsOf(T types.Type) {
prog.methodsMu.Lock()
prog.needMethods(T, false)
prog.methodsMu.Unlock()
}
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
// Recursive case: skip => don't create methods for T.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) needMethods(T types.Type, skip bool) {
// Each package maintains its own set of types it has visited.
if prevSkip, ok := prog.runtimeTypes.At(T); ok {
// needMethods(T) was previously called
if !prevSkip || skip {
return // already seen, with same or false 'skip' value
}
}
prog.runtimeTypes.Set(T, skip)
tmset := prog.MethodSets.MethodSet(T)
if !skip && !types.IsInterface(T) && tmset.Len() > 0 {
// Create methods of T.
mset := prog.createMethodSet(T)
if !mset.complete {
mset.complete = true
n := tmset.Len()
for i := range n {
prog.addMethod(mset, tmset.At(i))
}
}
}
// Recursion over signatures of each method.
for method := range tmset.Methods() {
sig := method.Type().(*types.Signature)
prog.needMethods(sig.Params(), false)
prog.needMethods(sig.Results(), false)
}
switch t := T.(type) {
case *types.Basic:
// nop
case *types.Interface, *types.TypeParam:
// nop---handled by recursion over method set.
case *types.Pointer:
prog.needMethods(t.Elem(), false)
case *types.Slice:
prog.needMethods(t.Elem(), false)
case *types.Chan:
prog.needMethods(t.Elem(), false)
case *types.Map:
prog.needMethods(t.Key(), false)
prog.needMethods(t.Elem(), false)
case *types.Signature:
if t.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
}
prog.needMethods(t.Params(), false)
prog.needMethods(t.Results(), false)
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
prog.needMethods(types.NewPointer(t), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
prog.needMethods(t.Underlying(), true)
case *types.Array:
prog.needMethods(t.Elem(), false)
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
prog.needMethods(t.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, t.Len(); i < n; i++ {
prog.needMethods(t.At(i).Type(), false)
}
case *types.Alias:
prog.needMethods(types.Unalias(t), false)
default:
lint.ExhaustiveTypeSwitch(T)
}
}
================================================
FILE: go/ir/mode.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines the BuilderMode type and its command-line flag.
import (
"bytes"
"fmt"
)
// BuilderMode is a bitmask of options for diagnostics and checking.
//
// *BuilderMode satisfies the flag.Value interface. Example:
//
// var mode = ir.BuilderMode(0)
// func init() { flag.Var(&mode, "build", ir.BuilderModeDoc) }
type BuilderMode uint
const (
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
PrintFunctions // Print function IR code to stdout
PrintSource // Print source code when printing function IR
LogSource // Log source locations as IR builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve IR form: don't replace local loads/stores with registers
GlobalDebug // Enable debug info for all packages
SplitAfterNewInformation // Split live range after we learn something new about a value
)
const BuilderModeDoc = `Options controlling the IR builder.
The value is a sequence of zero or more of these symbols:
C perform sanity [C]hecking of the IR form.
D include [D]ebug info for every function.
P print [P]ackage inventory.
F print [F]unction IR code.
A print [A]ST nodes responsible for IR instructions
S log [S]ource locations as IR builder progresses.
N build [N]aive IR form: don't replace local loads/stores with registers.
I Split live range after a value is used as slice or array index
`
func (m BuilderMode) String() string {
var buf bytes.Buffer
if m&GlobalDebug != 0 {
buf.WriteByte('D')
}
if m&PrintPackages != 0 {
buf.WriteByte('P')
}
if m&PrintFunctions != 0 {
buf.WriteByte('F')
}
if m&PrintSource != 0 {
buf.WriteByte('A')
}
if m&LogSource != 0 {
buf.WriteByte('S')
}
if m&SanityCheckFunctions != 0 {
buf.WriteByte('C')
}
if m&NaiveForm != 0 {
buf.WriteByte('N')
}
if m&SplitAfterNewInformation != 0 {
buf.WriteByte('I')
}
return buf.String()
}
// Set parses the flag characters in s and updates *m.
func (m *BuilderMode) Set(s string) error {
var mode BuilderMode
for _, c := range s {
switch c {
case 'D':
mode |= GlobalDebug
case 'P':
mode |= PrintPackages
case 'F':
mode |= PrintFunctions
case 'A':
mode |= PrintSource
case 'S':
mode |= LogSource
case 'C':
mode |= SanityCheckFunctions
case 'N':
mode |= NaiveForm
case 'I':
mode |= SplitAfterNewInformation
default:
return fmt.Errorf("unknown BuilderMode option: %q", c)
}
}
*m = mode
return nil
}
// Get returns m.
func (m BuilderMode) Get() any { return m }
================================================
FILE: go/ir/print.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file implements the String() methods for all Value and
// Instruction types.
import (
"bytes"
"fmt"
"go/types"
"io"
"reflect"
"sort"
"strings"
"honnef.co/go/tools/go/types/typeutil"
)
// relName returns the name of v relative to i.
// In most cases, this is identical to v.Name(), but references to
// Functions (including methods) and Globals use RelString and
// all types are displayed with relType, so that only cross-package
// references are package-qualified.
func relName(v Value, i Instruction) string {
if v == nil {
return ""
}
var from *types.Package
if i != nil {
from = i.Parent().pkg()
}
switch v := v.(type) {
case Member: // *Function or *Global
return v.RelString(from)
}
return v.Name()
}
func relType(t types.Type, from *types.Package) string {
return types.TypeString(t, types.RelativeTo(from))
}
func relTerm(term *types.Term, from *types.Package) string {
s := relType(term.Type(), from)
if term.Tilde() {
return "~" + s
}
return s
}
func relString(m Member, from *types.Package) string {
// NB: not all globals have an Object (e.g. init$guard),
// so use Package().Object not Object.Package().
if pkg := m.Package().Pkg; pkg != nil && pkg != from {
return fmt.Sprintf("%s.%s", pkg.Path(), m.Name())
}
return m.Name()
}
// Value.String()
//
// This method is provided only for debugging.
// It never appears in disassembly, which uses Value.Name().
func (v *Parameter) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name)
}
func (v *FreeVar) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name())
}
func (v *Builtin) String() string {
return fmt.Sprintf("Builtin %s", v.Name())
}
// Instruction.String()
func (v *Alloc) String() string {
from := v.Parent().pkg()
storage := "Stack"
if v.Heap {
storage = "Heap"
}
return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from))
}
func (v *Sigma) String() string {
from := v.Parent().pkg()
s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name())
return s
}
func (v *Phi) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "Phi <%s>", v.Type())
for i, edge := range v.Edges {
b.WriteString(" ")
// Be robust against malformed CFG.
if v.block == nil {
b.WriteString("??")
continue
}
block := -1
if i < len(v.block.Preds) {
block = v.block.Preds[i].Index
}
fmt.Fprintf(&b, "%d:", block)
edgeVal := "" // be robust
if edge != nil {
edgeVal = relName(edge, v)
}
b.WriteString(edgeVal)
}
return b.String()
}
func printCall(v *CallCommon, prefix string, instr Instruction) string {
var b bytes.Buffer
if !v.IsInvoke() {
if value, ok := instr.(Value); ok {
fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr))
} else {
fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr))
}
} else {
if value, ok := instr.(Value); ok {
fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name())
} else {
fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name())
}
}
for _, arg := range v.TypeArgs {
b.WriteString(" ")
b.WriteString(relType(arg, instr.Parent().pkg()))
}
for _, arg := range v.Args {
b.WriteString(" ")
b.WriteString(relName(arg, instr))
}
return b.String()
}
func (c *CallCommon) String() string {
return printCall(c, "", nil)
}
func (v *Call) String() string {
return printCall(&v.Call, "Call", v)
}
func (v *BinOp) String() string {
return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v))
}
func (v *UnOp) String() string {
return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v))
}
func (v *Load) String() string {
return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v))
}
func (v *Copy) String() string {
return fmt.Sprintf("Copy <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v))
}
func printConv(prefix string, v, x Value) string {
from := v.Parent().pkg()
return fmt.Sprintf("%s <%s> %s",
prefix,
relType(v.Type(), from),
relName(x, v.(Instruction)))
}
func (v *ChangeType) String() string { return printConv("ChangeType", v, v.X) }
func (v *Convert) String() string { return printConv("Convert", v, v.X) }
func (v *ChangeInterface) String() string { return printConv("ChangeInterface", v, v.X) }
func (v *SliceToArrayPointer) String() string { return printConv("SliceToArrayPointer", v, v.X) }
func (v *SliceToArray) String() string { return printConv("SliceToArray", v, v.X) }
func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) }
func (v *MakeClosure) String() string {
from := v.Parent().pkg()
var b bytes.Buffer
fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v))
if v.Bindings != nil {
for _, c := range v.Bindings {
b.WriteString(" ")
b.WriteString(relName(c, v))
}
}
return b.String()
}
func (v *MakeSlice) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("MakeSlice <%s> %s %s",
relType(v.Type(), from),
relName(v.Len, v),
relName(v.Cap, v))
}
func (v *Slice) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("Slice <%s> %s %s %s %s",
relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v))
}
func (v *MakeMap) String() string {
res := ""
if v.Reserve != nil {
res = relName(v.Reserve, v)
}
from := v.Parent().pkg()
return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res)
}
func (v *MakeChan) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v))
}
func (v *FieldAddr) String() string {
from := v.Parent().pkg()
// v.X.Type() might be a pointer to a type parameter whose core type is a pointer to a struct
st := deref(typeutil.CoreType(deref(v.X.Type()))).Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
return fmt.Sprintf("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
}
func (v *Field) String() string {
st := typeutil.CoreType(v.X.Type()).Underlying().(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
name = st.Field(v.Field).Name()
}
from := v.Parent().pkg()
return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v))
}
func (v *IndexAddr) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *Index) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *MapLookup) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *StringLookup) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v))
}
func (v *Range) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v))
}
func (v *Next) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v))
}
func (v *TypeAssert) String() string {
from := v.Parent().pkg()
return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v))
}
func (v *Extract) String() string {
from := v.Parent().pkg()
name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name()
return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v))
}
func (s *Jump) String() string {
// Be robust against malformed CFG.
block := -1
if s.block != nil && len(s.block.Succs) == 1 {
block = s.block.Succs[0].Index
}
str := fmt.Sprintf("Jump → b%d", block)
if s.Comment() != "" {
str = fmt.Sprintf("%s # %s", str, s.Comment())
}
return str
}
func (s *Unreachable) String() string {
// Be robust against malformed CFG.
block := -1
if s.block != nil && len(s.block.Succs) == 1 {
block = s.block.Succs[0].Index
}
return fmt.Sprintf("Unreachable → b%d", block)
}
func (s *If) String() string {
// Be robust against malformed CFG.
tblock, fblock := -1, -1
if s.block != nil && len(s.block.Succs) == 2 {
tblock = s.block.Succs[0].Index
fblock = s.block.Succs[1].Index
}
return fmt.Sprintf("If %s → b%d b%d", relName(s.Cond, s), tblock, fblock)
}
func (s *ConstantSwitch) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s))
for _, cond := range s.Conds {
fmt.Fprintf(&b, " %s", relName(cond, s))
}
fmt.Fprint(&b, " →")
for _, succ := range s.block.Succs {
fmt.Fprintf(&b, " b%d", succ.Index)
}
return b.String()
}
func (v *CompositeValue) String() string {
var b bytes.Buffer
from := v.Parent().pkg()
fmt.Fprintf(&b, "CompositeValue <%s>", relType(v.Type(), from))
if v.NumSet >= len(v.Values) {
// All values provided
fmt.Fprint(&b, " [all]")
} else if v.Bitmap.BitLen() == 0 {
// No values provided
fmt.Fprint(&b, " [none]")
} else {
// Some values provided
bits := fmt.Appendf(nil, "%0*b", len(v.Values), &v.Bitmap)
for i := 0; i < len(bits)/2; i++ {
o := len(bits) - 1 - i
bits[i], bits[o] = bits[o], bits[i]
}
fmt.Fprintf(&b, " [%s]", bits)
}
for _, vv := range v.Values {
fmt.Fprintf(&b, " %s", relName(vv, v))
}
return b.String()
}
func (s *TypeSwitch) String() string {
from := s.Parent().pkg()
var b bytes.Buffer
fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s))
for _, cond := range s.Conds {
fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg()))
}
return b.String()
}
func (s *Go) String() string {
return printCall(&s.Call, "Go", s)
}
func (s *Panic) String() string {
// Be robust against malformed CFG.
block := -1
if s.block != nil && len(s.block.Succs) == 1 {
block = s.block.Succs[0].Index
}
return fmt.Sprintf("Panic %s → b%d", relName(s.X, s), block)
}
func (s *Return) String() string {
var b bytes.Buffer
b.WriteString("Return")
for _, r := range s.Results {
b.WriteString(" ")
b.WriteString(relName(r, s))
}
return b.String()
}
func (*RunDefers) String() string {
return "RunDefers"
}
func (s *Send) String() string {
return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s))
}
func (recv *Recv) String() string {
from := recv.Parent().pkg()
return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv))
}
func (s *Defer) String() string {
prefix := "Defer "
if s._DeferStack != nil {
prefix += "[" + relName(s._DeferStack, s) + "] "
}
c := printCall(&s.Call, prefix, s)
return c
}
func (s *Select) String() string {
var b bytes.Buffer
for i, st := range s.States {
if i > 0 {
b.WriteString(", ")
}
if st.Dir == types.RecvOnly {
b.WriteString("<-")
b.WriteString(relName(st.Chan, s))
} else {
b.WriteString(relName(st.Chan, s))
b.WriteString("<-")
b.WriteString(relName(st.Send, s))
}
}
non := ""
if !s.Blocking {
non = "Non"
}
from := s.Parent().pkg()
return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String())
}
func (s *Store) String() string {
return fmt.Sprintf("Store {%s} %s %s",
s.Val.Type(), relName(s.Addr, s), relName(s.Val, s))
}
func (s *BlankStore) String() string {
return fmt.Sprintf("BlankStore %s", relName(s.Val, s))
}
func (s *MapUpdate) String() string {
return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
}
func (s *DebugRef) String() string {
p := s.Parent().Prog.Fset.Position(s.Pos())
var descr any
if s.object != nil {
descr = s.object // e.g. "var x int"
} else {
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
}
var addr string
if s.IsAddr {
addr = "address of "
}
return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name())
}
func (p *Package) String() string {
return "package " + p.Pkg.Path()
}
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
func (p *Package) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
WritePackage(&buf, p)
n, err := w.Write(buf.Bytes())
return int64(n), err
}
// WritePackage writes to buf a human-readable summary of p.
func WritePackage(buf *bytes.Buffer, p *Package) {
fmt.Fprintf(buf, "%s:\n", p)
var names []string
maxname := 0
for name := range p.Members {
if l := len(name); l > maxname {
maxname = l
}
names = append(names, name)
}
from := p.Pkg
sort.Strings(names)
for _, name := range names {
switch mem := p.Members[name].(type) {
case *NamedConst:
fmt.Fprintf(buf, " const %-*s %s = %s\n",
maxname, name, mem.Name(), mem.Value.RelString(from))
case *Function:
fmt.Fprintf(buf, " func %-*s %s\n",
maxname, name, relType(mem.Type(), from))
case *Type:
fmt.Fprintf(buf, " type %-*s %s\n",
maxname, name, relType(mem.Type().Underlying(), from))
for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from)))
}
case *Global:
fmt.Fprintf(buf, " var %-*s %s\n",
maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from))
}
}
fmt.Fprintf(buf, "\n")
}
func (v *MultiConvert) String() string {
from := v.Parent().Pkg.Pkg
var b strings.Builder
b.WriteString(printConv("MultiConvert", v, v.X))
b.WriteString(" [")
for i, s := range v.from.Terms {
for j, d := range v.to.Terms {
if i != 0 || j != 0 {
b.WriteString(" | ")
}
fmt.Fprintf(&b, "%s -> %s", relTerm(s, from), relTerm(d, from))
}
}
b.WriteString("]")
return b.String()
}
================================================
FILE: go/ir/sanity.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// An optional pass for sanity-checking invariants of the IR representation.
// Currently it checks CFG invariants but little at the instruction level.
import (
"bytes"
"fmt"
"go/ast"
"go/types"
"io"
"os"
"slices"
"strings"
"honnef.co/go/tools/go/types/typeutil"
)
type sanity struct {
reporter io.Writer
fn *Function
block *BasicBlock
instrs map[Instruction]struct{}
insane bool
}
// sanityCheck performs integrity checking of the IR representation
// of the function fn and returns true if it was valid. Diagnostics
// are written to reporter if non-nil, os.Stderr otherwise. Some
// diagnostics are only warnings and do not imply a negative result.
//
// Sanity-checking is intended to facilitate the debugging of code
// transformation passes.
func sanityCheck(fn *Function, reporter io.Writer) bool {
if reporter == nil {
reporter = os.Stderr
}
return (&sanity{reporter: reporter}).checkFunction(fn)
}
// mustSanityCheck is like sanityCheck but panics instead of returning
// a negative result.
func mustSanityCheck(fn *Function, reporter io.Writer) {
if !sanityCheck(fn, reporter) {
fn.WriteTo(os.Stderr)
panic("SanityCheck failed")
}
}
func (s *sanity) diagnostic(prefix, format string, args ...any) {
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
if s.block != nil {
fmt.Fprintf(s.reporter, ", block %s", s.block)
}
io.WriteString(s.reporter, ": ")
fmt.Fprintf(s.reporter, format, args...)
io.WriteString(s.reporter, "\n")
}
func (s *sanity) errorf(format string, args ...any) {
s.insane = true
s.diagnostic("Error", format, args...)
}
func (s *sanity) warnf(format string, args ...any) {
s.diagnostic("Warning", format, args...)
}
// findDuplicate returns an arbitrary basic block that appeared more
// than once in blocks, or nil if all were unique.
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
if len(blocks) < 2 {
return nil
}
if blocks[0] == blocks[1] {
return blocks[0]
}
// Slow path:
m := make(map[*BasicBlock]bool)
for _, b := range blocks {
if m[b] {
return b
}
m[b] = true
}
return nil
}
func (s *sanity) checkInstr(idx int, instr Instruction) {
switch instr := instr.(type) {
case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch:
s.errorf("control flow instruction not at end of block")
case *Sigma:
if idx > 0 {
prev := s.block.Instrs[idx-1]
if _, ok := prev.(*Sigma); !ok {
s.errorf("Sigma instruction follows a non-Sigma: %T", prev)
}
}
case *Phi:
if idx == 0 {
// It suffices to apply this check to just the first phi node.
if dup := findDuplicate(s.block.Preds); dup != nil {
s.errorf("phi node in block with duplicate predecessor %s", dup)
}
} else {
prev := s.block.Instrs[idx-1]
switch prev.(type) {
case *Phi, *Sigma:
default:
s.errorf("Phi instruction follows a non-Phi, non-Sigma: %T", prev)
}
}
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
s.errorf("phi node has %d edges but %d predecessors", ne, np)
} else {
for i, e := range instr.Edges {
if e == nil {
s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment(), i, s.block.Preds[i])
}
}
}
case *Alloc:
if !instr.Heap && !slices.Contains(s.fn.Locals, instr) {
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
}
case *BinOp:
case *Call:
case *ChangeInterface:
case *ChangeType:
case *SliceToArrayPointer:
case *SliceToArray:
case *Convert:
tsetInstrX := typeutil.NewTypeSet(instr.X.Type().Underlying())
tsetInstr := typeutil.NewTypeSet(instr.Type().Underlying())
ok1 := tsetInstr.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok })
ok2 := tsetInstrX.Any(func(term *types.Term) bool { _, ok := term.Type().Underlying().(*types.Basic); return ok })
if !ok1 && !ok2 {
s.errorf("convert %s -> %s: at least one type set must contain basic type", instr.X.Type(), instr.Type())
}
case *MultiConvert:
case *Defer:
case *Extract:
case *Field:
case *FieldAddr:
case *Go:
case *Index:
case *IndexAddr:
case *MapLookup:
case *StringLookup:
case *MakeChan:
case *MakeClosure:
numFree := len(instr.Fn.(*Function).FreeVars)
numBind := len(instr.Bindings)
if numFree != numBind {
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
numBind, instr.Fn, numFree)
}
if recv := instr.Type().(*types.Signature).Recv(); recv != nil {
s.errorf("MakeClosure's type includes receiver %s", recv.Type())
}
case *MakeInterface:
case *MakeMap:
case *MakeSlice:
case *MapUpdate:
case *Next:
case *Range:
case *RunDefers:
case *Select:
case *Send:
case *Slice:
case *Store:
case *TypeAssert:
case *UnOp:
case *DebugRef:
case *BlankStore:
case *Load:
case *Parameter:
case *Const:
case *AggregateConst:
case *ArrayConst:
case *GenericConst:
case *Recv:
case *TypeSwitch:
case *CompositeValue:
default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
}
if call, ok := instr.(CallInstruction); ok {
if call.Common().Signature() == nil {
s.errorf("nil signature: %s", call)
}
}
// Check that value-defining instructions have valid types
// and a valid referrer list.
if v, ok := instr.(Value); ok {
t := v.Type()
if t == nil {
s.errorf("no type: %s = %s", v.Name(), v)
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
if _, ok := v.(*Const); !ok {
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
}
}
s.checkReferrerList(v)
}
// Untyped constants are legal as instruction Operands(),
// for example:
// _ = "foo"[0]
// or:
// if wordsize==64 {...}
// All other non-Instruction Values can be found via their
// enclosing Function or Package.
}
func (s *sanity) checkFinalInstr(instr Instruction) {
switch instr := instr.(type) {
case *If:
if nsuccs := len(s.block.Succs); nsuccs != 2 {
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
return
}
if s.block.Succs[0] == s.block.Succs[1] {
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
return
}
case *Jump:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
return
}
case *Return:
if nsuccs := len(s.block.Succs); nsuccs != 0 {
s.errorf("Return-terminated block has %d successors; expected none", nsuccs)
return
}
if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na {
s.errorf("%d-ary return in %d-ary function", na, nf)
}
case *Panic:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Panic-terminated block has %d successors; expected one", nsuccs)
return
}
case *Unreachable:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs)
return
}
case *ConstantSwitch:
default:
s.errorf("non-control flow instruction at end of block")
}
}
func (s *sanity) checkBlock(b *BasicBlock, index int) {
s.block = b
if b.Index != index {
s.errorf("block has incorrect Index %d", b.Index)
}
if b.parent != s.fn {
s.errorf("block has incorrect parent %s", b.parent)
}
// Check all blocks are reachable.
// (The entry block is always implicitly reachable, the exit block may be unreachable.)
if index > 1 && len(b.Preds) == 0 {
s.warnf("unreachable block")
if b.Instrs == nil {
// Since this block is about to be pruned,
// tolerating transient problems in it
// simplifies other optimizations.
return
}
}
// Check predecessor and successor relations are dual,
// and that all blocks in CFG belong to same function.
for _, a := range b.Preds {
if !slices.Contains(a.Succs, b) {
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
}
if a.parent != s.fn {
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
}
}
for _, c := range b.Succs {
if !slices.Contains(c.Preds, b) {
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
}
if c.parent != s.fn {
s.errorf("successor %s belongs to different function %s", c, c.parent)
}
}
// Check each instruction is sane.
n := len(b.Instrs)
if n == 0 {
s.errorf("basic block contains no instructions")
}
var rands [10]*Value // reuse storage
for j, instr := range b.Instrs {
if instr == nil {
s.errorf("nil instruction at index %d", j)
continue
}
if b2 := instr.Block(); b2 == nil {
s.errorf("nil Block() for instruction at index %d", j)
continue
} else if b2 != b {
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
continue
}
if j < n-1 {
s.checkInstr(j, instr)
} else {
s.checkFinalInstr(instr)
}
// Check Instruction.Operands.
operands:
for i, op := range instr.Operands(rands[:0]) {
if op == nil {
s.errorf("nil operand pointer %d of %s", i, instr)
continue
}
val := *op
if val == nil {
continue // a nil operand is ok
}
// Check that "untyped" types only appear on constant operands.
if _, ok := (*op).(*Const); !ok {
if basic, ok := types.Unalias((*op).Type()).(*types.Basic); ok {
if basic.Info()&types.IsUntyped != 0 {
s.errorf("operand #%d of %s is untyped: %s", i, instr, basic)
}
}
}
// Check that Operands that are also Instructions belong to same function.
// TODO(adonovan): also check their block dominates block b.
if val, ok := val.(Instruction); ok {
if val.Block() == nil {
s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val)
} else if val.Parent() != s.fn {
s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent())
}
}
// Check that each function-local operand of
// instr refers back to instr. (NB: quadratic)
switch val := val.(type) {
case *Const, *Global, *Builtin:
continue // not local
case *Function:
if val.parent == nil {
continue // only anon functions are local
}
}
// TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined.
if refs := val.Referrers(); refs != nil {
for _, ref := range *refs {
if ref == instr {
continue operands
}
}
s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val)
} else {
s.errorf("operand %d of %s (%s) has no referrers", i, instr, val)
}
}
}
}
func (s *sanity) checkReferrerList(v Value) {
refs := v.Referrers()
if refs == nil {
s.errorf("%s has missing referrer list", v.Name())
return
}
for i, ref := range *refs {
if _, ok := s.instrs[ref]; !ok {
if val, ok := ref.(Value); ok {
s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val)
} else {
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
}
}
}
}
func (s *sanity) checkFunction(fn *Function) bool {
// TODO(adonovan): check Function invariants:
// - check params match signature
// - check transient fields are nil
// - warn if any fn.Locals do not appear among block instructions.
s.fn = fn
if fn.Prog == nil {
s.errorf("nil Prog")
}
var buf bytes.Buffer
_ = fn.String() // must not crash
_ = fn.RelString(fn.pkg()) // must not crash
WriteFunction(&buf, fn) // must not crash
// All functions have a package, except delegates (which are
// shared across packages, or duplicated as weak symbols in a
// separate-compilation model), and error.Error.
if fn.Pkg == nil {
switch fn.Synthetic {
case SyntheticWrapper, SyntheticBound, SyntheticThunk, SyntheticGeneric:
default:
if !strings.HasSuffix(fn.name, "Error") {
s.errorf("nil Pkg")
}
}
}
if syn, src := fn.Synthetic == 0, fn.source != nil; src != syn {
if _, ok := fn.source.(*ast.RangeStmt); !ok || fn.Synthetic != SyntheticRangeOverFuncYield {
// Only range-over-func yield functions are synthetic and have syntax
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
}
}
for i, l := range fn.Locals {
if l.Parent() != fn {
s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
}
if l.Heap {
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
}
}
// Build the set of valid referrers.
s.instrs = make(map[Instruction]struct{})
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
s.instrs[instr] = struct{}{}
}
}
for i, p := range fn.Params {
if p.Parent() != fn {
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
}
// Check common suffix of Signature and Params match type.
if sig := fn.Signature; sig != nil {
j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params
if j < 0 {
continue
}
if !types.Identical(p.Type(), sig.Params().At(j).Type()) {
s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type())
}
}
s.checkReferrerList(p)
}
for i, fv := range fn.FreeVars {
if fv.Parent() != fn {
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
}
s.checkReferrerList(fv)
}
if fn.Blocks != nil && len(fn.Blocks) == 0 {
// Function _had_ blocks (so it's not external) but
// they were "optimized" away, even the entry block.
s.errorf("Blocks slice is non-nil but empty")
}
for i, b := range fn.Blocks {
if b == nil {
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
continue
}
s.checkBlock(b, i)
}
s.block = nil
for i, anon := range fn.AnonFuncs {
if anon.Parent() != fn {
s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent())
}
}
s.fn = nil
return !s.insane
}
// sanityCheckPackage checks invariants of packages upon creation.
// It does not require that the package is built.
// Unlike sanityCheck (for functions), it just panics at the first error.
func sanityCheckPackage(pkg *Package) {
if pkg.Pkg == nil {
panic(fmt.Sprintf("Package %s has no Object", pkg))
}
_ = pkg.String() // must not crash
for name, mem := range pkg.Members {
if name != mem.Name() {
panic(fmt.Sprintf("%s: %T.Name() = %s, want %s",
pkg.Pkg.Path(), mem, mem.Name(), name))
}
obj := mem.Object()
if obj == nil {
// This check is sound because fields
// {Global,Function}.object have type
// types.Object. (If they were declared as
// *types.{Var,Func}, we'd have a non-empty
// interface containing a nil pointer.)
continue // not all members have typechecker objects
}
if obj.Name() != name {
if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") {
// Ok. The name of a declared init function varies between
// its types.Func ("init") and its ir.Function ("init#%d").
} else {
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
pkg.Pkg.Path(), mem, obj.Name(), name))
}
}
}
}
================================================
FILE: go/ir/source.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines utilities for working with source positions
// or source-level named entities ("objects").
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
// the originating syntax, as specified.
import (
"go/ast"
"go/token"
"go/types"
)
// EnclosingFunction returns the function that contains the syntax
// node denoted by path.
//
// Syntax associated with package-level variable specifications is
// enclosed by the package's init() function.
//
// Returns nil if not found; reasons might include:
// - the node is not enclosed by any function.
// - the node is within an anonymous function (FuncLit) and
// its IR function has not been created yet
// (pkg.Build() has not yet been called).
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
// Start with package-level function...
fn := findEnclosingPackageLevelFunction(pkg, path)
if fn == nil {
return nil // not in any function
}
// ...then walk down the nested anonymous functions.
n := len(path)
outer:
for i := range path {
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
for _, anon := range fn.AnonFuncs {
if anon.Pos() == lit.Type.Func {
fn = anon
continue outer
}
}
// IR function not found:
// - package not yet built, or maybe
// - builder skipped FuncLit in dead block
// (in principle; but currently the Builder
// generates even dead FuncLits).
return nil
}
}
return fn
}
// HasEnclosingFunction returns true if the AST node denoted by path
// is contained within the declaration of some function or
// package-level variable.
//
// Unlike EnclosingFunction, the behaviour of this function does not
// depend on whether IR code for pkg has been built, so it can be
// used to quickly reject check inputs that will cause
// EnclosingFunction to fail, prior to IR building.
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
return findEnclosingPackageLevelFunction(pkg, path) != nil
}
// findEnclosingPackageLevelFunction returns the Function
// corresponding to the package-level function enclosing path.
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
switch decl := path[n-2].(type) {
case *ast.GenDecl:
if decl.Tok == token.VAR && n >= 3 {
// Package-level 'var' initializer.
return pkg.init
}
case *ast.FuncDecl:
// Declared function/method.
fn := findNamedFunc(pkg, decl.Pos())
if fn == nil && decl.Recv == nil && decl.Name.Name == "init" {
// Hack: return non-nil when IR is not yet
// built so that HasEnclosingFunction works.
return pkg.init
}
return fn
}
}
return nil // not in any function
}
// findNamedFunc returns the named function whose FuncDecl.Ident is at
// position pos.
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
for _, fn := range pkg.Functions {
if fn.Pos() == pos {
return fn
}
}
return nil
}
// ValueForExpr returns the IR Value that corresponds to non-constant
// expression e.
//
// It returns nil if no value was found, e.g.
// - the expression is not lexically contained within f;
// - f was not built with debug information; or
// - e is a constant expression. (For efficiency, no debug
// information is stored for constants. Use
// go/types.Info.Types[e].Value instead.)
// - e is a reference to nil or a built-in function.
// - the value was optimised away.
//
// If e is an addressable expression used in an lvalue context,
// value is the address denoted by e, and isAddr is true.
//
// The types of e (or &e, if isAddr) and the result are equal
// (modulo "untyped" bools resulting from comparisons).
//
// (Tip: to find the ir.Value given a source position, use
// astutil.PathEnclosingInterval to locate the ast.Node, then
// EnclosingFunction to locate the Function, then ValueForExpr to find
// the ir.Value.)
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
if f.debugInfo() { // (opt)
e = unparen(e)
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if ref, ok := instr.(*DebugRef); ok {
if ref.Expr == e {
return ref.X, ref.IsAddr
}
}
}
}
}
return
}
// --- Lookup functions for source-level named entities (types.Objects) ---
// Package returns the IR Package corresponding to the specified
// type-checker package object.
// It returns nil if no such IR package has been created.
func (prog *Program) Package(obj *types.Package) *Package {
return prog.packages[obj]
}
// packageLevelValue returns the package-level value corresponding to
// the specified named object, which may be a package-level const
// (*Const), var (*Global) or func (*Function) of some package in
// prog. It returns nil if the object is not found.
func (prog *Program) packageLevelValue(obj types.Object) Value {
if pkg, ok := prog.packages[obj.Pkg()]; ok {
return pkg.values[obj]
}
return nil
}
// FuncValue returns the concrete Function denoted by the source-level
// named function obj, or nil if obj denotes an interface method.
//
// TODO(adonovan): check the invariant that obj.Type() matches the
// result's Signature, both in the params/results and in the receiver.
func (prog *Program) FuncValue(obj *types.Func) *Function {
obj = obj.Origin()
fn, _ := prog.packageLevelValue(obj).(*Function)
return fn
}
// ConstValue returns the IR Value denoted by the source-level named
// constant obj.
func (prog *Program) ConstValue(obj *types.Const) *Const {
// TODO(adonovan): opt: share (don't reallocate)
// Consts for const objects and constant ast.Exprs.
// Universal constant? {true,false,nil}
if obj.Parent() == types.Universe {
return NewConst(obj.Val(), obj.Type(), nil)
}
// Package-level named constant?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Const)
}
return NewConst(obj.Val(), obj.Type(), nil)
}
// VarValue returns the IR Value that corresponds to a specific
// identifier denoting the source-level named variable obj.
//
// VarValue returns nil if a local variable was not found, perhaps
// because its package was not built, the debug information was not
// requested during IR construction, or the value was optimized away.
//
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
// and that ident must resolve to obj.
//
// pkg is the package enclosing the reference. (A reference to a var
// always occurs within a function, so we need to know where to find it.)
//
// If the identifier is a field selector and its base expression is
// non-addressable, then VarValue returns the value of that field.
// For example:
//
// func f() struct {x int}
// f().x // VarValue(x) returns a *Field instruction of type int
//
// All other identifiers denote addressable locations (variables).
// For them, VarValue may return either the variable's address or its
// value, even when the expression is evaluated only for its value; the
// situation is reported by isAddr, the second component of the result.
//
// If !isAddr, the returned value is the one associated with the
// specific identifier. For example,
//
// var x int // VarValue(x) returns Const 0 here
// x = 1 // VarValue(x) returns Const 1 here
//
// It is not specified whether the value or the address is returned in
// any particular case, as it may depend upon optimizations performed
// during IR code generation, such as registerization, constant
// folding, avoidance of materialization of subexpressions, etc.
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
// All references to a var are local to some function, possibly init.
fn := EnclosingFunction(pkg, ref)
if fn == nil {
return // e.g. def of struct field; IR not built?
}
id := ref[0].(*ast.Ident)
// Defining ident of a parameter?
if id.Pos() == obj.Pos() {
for _, param := range fn.Params {
if param.Object() == obj {
return param, false
}
}
}
// Other ident?
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if dr, ok := instr.(*DebugRef); ok {
if dr.Pos() == id.Pos() {
return dr.X, dr.IsAddr
}
}
}
}
// Defining ident of package-level var?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Global), true
}
return // e.g. debug info not requested, or var optimized away
}
================================================
FILE: go/ir/source_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
package ir_test
// This file defines tests of source-level debugging utilities.
import (
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/loader"
)
func TestObjValueLookup(t *testing.T) {
if runtime.GOOS == "android" {
t.Skipf("no testdata directory on %s", runtime.GOOS)
}
conf := loader.Config{ParserMode: parser.ParseComments}
src, err := ioutil.ReadFile(filepath.Join(analysistest.TestData(), "objlookup.go"))
if err != nil {
t.Fatal(err)
}
readFile := func(filename string) ([]byte, error) { return src, nil }
f, err := conf.ParseFile(filepath.Join(analysistest.TestData(), "objlookup.go"), src)
if err != nil {
t.Fatal(err)
}
conf.CreateFromFiles("main", f)
// Maps each var Ident (represented "name:linenum") to the
// kind of ir.Value we expect (represented "Constant", "&Alloc").
expectations := make(map[string]string)
// Each note of the form @ir(x, "BinOp") in testdata/objlookup.go
// specifies an expectation that an object named x declared on the
// same line is associated with an ir.Value of type *ir.BinOp.
notes, err := expect.ExtractGo(conf.Fset, f)
if err != nil {
t.Fatal(err)
}
for _, n := range notes {
if n.Name != "ir" {
t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name)
continue
}
if len(n.Args) != 2 {
t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args))
continue
}
ident, ok := n.Args[0].(expect.Identifier)
if !ok {
t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0])
continue
}
exp, ok := n.Args[1].(string)
if !ok {
t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1])
continue
}
p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident))
if err != nil {
t.Error(err)
continue
}
pos := conf.Fset.Position(p)
key := fmt.Sprintf("%s:%d", ident, pos.Line)
expectations[key] = exp
}
iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/)
mainInfo := iprog.Created[0]
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
var varIds []*ast.Ident
var varObjs []*types.Var
for id, obj := range mainInfo.Defs {
// Check invariants for func and const objects.
switch obj := obj.(type) {
case *types.Func:
checkFuncValue(t, prog, obj)
case *types.Const:
checkConstValue(t, prog, obj)
case *types.Var:
if id.Name == "_" {
continue
}
varIds = append(varIds, id)
varObjs = append(varObjs, obj)
}
}
for id, obj := range mainInfo.Uses {
if obj, ok := obj.(*types.Var); ok {
varIds = append(varIds, id)
varObjs = append(varObjs, obj)
}
}
// Check invariants for var objects.
// The result varies based on the specific Ident.
for i, id := range varIds {
obj := varObjs[i]
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := prog.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
if exp == "" {
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
continue
}
wantAddr := false
if exp[0] == '&' {
wantAddr = true
exp = exp[1:]
}
checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
}
}
func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) {
fn := prog.FuncValue(obj)
// fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
if fn == nil {
if obj.Name() != "interfaceMethod" {
t.Errorf("FuncValue(%s) == nil", obj)
}
return
}
if fnobj := fn.Object(); fnobj != obj {
t.Errorf("FuncValue(%s).Object() == %s; value was %s",
obj, fnobj, fn.Name())
return
}
if !types.Identical(fn.Type(), obj.Type()) {
t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
return
}
}
func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) {
c := prog.ConstValue(obj)
// fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
if c == nil {
t.Errorf("ConstValue(%s) == nil", obj)
return
}
if !types.Identical(c.Type(), obj.Type()) {
t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
return
}
if obj.Name() != "nil" {
if !constant.Compare(c.Value, token.EQL, obj.Val()) {
t.Errorf("ConstValue(%s).Value (%s) != %s",
obj, c.Value, obj.Val())
return
}
}
}
func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
// The prefix of all assertions messages.
prefix := fmt.Sprintf("VarValue(%s @ L%d)",
obj, prog.Fset.Position(ref[0].Pos()).Line)
v, gotAddr := prog.VarValue(obj, pkg, ref)
// Kind is the concrete type of the ir Value.
gotKind := "nil"
if v != nil {
gotKind = fmt.Sprintf("%T", v)[len("*ir."):]
}
// fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
// Check the kinds match.
// "nil" indicates expected failure (e.g. optimized away).
if expKind != gotKind {
t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
}
// Check the types match.
// If wantAddr, the expected type is the object's address.
if v != nil {
expType := obj.Type()
if wantAddr {
expType = types.NewPointer(expType)
if !gotAddr {
t.Errorf("%s: got value, want address", prefix)
}
} else if gotAddr {
t.Errorf("%s: got address, want value", prefix)
}
if !types.Identical(v.Type(), expType) {
t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
}
}
}
// Ensure that, in debug mode, we can determine the ir.Value
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
testValueForExpr(t, filepath.Join(analysistest.TestData(), "valueforexpr.go"))
}
func testValueForExpr(t *testing.T, testfile string) {
if runtime.GOOS == "android" {
t.Skipf("no testdata dir on %s", runtime.GOOS)
}
conf := loader.Config{ParserMode: parser.ParseComments}
f, err := conf.ParseFile(testfile, nil)
if err != nil {
t.Error(err)
return
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
mainInfo := iprog.Created[0]
prog := irutil.CreateProgram(iprog, 0)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
if false {
// debugging
for _, mem := range mainPkg.Members {
if fn, ok := mem.(*ir.Function); ok {
fn.WriteTo(os.Stderr)
}
}
}
var parenExprs []*ast.ParenExpr
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
if e, ok := n.(*ast.ParenExpr); ok {
parenExprs = append(parenExprs, e)
}
}
return true
})
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Fatal(err)
}
for _, n := range notes {
want := n.Name
if want == "nil" {
want = ""
}
position := prog.Fset.Position(n.Pos)
var e ast.Expr
for _, paren := range parenExprs {
if paren.Pos() > n.Pos {
e = paren.X
break
}
}
if e == nil {
t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want)
continue
}
path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos)
if path == nil {
t.Errorf("%s: can't find AST path from root to comment: %s", position, want)
continue
}
fn := ir.EnclosingFunction(mainPkg, path)
if fn == nil {
t.Errorf("%s: can't find enclosing function", position)
continue
}
v, gotAddr := fn.ValueForExpr(e) // (may be nil)
got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.")
if got != want {
t.Errorf("%s: got value %q, want %q", position, got, want)
}
if v != nil {
T := v.Type()
if gotAddr {
T = T.Underlying().(*types.Pointer).Elem() // deref
}
if !types.Identical(T, mainInfo.TypeOf(e)) {
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
}
}
}
}
// findInterval parses input and returns the [start, end) positions of
// the first occurrence of substr in input. f==nil indicates failure;
// an error has already been reported in that case.
func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
f, err := parser.ParseFile(fset, " ", input, parser.SkipObjectResolution)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
i := strings.Index(input, substr)
if i < 0 {
t.Errorf("%q is not a substring of input", substr)
f = nil
return
}
filePos := fset.File(f.Package)
return f, filePos.Pos(i), filePos.Pos(i + len(substr))
}
func TestEnclosingFunction(t *testing.T) {
tests := []struct {
input string // the input file
substr string // first occurrence of this string denotes interval
fn string // name of expected containing function
}{
// We use distinctive numbers as syntactic landmarks.
// Ordinary function:
{`package main
func f() { println(1003) }`,
"100", "main.f"},
// Methods:
{`package main
type T int
func (t T) f() { println(200) }`,
"200", "(main.T).f"},
// Function literal:
{`package main
func f() { println(func() { print(300) }) }`,
"300", "main.f$1"},
// Doubly nested
{`package main
func f() { println(func() { print(func() { print(350) })})}`,
"350", "main.f$1$1"},
// Implicit init for package-level var initializer.
{"package main; var a = 400", "400", "main.init"},
// No code for constants:
{"package main; const a = 500", "500", "(none)"},
// Explicit init()
{"package main; func init() { println(600) }", "600", "main.init#1"},
// Multiple explicit init functions:
{`package main
func init() { println("foo") }
func init() { println(800) }`,
"800", "main.init#2"},
// init() containing FuncLit.
{`package main
func init() { println(func(){print(900)}) }`,
"900", "main.init#1$1"},
}
for _, test := range tests {
conf := loader.Config{Fset: token.NewFileSet()}
f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
if f == nil {
continue
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if !exact {
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
continue
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}
prog := irutil.CreateProgram(iprog, 0)
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
name := "(none)"
fn := ir.EnclosingFunction(pkg, path)
if fn != nil {
name = fn.String()
}
if name != test.fn {
t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
test.substr, test.input, name, test.fn)
continue
}
// While we're here: test HasEnclosingFunction.
if has := ir.HasEnclosingFunction(pkg, path); has != (fn != nil) {
t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
test.substr, test.input, has, fn != nil)
continue
}
}
}
================================================
FILE: go/ir/ssa.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This package defines a high-level intermediate representation for
// Go programs using static single-information (SSI) form.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"math/big"
"sync"
"honnef.co/go/tools/go/types/typeutil"
)
const (
// Replace CompositeValue with only constant values with AggregateConst. Currently disabled because it breaks field
// tracking in U1000.
doSimplifyConstantCompositeValues = false
)
type ID int
// A Program is a partial or complete Go program converted to IR form.
type Program struct {
Fset *token.FileSet // position information for the files of this Program
PrintFunc string // create ir.html for function specified in PrintFunc
imported map[string]*Package // all importable Packages, keyed by import path
packages map[*types.Package]*Package // all loaded Packages, keyed by object
mode BuilderMode // set of mode bits for IR construction
MethodSets typeutil.MethodSetCache // cache of type-checker's method-sets
methodsMu sync.Mutex // guards the following maps:
methodSets typeutil.Map[*methodSet] // maps type to its concrete methodSet
runtimeTypes typeutil.Map[bool] // types for which rtypes are needed
canon typeutil.Map[types.Type] // type canonicalization map
noReturn func(*types.Func) bool
}
// A Package is a single analyzed Go package containing Members for
// all package-level functions, variables, constants and types it
// declares. These may be accessed directly via Members, or via the
// type-specific accessor methods Func, Type, Var and Const.
//
// Members also contains entries for "init" (the synthetic package
// initializer) and "init#%d", the nth declared init function,
// and unspecified other things too.
type Package struct {
Prog *Program // the owning program
Pkg *types.Package // the corresponding go/types.Package
Members map[string]Member // all package members keyed by name (incl. init and init#%d)
Functions []*Function // all functions, excluding anonymous ones
values map[types.Object]Value // package members (incl. types and methods), keyed by object
init *Function // Func("init"); the package's init function
debug bool // include full debug info in this package
printFunc string // which function to print in HTML form
// The following fields are set transiently, then cleared
// after building.
buildOnce sync.Once // ensures package building occurs once
ninit int32 // number of init functions
info *types.Info // package type information
files []*ast.File // package ASTs
initVersion map[ast.Expr]string // goversion to use for each global var init expr
}
// A Member is a member of a Go package, implemented by *NamedConst,
// *Global, *Function, or *Type; they are created by package-level
// const, var, func and type declarations respectively.
type Member interface {
Name() string // declared name of the package member
String() string // package-qualified name of the package member
RelString(*types.Package) string // like String, but relative refs are unqualified
Object() types.Object // typechecker's object for this member, if any
Type() types.Type // type of the package member
Token() token.Token // token.{VAR,FUNC,CONST,TYPE}
Package() *Package // the containing package
}
// A Type is a Member of a Package representing a package-level named type.
type Type struct {
object *types.TypeName
pkg *Package
}
// A NamedConst is a Member of a Package representing a package-level
// named constant.
//
// Pos() returns the position of the declaring ast.ValueSpec.Names[*]
// identifier.
//
// NB: a NamedConst is not a Value; it contains a constant Value, which
// it augments with the name and position of its 'const' declaration.
type NamedConst struct {
object *types.Const
Value *Const
pkg *Package
}
// A Value is an IR value that can be referenced by an instruction.
type Value interface {
setID(ID)
// Name returns the name of this value, and determines how
// this Value appears when used as an operand of an
// Instruction.
//
// This is the same as the source name for Parameters,
// Builtins, Functions, FreeVars, Globals.
// For constants, it is a representation of the constant's value
// and type. For all other Values this is the name of the
// virtual register defined by the instruction.
//
// The name of an IR Value is not semantically significant,
// and may not even be unique within a function.
Name() string
// ID returns the ID of this value. IDs are unique within a single
// function and are densely numbered, but may contain gaps.
// Values and other Instructions share the same ID space.
// Globally, values are identified by their addresses. However,
// IDs exist to facilitate efficient storage of mappings between
// values and data when analysing functions.
//
// NB: IDs are allocated late in the IR construction process and
// are not available to early stages of said process.
ID() ID
// If this value is an Instruction, String returns its
// disassembled form; otherwise it returns unspecified
// human-readable information about the Value, such as its
// kind, name and type.
String() string
// Type returns the type of this value. Many instructions
// (e.g. IndexAddr) change their behaviour depending on the
// types of their operands.
Type() types.Type
// Parent returns the function to which this Value belongs.
// It returns nil for named Functions, Builtin and Global.
Parent() *Function
// Referrers returns the list of instructions that have this
// value as one of their operands; it may contain duplicates
// if an instruction has a repeated operand.
//
// Referrers actually returns a pointer through which the
// caller may perform mutations to the object's state.
//
// Referrers is currently only defined if Parent()!=nil,
// i.e. for the function-local values FreeVar, Parameter,
// Functions (iff anonymous) and all value-defining instructions.
// It returns nil for named Functions, Builtin and Global.
//
// Instruction.Operands contains the inverse of this relation.
Referrers() *[]Instruction
Operands(rands []*Value) []*Value // nil for non-Instructions
// Source returns the AST node responsible for creating this
// value. A single AST node may be responsible for more than one
// value, and not all values have an associated AST node.
//
// Do not use this method to find a Value given an ast.Expr; use
// ValueForExpr instead.
Source() ast.Node
// Pos returns Source().Pos() if Source is not nil, else it
// returns token.NoPos.
Pos() token.Pos
}
// An Instruction is an IR instruction that computes a new Value or
// has some effect.
//
// An Instruction that defines a value (e.g. BinOp) also implements
// the Value interface; an Instruction that only has an effect (e.g. Store)
// does not.
type Instruction interface {
setSource(ast.Node)
setID(ID)
Comment() string
// String returns the disassembled form of this value.
//
// Examples of Instructions that are Values:
// "BinOp {+} t1 t2" (BinOp)
// "Call len t1" (Call)
// Note that the name of the Value is not printed.
//
// Examples of Instructions that are not Values:
// "Return t1" (Return)
// "Store {int} t2 t1" (Store)
//
// (The separation of Value.Name() from Value.String() is useful
// for some analyses which distinguish the operation from the
// value it defines, e.g., 'y = local int' is both an allocation
// of memory 'local int' and a definition of a pointer y.)
String() string
// ID returns the ID of this instruction. IDs are unique within a single
// function and are densely numbered, but may contain gaps.
// Globally, instructions are identified by their addresses. However,
// IDs exist to facilitate efficient storage of mappings between
// instructions and data when analysing functions.
//
// NB: IDs are allocated late in the IR construction process and
// are not available to early stages of said process.
ID() ID
// Parent returns the function to which this instruction
// belongs.
Parent() *Function
// Block returns the basic block to which this instruction
// belongs.
Block() *BasicBlock
// setBlock sets the basic block to which this instruction belongs.
setBlock(*BasicBlock)
// Operands returns the operands of this instruction: the
// set of Values it references.
//
// Specifically, it appends their addresses to rands, a
// user-provided slice, and returns the resulting slice,
// permitting avoidance of memory allocation.
//
// The operands are appended in undefined order, but the order
// is consistent for a given Instruction; the addresses are
// always non-nil but may point to a nil Value. Clients may
// store through the pointers, e.g. to effect a value
// renaming.
//
// Value.Referrers is a subset of the inverse of this
// relation. (Referrers are not tracked for all types of
// Values.)
Operands(rands []*Value) []*Value
Referrers() *[]Instruction // nil for non-Values
// Source returns the AST node responsible for creating this
// instruction. A single AST node may be responsible for more than
// one instruction, and not all instructions have an associated
// AST node.
Source() ast.Node
// Pos returns Source().Pos() if Source is not nil, else it
// returns token.NoPos.
Pos() token.Pos
}
// A Node is a node in the IR value graph. Every concrete type that
// implements Node is also either a Value, an Instruction, or both.
//
// Node contains the methods common to Value and Instruction, plus the
// Operands and Referrers methods generalized to return nil for
// non-Instructions and non-Values, respectively.
//
// Node is provided to simplify IR graph algorithms. Clients should
// use the more specific and informative Value or Instruction
// interfaces where appropriate.
type Node interface {
setID(ID)
// Common methods:
ID() ID
String() string
Source() ast.Node
Pos() token.Pos
Parent() *Function
// Partial methods:
Operands(rands []*Value) []*Value // nil for non-Instructions
Referrers() *[]Instruction // nil for non-Values
}
type Synthetic int
const (
SyntheticLoadedFromExportData Synthetic = iota + 1
SyntheticPackageInitializer
SyntheticThunk
SyntheticWrapper
SyntheticBound
SyntheticGeneric
SyntheticRangeOverFuncYield
)
func (syn Synthetic) String() string {
switch syn {
case SyntheticLoadedFromExportData:
return "loaded from export data"
case SyntheticPackageInitializer:
return "package initializer"
case SyntheticThunk:
return "thunk"
case SyntheticWrapper:
return "wrapper"
case SyntheticBound:
return "bound"
case SyntheticGeneric:
return "generic"
case SyntheticRangeOverFuncYield:
return "range-over-func yield"
default:
return fmt.Sprintf("Synthetic(%d)", syn)
}
}
// Function represents the parameters, results, and code of a function
// or method.
//
// If Blocks is nil, this indicates an external function for which no
// Go source code is available. In this case, FreeVars, Locals, and
// Params are nil too. Clients performing whole-program analysis must
// handle external functions specially.
//
// Blocks contains the function's control-flow graph (CFG).
// Blocks[0] is the function entry point; block order is not otherwise
// semantically significant, though it may affect the readability of
// the disassembly.
// To iterate over the blocks in dominance order, use DomPreorder().
//
// A nested function (Parent()!=nil) that refers to one or more
// lexically enclosing local variables ("free variables") has FreeVars.
// Such functions cannot be called directly but require a
// value created by MakeClosure which, via its Bindings, supplies
// values for these parameters.
//
// If the function is a method (Signature.Recv() != nil) then the first
// element of Params is the receiver parameter.
//
// A Go package may declare many functions called "init".
// For each one, Object().Name() returns "init" but Name() returns
// "init#1", etc, in declaration order.
//
// Pos() returns the declaring ast.FuncLit.Type.Func or the position
// of the ast.FuncDecl.Name, if the function was explicit in the
// source. Synthetic wrappers, for which Synthetic != "", may share
// the same position as the function they wrap.
// Syntax.Pos() always returns the position of the declaring "func" token.
//
// When the operand of a range statement is an iterator function,
// the loop body is transformed into a synthetic anonymous function
// that is passed as the yield argument in a call to the iterator.
// In that case, Function.Source() is the ast.RangeStmt.
//
// Synthetic functions, for which Synthetic != "", are functions
// that do not appear in the source AST. These include:
// - method wrappers,
// - thunks,
// - bound functions,
// - empty functions built from loaded type information,
// - yield functions created from range-over-func loops, and
// - package init functions.
//
// Type() returns the function's Signature.
type Function struct {
node
name string
object *types.Func // symbol for declared function (nil for FuncLit or synthetic init)
method *types.Selection // info about provenance of synthetic methods
Signature *types.Signature
generics instanceWrapperMap
Synthetic Synthetic // provenance of synthetic function; 0 for true source functions
parent *Function // enclosing function if anon; nil if global
Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error)
Prog *Program // enclosing program
// These fields are populated only when the function body is built:
Params []*Parameter // function parameters; for methods, includes receiver
FreeVars []*FreeVar // free variables whose values must be supplied by closure
Locals []*Alloc // frame-allocated variables of this function
Blocks []*BasicBlock // basic blocks of the function; nil => external
Exit *BasicBlock // The function's exit block
AnonFuncs []*Function // anonymous functions (from FuncLit, RangeStmt) directly beneath this one
referrers []Instruction // referring instructions (iff Parent() != nil)
goversion string // Go version of syntax (NB: init is special)
// uniq is not stored in functionBody because we need it after function building finishes
uniq int64 // source of unique ints within the source tree while building
*functionBody
}
type instanceWrapperMap struct {
h typeutil.Hasher
entries map[uint32][]struct {
key *types.TypeList
val *Function
}
len int
}
func typeListIdentical(l1, l2 *types.TypeList) bool {
if l1.Len() != l2.Len() {
return false
}
for i := 0; i < l1.Len(); i++ {
t1 := l1.At(i)
t2 := l2.At(i)
if !types.Identical(t1, t2) {
return false
}
}
return true
}
func (m *instanceWrapperMap) At(key *types.TypeList) *Function {
if m.entries == nil {
m.entries = make(map[uint32][]struct {
key *types.TypeList
val *Function
})
m.h = typeutil.MakeHasher()
}
var hash uint32
for t := range key.Types() {
hash += m.h.Hash(t)
}
for _, e := range m.entries[hash] {
if typeListIdentical(e.key, key) {
return e.val
}
}
return nil
}
func (m *instanceWrapperMap) Set(key *types.TypeList, val *Function) {
if m.entries == nil {
m.entries = make(map[uint32][]struct {
key *types.TypeList
val *Function
})
m.h = typeutil.MakeHasher()
}
var hash uint32
for t := range key.Types() {
hash += m.h.Hash(t)
}
for i, e := range m.entries[hash] {
if typeListIdentical(e.key, key) {
m.entries[hash][i].val = val
return
}
}
m.entries[hash] = append(m.entries[hash], struct {
key *types.TypeList
val *Function
}{key, val})
m.len++
}
func (m *instanceWrapperMap) Len() int {
return m.len
}
type constValue struct {
c Constant
idx int
}
type functionBody struct {
// The following fields are set transiently during building,
// then cleared.
currentBlock *BasicBlock // where to emit code
vars map[*types.Var]Value // addresses of local variables
results []*Alloc // result allocations of the current function
returnVars []*types.Var // variables for a return statement. Either results or for range-over-func a parent's results
targets *targets // linked stack of branch targets
lblocks map[*types.Label]*lblock // labelled blocks
jump *types.Var // synthetic variable for the yield state (non-nil => range-over-func)
deferstack *types.Var // synthetic variable holding enclosing ssa:deferstack()
sourceFn *Function // nearest enclosing source function
exits []*exit // exits of the function that need to be resolved
consts map[constKey]constValue
aggregateConsts typeutil.Map[[]*AggregateConst]
wr *HTMLWriter
fakeExits BlockSet
blocksets [5]BlockSet
hasDefer bool
// a contiguous block of instructions that will be used by blocks,
// to avoid making multiple allocations.
scratchInstructions []Instruction
}
// BasicBlock represents an IR basic block.
//
// The final element of Instrs is always an explicit transfer of
// control (If, Jump, Return, Panic, or Unreachable).
//
// A block may contain no Instructions only if it is unreachable,
// i.e., Preds is nil. Empty blocks are typically pruned.
//
// BasicBlocks and their Preds/Succs relation form a (possibly cyclic)
// graph independent of the IR Value graph: the control-flow graph or
// CFG. It is illegal for multiple edges to exist between the same
// pair of blocks.
//
// Each BasicBlock is also a node in the dominator tree of the CFG.
// The tree may be navigated using Idom()/Dominees() and queried using
// Dominates().
//
// The order of Preds and Succs is significant (to Phi and If
// instructions, respectively).
type BasicBlock struct {
Index int // index of this block within Parent().Blocks
Comment string // optional label; no semantic significance
parent *Function // parent function
Instrs []Instruction // instructions in order
Preds, Succs []*BasicBlock // predecessors and successors
succs2 [2]*BasicBlock // initial space for Succs
dom domInfo // dominator tree info
pdom domInfo // post-dominator tree info
post int
gaps int // number of nil Instrs (transient)
rundefers int // number of rundefers (transient)
}
// Pure values ----------------------------------------
// A FreeVar represents a free variable of the function to which it
// belongs.
//
// FreeVars are used to implement anonymous functions, whose free
// variables are lexically captured in a closure formed by
// MakeClosure. The value of such a free var is an Alloc or another
// FreeVar and is considered a potentially escaping heap address, with
// pointer type.
//
// FreeVars are also used to implement bound method closures. Such a
// free var represents the receiver value and may be of any type that
// has concrete methods.
//
// Pos() returns the position of the value that was captured, which
// belongs to an enclosing function.
type FreeVar struct {
node
name string
typ types.Type
parent *Function
referrers []Instruction
// Transiently needed during building.
outer Value // the Value captured from the enclosing context.
}
// A Parameter represents an input parameter of a function.
type Parameter struct {
register
name string
object *types.Var // non-nil
}
// A Const represents the value of a constant expression.
//
// The underlying type of a constant may be any boolean, numeric, or
// string type. In addition, a Const may represent the nil value of
// any reference type---interface, map, channel, pointer, slice, or
// function---but not "untyped nil".
//
// All source-level constant expressions are represented by a Const
// of the same type and value.
//
// Value holds the exact value of the constant, independent of its
// Type(), using the same representation as package go/constant uses for
// constants, or nil for a typed nil value.
//
// Pos() returns token.NoPos.
//
// Example printed form:
//
// Const {42}
// Const {"test"}
// Const {(3 + 4i)}
type Const struct {
register
Value constant.Value
}
type AggregateConst struct {
register
Values []Value
}
type CompositeValue struct {
register
// Bitmap records which elements were explicitly provided. For example, [4]byte{2: x} would have a bitmap of 0010.
Bitmap big.Int
// The number of bits set in Bitmap
NumSet int
// Dense list of values in the composite literal. Omitted elements are filled in with zero values.
Values []Value
}
// TODO add the element's zero constant to ArrayConst
type ArrayConst struct {
register
}
type GenericConst struct {
register
}
type Constant interface {
Instruction
Value
aConstant()
RelString(*types.Package) string
equal(Constant) bool
setType(types.Type)
}
func (*Const) aConstant() {}
func (*AggregateConst) aConstant() {}
func (*ArrayConst) aConstant() {}
func (*GenericConst) aConstant() {}
// A Global is a named Value holding the address of a package-level
// variable.
//
// Pos() returns the position of the ast.ValueSpec.Names[*]
// identifier.
type Global struct {
node
name string
object types.Object // a *types.Var; may be nil for synthetics e.g. init$guard
typ types.Type
Pkg *Package
}
// A Builtin represents a specific use of a built-in function, e.g. len.
//
// Builtins are immutable values. Builtins do not have addresses.
// Builtins can only appear in CallCommon.Func.
//
// Name() indicates the function: one of the built-in functions from the
// Go spec (excluding "make" and "new") or one of these ir-defined
// intrinsics:
//
// // wrapnilchk returns ptr if non-nil, panics otherwise.
// // (For use in indirection wrappers.)
// func ir:wrapnilchk(ptr *T, recvType, methodName string) *T
//
// // noreturnWasPanic returns true if the previously called
// // function panicked, false if it exited the process.
// func ir:noreturnWasPanic() bool
//
// Object() returns a *types.Builtin for built-ins defined by the spec,
// nil for others.
//
// Type() returns a *types.Signature representing the effective
// signature of the built-in for this call.
type Builtin struct {
node
name string
sig *types.Signature
}
// Value-defining instructions ----------------------------------------
// The Alloc instruction reserves space for a variable of the given type,
// zero-initializes it, and yields its address.
//
// Alloc values are always addresses, and have pointer types, so the
// type of the allocated variable is actually
// Type().Underlying().(*types.Pointer).Elem().
//
// If Heap is false, Alloc zero-initializes the same local variable in
// the call frame and returns its address; in this case the Alloc must
// be present in Function.Locals. We call this a "local" alloc.
//
// If Heap is true, Alloc allocates a new zero-initialized variable
// each time the instruction is executed. We call this a "new" alloc.
//
// When Alloc is applied to a channel, map or slice type, it returns
// the address of an uninitialized (nil) reference of that kind; store
// the result of MakeSlice, MakeMap or MakeChan in that location to
// instantiate these types.
//
// Pos() returns the ast.CompositeLit.Lbrace for a composite literal,
// or the ast.CallExpr.Rparen for a call to new() or for a call that
// allocates a varargs slice.
//
// Example printed form:
//
// t1 = StackAlloc <*int>
// t2 = HeapAlloc <*int> (new)
type Alloc struct {
register
Heap bool
index int // dense numbering; for lifting
}
var _ Instruction = (*Sigma)(nil)
var _ Value = (*Sigma)(nil)
// The Sigma instruction represents an SSI σ-node, which splits values
// at branches in the control flow.
//
// Conceptually, σ-nodes exist at the end of blocks that branch and
// constitute parallel assignments to one value per destination block.
// However, such a representation would be awkward to work with, so
// instead we place σ-nodes at the beginning of branch targets. The
// From field denotes to which incoming edge the node applies.
//
// Within a block, all σ-nodes must appear before all non-σ nodes.
//
// Example printed form:
//
// t2 = Sigma [#0] t1 (x)
type Sigma struct {
register
From *BasicBlock
X Value
live bool // used during lifting
}
type CopyInfo uint64
const (
CopyInfoUnspecified CopyInfo = 0
CopyInfoNotNil CopyInfo = 1 << iota
CopyInfoNotZeroLength
CopyInfoNotNegative
CopyInfoSingleConcreteType
CopyInfoClosed
)
type Copy struct {
register
X Value
Why Instruction
Info CopyInfo
}
// The Phi instruction represents an SSA φ-node, which combines values
// that differ across incoming control-flow edges and yields a new
// value. Within a block, all φ-nodes must appear before all non-φ, non-σ
// nodes.
//
// Pos() returns the position of the && or || for short-circuit
// control-flow joins, or that of the *Alloc for φ-nodes inserted
// during SSA renaming.
//
// Example printed form:
//
// t3 = Phi 2:t1 4:t2 (x)
type Phi struct {
register
Edges []Value // Edges[i] is value for Block().Preds[i]
live bool // used during lifting
}
// The Call instruction represents a function or method call.
//
// The Call instruction yields the function result if there is exactly
// one. Otherwise it returns a tuple, the components of which are
// accessed via Extract.
//
// See CallCommon for generic function call documentation.
//
// Pos() returns the ast.CallExpr.Lparen, if explicit in the source.
//
// Example printed form:
//
// t3 = Call <()> println t1 t2
// t4 = Call <()> foo$1
// t6 = Invoke t5.String
type Call struct {
register
Call CallCommon
}
// The BinOp instruction yields the result of binary operation X Op Y.
//
// Pos() returns the ast.BinaryExpr.OpPos, if explicit in the source.
//
// Example printed form:
//
// t3 = BinOp {+} t2 t1
type BinOp struct {
register
// One of:
// ADD SUB MUL QUO REM + - * / %
// AND OR XOR SHL SHR AND_NOT & | ^ << >> &^
// EQL NEQ LSS LEQ GTR GEQ == != < <= < >=
Op token.Token
X, Y Value
}
// The UnOp instruction yields the result of Op X.
// XOR is bitwise complement.
// SUB is negation.
// NOT is logical negation.
//
// Example printed form:
//
// t2 = UnOp {^} t1
type UnOp struct {
register
Op token.Token // One of: NOT SUB XOR ! - ^
X Value
}
// The Load instruction loads a value from a memory address.
//
// For implicit memory loads, Pos() returns the position of the
// most closely associated source-level construct; the details are not
// specified.
//
// Example printed form:
//
// t2 = Load t1
type Load struct {
register
X Value
}
// The ChangeType instruction applies to X a value-preserving type
// change to Type().
//
// Type changes are permitted:
// - between a named type and its underlying type.
// - between two named types of the same underlying type.
// - between (possibly named) pointers to identical base types.
// - from a bidirectional channel to a read- or write-channel,
// optionally adding/removing a name.
//
// This operation cannot fail dynamically.
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Example printed form:
//
// t2 = ChangeType <*T> t1
type ChangeType struct {
register
X Value
}
// The Convert instruction yields the conversion of value X to type
// Type(). One or both of those types is basic (but possibly named).
//
// A conversion may change the value and representation of its operand.
// Conversions are permitted:
// - between real numeric types.
// - between complex numeric types.
// - between string and []byte or []rune.
// - between pointers and unsafe.Pointer.
// - between unsafe.Pointer and uintptr.
// - from (Unicode) integer to (UTF-8) string.
//
// A conversion may imply a type name change also.
//
// This operation cannot fail dynamically.
//
// Conversions of untyped string/number/bool constants to a specific
// representation are eliminated during IR construction.
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Example printed form:
//
// t2 = Convert <[]byte> t1
type Convert struct {
register
X Value
}
// The MultiConvert instruction yields the conversion of value X to type
// Type(). Either X.Type() or Type() must be a type parameter. Each
// type in the type set of X.Type() can be converted to each type in the
// type set of Type().
//
// See the documentation for Convert, ChangeType, SliceToArray, and SliceToArrayPointer
// for the conversions that are permitted.
//
// This operation can fail dynamically (see SliceToArrayPointer).
//
// Example printed form:
//
// t1 = multiconvert D <- S (t0) [*[2]rune <- []rune | string <- []rune]
type MultiConvert struct {
register
X Value
from typeutil.TypeSet
to typeutil.TypeSet
}
// ChangeInterface constructs a value of one interface type from a
// value of another interface type known to be assignable to it.
// This operation cannot fail.
//
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
// instruction arose from an explicit e.(T) operation; or token.NoPos
// otherwise.
//
// Example printed form:
//
// t2 = ChangeInterface t1
type ChangeInterface struct {
register
X Value
}
// The SliceToArrayPointer instruction yields the conversion of slice X to
// array pointer.
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Example printed form:
//
// t2 = SliceToArrayPointer <*[4]byte> t1
type SliceToArrayPointer struct {
register
X Value
}
// The SliceToArray instruction yields the conversion of slice X to
// array.
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Example printed form:
//
// t2 = SliceToArray <[4]byte> t1
type SliceToArray struct {
register
X Value
}
// MakeInterface constructs an instance of an interface type from a
// value of a concrete type.
//
// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set
// of X, and Program.MethodValue(m) to find the implementation of a method.
//
// To construct the zero value of an interface type T, use:
//
// NewConst(constant.MakeNil(), T, pos)
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Example printed form:
//
// t2 = MakeInterface t1
type MakeInterface struct {
register
X Value
}
// The MakeClosure instruction yields a closure value whose code is
// Fn and whose free variables' values are supplied by Bindings.
//
// Type() returns a (possibly named) *types.Signature.
//
// Pos() returns the ast.FuncLit.Type.Func for a function literal
// closure or the ast.SelectorExpr.Sel for a bound method closure.
//
// Example printed form:
//
// t1 = MakeClosure foo$1 t1 t2
// t5 = MakeClosure (T).foo$bound t4
type MakeClosure struct {
register
Fn Value // always a *Function
Bindings []Value // values for each free variable in Fn.FreeVars
}
// The MakeMap instruction creates a new hash-table-based map object
// and yields a value of kind map.
//
// Type() returns a (possibly named) *types.Map.
//
// Pos() returns the ast.CallExpr.Lparen, if created by make(map), or
// the ast.CompositeLit.Lbrack if created by a literal.
//
// Example printed form:
//
// t1 = MakeMap
// t2 = MakeMap t1
type MakeMap struct {
register
Reserve Value // initial space reservation; nil => default
}
// The MakeChan instruction creates a new channel object and yields a
// value of kind chan.
//
// Type() returns a (possibly named) *types.Chan.
//
// Pos() returns the ast.CallExpr.Lparen for the make(chan) that
// created it.
//
// Example printed form:
//
// t3 = MakeChan t1
// t4 = MakeChan t2
type MakeChan struct {
register
Size Value // int; size of buffer; zero => synchronous.
}
// The MakeSlice instruction yields a slice of length Len backed by a
// newly allocated array of length Cap.
//
// Both Len and Cap must be non-nil Values of integer type.
//
// (Alloc(types.Array) followed by Slice will not suffice because
// Alloc can only create arrays of constant length.)
//
// Type() returns a (possibly named) *types.Slice.
//
// Pos() returns the ast.CallExpr.Lparen for the make([]T) that
// created it.
//
// Example printed form:
//
// t3 = MakeSlice <[]string> t1 t2
// t4 = MakeSlice t1 t2
type MakeSlice struct {
register
Len Value
Cap Value
}
// The Slice instruction yields a slice of an existing string, slice
// or *array X between optional integer bounds Low and High.
//
// Dynamically, this instruction panics if X evaluates to a nil *array
// pointer.
//
// Type() returns string if the type of X was string, otherwise a
// *types.Slice with the same element type as X.
//
// Pos() returns the ast.SliceExpr.Lbrack if created by a x[:] slice
// operation, the ast.CompositeLit.Lbrace if created by a literal, or
// NoPos if not explicit in the source (e.g. a variadic argument slice).
//
// Example printed form:
//
// t4 = Slice <[]int> t3 t2 t1
type Slice struct {
register
X Value // slice, string, or *array
Low, High, Max Value // each may be nil
}
// The FieldAddr instruction yields the address of Field of *struct X.
//
// The field is identified by its index within the field list of the
// struct type of X.
//
// Dynamically, this instruction panics if X evaluates to a nil
// pointer.
//
// Type() returns a (possibly named) *types.Pointer.
//
// Pos() returns the position of the ast.SelectorExpr.Sel for the
// field, if explicit in the source.
//
// Example printed form:
//
// t2 = FieldAddr <*int> [0] (X) t1
type FieldAddr struct {
register
X Value // *struct
Field int // field is X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct).Field(Field)
}
// The Field instruction yields the Field of struct X.
//
// The field is identified by its index within the field list of the
// struct type of X; by using numeric indices we avoid ambiguity of
// package-local identifiers and permit compact representations.
//
// Pos() returns the position of the ast.SelectorExpr.Sel for the
// field, if explicit in the source.
//
// Example printed form:
//
// t2 = FieldAddr [0] (X) t1
type Field struct {
register
X Value // struct
Field int // index into X.Type().(*types.Struct).Fields
}
// The IndexAddr instruction yields the address of the element at
// index Index of collection X. Index is an integer expression.
//
// The elements of maps and strings are not addressable; use StringLookup, MapLookup or
// MapUpdate instead.
//
// Dynamically, this instruction panics if X evaluates to a nil *array
// pointer.
//
// Type() returns a (possibly named) *types.Pointer.
//
// Pos() returns the ast.IndexExpr.Lbrack for the index operation, if
// explicit in the source.
//
// Example printed form:
//
// t3 = IndexAddr <*int> t2 t1
type IndexAddr struct {
register
X Value // slice or *array,
Index Value // numeric index
}
// The Index instruction yields element Index of array X.
//
// Pos() returns the ast.IndexExpr.Lbrack for the index operation, if
// explicit in the source.
//
// Example printed form:
//
// t3 = Index t2 t1
type Index struct {
register
X Value // array
Index Value // integer index
}
// The MapLookup instruction yields element Index of collection X, a map.
//
// If CommaOk, the result is a 2-tuple of the value above and a
// boolean indicating the result of a map membership test for the key.
// The components of the tuple are accessed using Extract.
//
// Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source.
//
// Example printed form:
//
// t4 = MapLookup t3 t1
// t6 = MapLookup <(string, bool)> t3 t2
type MapLookup struct {
register
X Value // map
Index Value // key-typed index
CommaOk bool // return a value,ok pair
}
// The StringLookup instruction yields element Index of collection X, a string.
// Index is an integer expression.
//
// Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source.
//
// Example printed form:
//
// t3 = StringLookup t2 t1
type StringLookup struct {
register
X Value // string
Index Value // numeric index
}
// SelectState is a helper for Select.
// It represents one goal state and its corresponding communication.
type SelectState struct {
Dir types.ChanDir // direction of case (SendOnly or RecvOnly)
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
Pos token.Pos // position of token.ARROW
DebugNode ast.Node // ast.SendStmt or ast.UnaryExpr(<-) [debug mode]
}
// The Select instruction tests whether (or blocks until) one
// of the specified sent or received states is entered.
//
// Let n be the number of States for which Dir==RECV and Tᵢ (0 ≤ i < n)
// be the element type of each such state's Chan.
// Select returns an n+2-tuple
//
// (index int, recvOk bool, r₀ T₀, ... rₙ-1 Tₙ-1)
//
// The tuple's components, described below, must be accessed via the
// Extract instruction.
//
// If Blocking, select waits until exactly one state holds, i.e. a
// channel becomes ready for the designated operation of sending or
// receiving; select chooses one among the ready states
// pseudorandomly, performs the send or receive operation, and sets
// 'index' to the index of the chosen channel.
//
// If !Blocking, select doesn't block if no states hold; instead it
// returns immediately with index equal to -1.
//
// If the chosen channel was used for a receive, the rᵢ component is
// set to the received value, where i is the index of that state among
// all n receive states; otherwise rᵢ has the zero value of type Tᵢ.
// Note that the receive index i is not the same as the state
// index index.
//
// The second component of the triple, recvOk, is a boolean whose value
// is true iff the selected operation was a receive and the receive
// successfully yielded a value.
//
// Pos() returns the ast.SelectStmt.Select.
//
// Example printed form:
//
// t6 = SelectNonBlocking <(index int, ok bool, int)> [<-t4, t5<-t1]
// t11 = SelectBlocking <(index int, ok bool)> []
type Select struct {
register
States []*SelectState
Blocking bool
}
// The Range instruction yields an iterator over the domain and range
// of X, which must be a string or map.
//
// Elements are accessed via Next.
//
// Type() returns an opaque and degenerate "rangeIter" type.
//
// Pos() returns the ast.RangeStmt.For.
//
// Example printed form:
//
// t2 = Range t1
type Range struct {
register
X Value // string or map
}
// The Next instruction reads and advances the (map or string)
// iterator Iter and returns a 3-tuple value (ok, k, v). If the
// iterator is not exhausted, ok is true and k and v are the next
// elements of the domain and range, respectively. Otherwise ok is
// false and k and v are undefined.
//
// Components of the tuple are accessed using Extract.
//
// The IsString field distinguishes iterators over strings from those
// over maps, as the Type() alone is insufficient: consider
// map[int]rune.
//
// Type() returns a *types.Tuple for the triple (ok, k, v).
// The types of k and/or v may be types.Invalid.
//
// Example printed form:
//
// t5 = Next <(ok bool, k int, v rune)> t2
// t5 = Next <(ok bool, k invalid type, v invalid type)> t2
type Next struct {
register
Iter Value
IsString bool // true => string iterator; false => map iterator.
}
// The TypeAssert instruction tests whether interface value X has type
// AssertedType.
//
// If !CommaOk, on success it returns v, the result of the conversion
// (defined below); on failure it panics.
//
// If CommaOk: on success it returns a pair (v, true) where v is the
// result of the conversion; on failure it returns (z, false) where z
// is AssertedType's zero value. The components of the pair must be
// accessed using the Extract instruction.
//
// If AssertedType is a concrete type, TypeAssert checks whether the
// dynamic type in interface X is equal to it, and if so, the result
// of the conversion is a copy of the value in the interface.
//
// If AssertedType is an interface, TypeAssert checks whether the
// dynamic type of the interface is assignable to it, and if so, the
// result of the conversion is a copy of the interface value X.
// If AssertedType is a superinterface of X.Type(), the operation will
// fail iff the operand is nil. (Contrast with ChangeInterface, which
// performs no nil-check.)
//
// Type() reflects the actual type of the result, possibly a
// 2-types.Tuple; AssertedType is the asserted type.
//
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
// instruction arose from an explicit e.(T) operation; or the
// ast.CaseClause.Case if the instruction arose from a case of a
// type-switch statement.
//
// Example printed form:
//
// t2 = TypeAssert t1
// t4 = TypeAssert <(value fmt.Stringer, ok bool)> t1
type TypeAssert struct {
register
X Value
AssertedType types.Type
CommaOk bool
}
// The Extract instruction yields component Index of Tuple.
//
// This is used to access the results of instructions with multiple
// return values, such as Call, TypeAssert, Next, Recv,
// MapLookup and others.
//
// Example printed form:
//
// t7 = Extract [1] (ok) t4
type Extract struct {
register
Tuple Value
Index int
}
// Instructions executed for effect. They do not yield a value. --------------------
// The Jump instruction transfers control to the sole successor of its
// owning block.
//
// A Jump must be the last instruction of its containing BasicBlock.
//
// Pos() returns NoPos.
//
// Example printed form:
//
// Jump → b1
type Jump struct {
anInstruction
}
// The Unreachable pseudo-instruction signals that execution cannot
// continue after the preceding function call because it terminates
// the process.
//
// The instruction acts as a control instruction, jumping to the exit
// block. However, this jump will never execute.
//
// An Unreachable instruction must be the last instruction of its
// containing BasicBlock.
//
// Example printed form:
//
// Unreachable → b1
type Unreachable struct {
anInstruction
}
// The If instruction transfers control to one of the two successors
// of its owning block, depending on the boolean Cond: the first if
// true, the second if false.
//
// An If instruction must be the last instruction of its containing
// BasicBlock.
//
// Pos() returns the *ast.IfStmt, if explicit in the source.
//
// Example printed form:
//
// If t2 → b1 b2
type If struct {
anInstruction
Cond Value
}
type ConstantSwitch struct {
anInstruction
Tag Value
// Constant branch conditions. A nil Value denotes the (implicit
// or explicit) default branch.
Conds []Value
}
type TypeSwitch struct {
register
Tag Value
Conds []types.Type
}
// The Return instruction returns values and control back to the calling
// function.
//
// len(Results) is always equal to the number of results in the
// function's signature.
//
// If len(Results) > 1, Return returns a tuple value with the specified
// components which the caller must access using Extract instructions.
//
// There is no instruction to return a ready-made tuple like those
// returned by a "value,ok"-mode TypeAssert, MapLookup or Recv or
// a tail-call to a function with multiple result parameters.
//
// Return must be the last instruction of its containing BasicBlock.
// Such a block has no successors.
//
// Pos() returns the ast.ReturnStmt.Return, if explicit in the source.
//
// Example printed form:
//
// Return
// Return t1 t2
type Return struct {
anInstruction
Results []Value
}
// The RunDefers instruction pops and invokes the entire stack of
// procedure calls pushed by Defer instructions in this function.
//
// It is legal to encounter multiple 'rundefers' instructions in a
// single control-flow path through a function; this is useful in
// the combined init() function, for example.
//
// Pos() returns NoPos.
//
// Example printed form:
//
// RunDefers
type RunDefers struct {
anInstruction
}
// The Panic instruction initiates a panic with value X.
//
// A Panic instruction must be the last instruction of its containing
// BasicBlock, which must have one successor, the exit block.
//
// NB: 'go panic(x)' and 'defer panic(x)' do not use this instruction;
// they are treated as calls to a built-in function.
//
// Pos() returns the ast.CallExpr.Lparen if this panic was explicit
// in the source.
//
// Example printed form:
//
// Panic t1
type Panic struct {
anInstruction
X Value // an interface{}
}
// The Go instruction creates a new goroutine and calls the specified
// function within it.
//
// See CallCommon for generic function call documentation.
//
// Pos() returns the ast.GoStmt.Go.
//
// Example printed form:
//
// Go println t1
// Go t3
// GoInvoke t4.Bar t2
type Go struct {
anInstruction
Call CallCommon
}
// The Defer instruction pushes the specified call onto a stack of
// functions to be called by a RunDefers instruction or by a panic.
//
// If _DeferStack != nil, it indicates the defer list that the defer is
// added to. Defer list values come from the Builtin function
// ssa:deferstack. Calls to ssa:deferstack() produces the defer stack
// of the current function frame. _DeferStack allows for deferring into an
// alternative function stack than the current function.
//
// See CallCommon for generic function call documentation.
//
// Pos() returns the ast.DeferStmt.Defer.
//
// Example printed form:
//
// Defer println t1
// Defer t3
// DeferInvoke t4.Bar t2
type Defer struct {
anInstruction
Call CallCommon
_DeferStack Value // stack (from ssa:deferstack() intrinsic) onto which this function is pushed
// TODO: Exporting _DeferStack and possibly making _DeferStack != nil awaits proposal https://github.com/golang/go/issues/66601.
}
// The Send instruction sends X on channel Chan.
//
// Pos() returns the ast.SendStmt.Arrow, if explicit in the source.
//
// Example printed form:
//
// Send t2 t1
type Send struct {
anInstruction
Chan, X Value
}
// The Recv instruction receives from channel Chan.
//
// If CommaOk, the result is a 2-tuple of the value above
// and a boolean indicating the success of the receive. The
// components of the tuple are accessed using Extract.
//
// Pos() returns the ast.UnaryExpr.OpPos, if explicit in the source.
// For receive operations implicit in ranging over a channel,
// Pos() returns the ast.RangeStmt.For.
//
// Example printed form:
//
// t2 = Recv t1
// t3 = Recv <(int, bool)> t1
type Recv struct {
register
Chan Value
CommaOk bool
}
// The Store instruction stores Val at address Addr.
// Stores can be of arbitrary types.
//
// Pos() returns the position of the source-level construct most closely
// associated with the memory store operation.
// Since implicit memory stores are numerous and varied and depend upon
// implementation choices, the details are not specified.
//
// Example printed form:
//
// Store {int} t2 t1
type Store struct {
anInstruction
Addr Value
Val Value
}
// The BlankStore instruction is emitted for assignments to the blank
// identifier.
//
// BlankStore is a pseudo-instruction: it has no dynamic effect.
//
// Pos() returns NoPos.
//
// Example printed form:
//
// BlankStore t1
type BlankStore struct {
anInstruction
Val Value
}
// The MapUpdate instruction updates the association of Map[Key] to
// Value.
//
// Pos() returns the ast.KeyValueExpr.Colon or ast.IndexExpr.Lbrack,
// if explicit in the source.
//
// Example printed form:
//
// MapUpdate t3 t1 t2
type MapUpdate struct {
anInstruction
Map Value
Key Value
Value Value
}
// A DebugRef instruction maps a source-level expression Expr to the
// IR value X that represents the value (!IsAddr) or address (IsAddr)
// of that expression.
//
// DebugRef is a pseudo-instruction: it has no dynamic effect.
//
// Pos() returns Expr.Pos(), the start position of the source-level
// expression. This is not the same as the "designated" token as
// documented at Value.Pos(). e.g. CallExpr.Pos() does not return the
// position of the ("designated") Lparen token.
//
// DebugRefs are generated only for functions built with debugging
// enabled; see Package.SetDebugMode() and the GlobalDebug builder
// mode flag.
//
// DebugRefs are not emitted for ast.Idents referring to constants or
// predeclared identifiers, since they are trivial and numerous.
// Nor are they emitted for ast.ParenExprs.
//
// (By representing these as instructions, rather than out-of-band,
// consistency is maintained during transformation passes by the
// ordinary SSA renaming machinery.)
//
// Example printed form:
//
// ; *ast.CallExpr @ 102:9 is t5
// ; var x float64 @ 109:72 is x
// ; address of *ast.CompositeLit @ 216:10 is t0
type DebugRef struct {
anInstruction
Expr ast.Expr // the referring expression (never *ast.ParenExpr)
object types.Object // the identity of the source var/func
IsAddr bool // Expr is addressable and X is the address it denotes
X Value // the value or address of Expr
}
// Embeddable mix-ins and helpers for common parts of other structs. -----------
// register is a mix-in embedded by all IR values that are also
// instructions, i.e. virtual registers, and provides a uniform
// implementation of most of the Value interface: Value.Name() is a
// numbered register (e.g. "t0"); the other methods are field accessors.
//
// Temporary names are automatically assigned to each register on
// completion of building a function in IR form.
type register struct {
anInstruction
typ types.Type // type of virtual register
referrers []Instruction
}
type node struct {
source ast.Node
id ID
}
func (n *node) setID(id ID) { n.id = id }
func (n node) ID() ID { return n.id }
func (n *node) setSource(source ast.Node) { n.source = source }
func (n *node) Source() ast.Node { return n.source }
func (n *node) Pos() token.Pos {
if n.source != nil {
return n.source.Pos()
}
return token.NoPos
}
// anInstruction is a mix-in embedded by all Instructions.
// It provides the implementations of the Block and setBlock methods.
type anInstruction struct {
node
block *BasicBlock // the basic block of this instruction
comment string
}
func (instr anInstruction) Comment() string {
return instr.comment
}
// CallCommon is contained by Go, Defer and Call to hold the
// common parts of a function or method call.
//
// Each CallCommon exists in one of two modes, function call and
// interface method invocation, or "call" and "invoke" for short.
//
// 1. "call" mode: when Method is nil (!IsInvoke), a CallCommon
// represents an ordinary function call of the value in Value,
// which may be a *Builtin, a *Function or any other value of kind
// 'func'.
//
// Value may be one of:
//
// (a) a *Function, indicating a statically dispatched call
// to a package-level function, an anonymous function, or
// a method of a named type.
// (b) a *MakeClosure, indicating an immediately applied
// function literal with free variables.
// (c) a *Builtin, indicating a statically dispatched call
// to a built-in function.
// (d) any other value, indicating a dynamically dispatched
// function call.
//
// StaticCallee returns the identity of the callee in cases
// (a) and (b), nil otherwise.
//
// Args contains the arguments to the call. If Value is a method,
// Args[0] contains the receiver parameter.
//
// Example printed form:
//
// t3 = Call <()> println t1 t2
// Go t3
// Defer t3
//
// 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon
// represents a dynamically dispatched call to an interface method.
// In this mode, Value is the interface value and Method is the
// interface's abstract method. Note: an abstract method may be
// shared by multiple interfaces due to embedding; Value.Type()
// provides the specific interface used for this call.
//
// Value is implicitly supplied to the concrete method implementation
// as the receiver parameter; in other words, Args[0] holds not the
// receiver but the first true argument.
//
// Example printed form:
//
// t6 = Invoke t5.String
// GoInvoke t4.Bar t2
// DeferInvoke t4.Bar t2
//
// For all calls to variadic functions (Signature().Variadic()),
// the last element of Args is a slice.
type CallCommon struct {
Value Value // receiver (invoke mode) or func value (call mode)
Method *types.Func // abstract method (invoke mode)
Args []Value // actual parameters (in static method call, includes receiver)
TypeArgs []types.Type
Results Value
}
// IsInvoke returns true if this call has "invoke" (not "call") mode.
func (c *CallCommon) IsInvoke() bool {
return c.Method != nil
}
// Signature returns the signature of the called function.
//
// For an "invoke"-mode call, the signature of the interface method is
// returned.
//
// In either "call" or "invoke" mode, if the callee is a method, its
// receiver is represented by sig.Recv, not sig.Params().At(0).
func (c *CallCommon) Signature() *types.Signature {
if c.Method != nil {
return c.Method.Type().(*types.Signature)
}
return typeutil.CoreType(c.Value.Type()).(*types.Signature)
}
// StaticCallee returns the callee if this is a trivially static
// "call"-mode call to a function.
func (c *CallCommon) StaticCallee() *Function {
switch fn := c.Value.(type) {
case *Function:
return fn
case *MakeClosure:
return fn.Fn.(*Function)
}
return nil
}
// Description returns a description of the mode of this call suitable
// for a user interface, e.g., "static method call".
func (c *CallCommon) Description() string {
switch fn := c.Value.(type) {
case *Builtin:
return "built-in function call"
case *MakeClosure:
return "static function closure call"
case *Function:
if fn.Signature.Recv() != nil {
return "static method call"
}
return "static function call"
}
if c.IsInvoke() {
return "dynamic method call" // ("invoke" mode)
}
return "dynamic function call"
}
// The CallInstruction interface, implemented by *Go, *Defer and *Call,
// exposes the common parts of function-calling instructions,
// yet provides a way back to the Value defined by *Call alone.
type CallInstruction interface {
Instruction
Common() *CallCommon // returns the common parts of the call
Value() *Call
}
func (s *Call) Common() *CallCommon { return &s.Call }
func (s *Defer) Common() *CallCommon { return &s.Call }
func (s *Go) Common() *CallCommon { return &s.Call }
func (s *Call) Value() *Call { return s }
func (s *Defer) Value() *Call { return nil }
func (s *Go) Value() *Call { return nil }
func (v *Builtin) Type() types.Type { return v.sig }
func (v *Builtin) Name() string { return v.name }
func (*Builtin) Referrers() *[]Instruction { return nil }
func (v *Builtin) Pos() token.Pos { return token.NoPos }
func (v *Builtin) Object() types.Object { return types.Universe.Lookup(v.name) }
func (v *Builtin) Parent() *Function { return nil }
func (v *FreeVar) Type() types.Type { return v.typ }
func (v *FreeVar) Name() string { return v.name }
func (v *FreeVar) Referrers() *[]Instruction { return &v.referrers }
func (v *FreeVar) Parent() *Function { return v.parent }
func (v *Global) Type() types.Type { return v.typ }
func (v *Global) Name() string { return v.name }
func (v *Global) Parent() *Function { return nil }
func (v *Global) Referrers() *[]Instruction { return nil }
func (v *Global) Token() token.Token { return token.VAR }
func (v *Global) Object() types.Object { return v.object }
func (v *Global) String() string { return v.RelString(nil) }
func (v *Global) Package() *Package { return v.Pkg }
func (v *Global) RelString(from *types.Package) string { return relString(v, from) }
func (v *Function) Name() string { return v.name }
func (v *Function) Type() types.Type { return v.Signature }
func (v *Function) Token() token.Token { return token.FUNC }
func (v *Function) Object() types.Object {
if v.object != nil {
return types.Object(v.object)
}
return nil
}
func (v *Function) String() string { return v.RelString(nil) }
func (v *Function) Package() *Package { return v.Pkg }
func (v *Function) Parent() *Function { return v.parent }
func (v *Function) Referrers() *[]Instruction {
if v.parent != nil {
return &v.referrers
}
return nil
}
func (v *Parameter) Object() types.Object { return v.object }
func (v *Alloc) Type() types.Type { return v.typ }
func (v *Alloc) Referrers() *[]Instruction { return &v.referrers }
func (v *register) Type() types.Type { return v.typ }
func (v *register) setType(typ types.Type) { v.typ = typ }
func (v *register) Name() string { return fmt.Sprintf("t%d", v.id) }
func (v *register) Referrers() *[]Instruction { return &v.referrers }
func (v *anInstruction) Parent() *Function { return v.block.parent }
func (v *anInstruction) Block() *BasicBlock { return v.block }
func (v *anInstruction) setBlock(block *BasicBlock) { v.block = block }
func (v *anInstruction) Referrers() *[]Instruction { return nil }
func (t *Type) Name() string { return t.object.Name() }
func (t *Type) Pos() token.Pos { return t.object.Pos() }
func (t *Type) Type() types.Type { return t.object.Type() }
func (t *Type) Token() token.Token { return token.TYPE }
func (t *Type) Object() types.Object { return t.object }
func (t *Type) String() string { return t.RelString(nil) }
func (t *Type) Package() *Package { return t.pkg }
func (t *Type) RelString(from *types.Package) string { return relString(t, from) }
func (c *NamedConst) Name() string { return c.object.Name() }
func (c *NamedConst) Pos() token.Pos { return c.object.Pos() }
func (c *NamedConst) String() string { return c.RelString(nil) }
func (c *NamedConst) Type() types.Type { return c.object.Type() }
func (c *NamedConst) Token() token.Token { return token.CONST }
func (c *NamedConst) Object() types.Object { return c.object }
func (c *NamedConst) Package() *Package { return c.pkg }
func (c *NamedConst) RelString(from *types.Package) string { return relString(c, from) }
// Func returns the package-level function of the specified name,
// or nil if not found.
func (p *Package) Func(name string) (f *Function) {
f, _ = p.Members[name].(*Function)
return
}
// Var returns the package-level variable of the specified name,
// or nil if not found.
func (p *Package) Var(name string) (g *Global) {
g, _ = p.Members[name].(*Global)
return
}
// Const returns the package-level constant of the specified name,
// or nil if not found.
func (p *Package) Const(name string) (c *NamedConst) {
c, _ = p.Members[name].(*NamedConst)
return
}
// Type returns the package-level type of the specified name,
// or nil if not found.
func (p *Package) Type(name string) (t *Type) {
t, _ = p.Members[name].(*Type)
return
}
func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() }
// Operands.
func (v *Alloc) Operands(rands []*Value) []*Value {
return rands
}
func (v *BinOp) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Y)
}
func (c *CallCommon) Operands(rands []*Value) []*Value {
rands = append(rands, &c.Value)
for i := range c.Args {
rands = append(rands, &c.Args[i])
}
return rands
}
func (s *Go) Operands(rands []*Value) []*Value {
return s.Call.Operands(rands)
}
func (s *Call) Operands(rands []*Value) []*Value {
return s.Call.Operands(rands)
}
func (s *Defer) Operands(rands []*Value) []*Value {
return append(s.Call.Operands(rands), &s._DeferStack)
}
func (v *ChangeInterface) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *ChangeType) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *Convert) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *MultiConvert) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *SliceToArrayPointer) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *SliceToArray) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (s *DebugRef) Operands(rands []*Value) []*Value {
return append(rands, &s.X)
}
func (s *Copy) Operands(rands []*Value) []*Value {
return append(rands, &s.X)
}
func (v *Extract) Operands(rands []*Value) []*Value {
return append(rands, &v.Tuple)
}
func (v *Field) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *FieldAddr) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (s *If) Operands(rands []*Value) []*Value {
return append(rands, &s.Cond)
}
func (s *ConstantSwitch) Operands(rands []*Value) []*Value {
rands = append(rands, &s.Tag)
for i := range s.Conds {
rands = append(rands, &s.Conds[i])
}
return rands
}
func (s *TypeSwitch) Operands(rands []*Value) []*Value {
rands = append(rands, &s.Tag)
return rands
}
func (v *Index) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Index)
}
func (v *IndexAddr) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Index)
}
func (*Jump) Operands(rands []*Value) []*Value {
return rands
}
func (*Unreachable) Operands(rands []*Value) []*Value {
return rands
}
func (v *MapLookup) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Index)
}
func (v *StringLookup) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Index)
}
func (v *MakeChan) Operands(rands []*Value) []*Value {
return append(rands, &v.Size)
}
func (v *MakeClosure) Operands(rands []*Value) []*Value {
rands = append(rands, &v.Fn)
for i := range v.Bindings {
rands = append(rands, &v.Bindings[i])
}
return rands
}
func (v *MakeInterface) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *MakeMap) Operands(rands []*Value) []*Value {
return append(rands, &v.Reserve)
}
func (v *MakeSlice) Operands(rands []*Value) []*Value {
return append(rands, &v.Len, &v.Cap)
}
func (v *MapUpdate) Operands(rands []*Value) []*Value {
return append(rands, &v.Map, &v.Key, &v.Value)
}
func (v *Next) Operands(rands []*Value) []*Value {
return append(rands, &v.Iter)
}
func (s *Panic) Operands(rands []*Value) []*Value {
return append(rands, &s.X)
}
func (v *Sigma) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *Phi) Operands(rands []*Value) []*Value {
for i := range v.Edges {
rands = append(rands, &v.Edges[i])
}
return rands
}
func (v *Range) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (s *Return) Operands(rands []*Value) []*Value {
for i := range s.Results {
rands = append(rands, &s.Results[i])
}
return rands
}
func (*RunDefers) Operands(rands []*Value) []*Value {
return rands
}
func (v *Select) Operands(rands []*Value) []*Value {
for i := range v.States {
rands = append(rands, &v.States[i].Chan, &v.States[i].Send)
}
return rands
}
func (s *Send) Operands(rands []*Value) []*Value {
return append(rands, &s.Chan, &s.X)
}
func (recv *Recv) Operands(rands []*Value) []*Value {
return append(rands, &recv.Chan)
}
func (v *Slice) Operands(rands []*Value) []*Value {
return append(rands, &v.X, &v.Low, &v.High, &v.Max)
}
func (s *Store) Operands(rands []*Value) []*Value {
return append(rands, &s.Addr, &s.Val)
}
func (s *BlankStore) Operands(rands []*Value) []*Value {
return append(rands, &s.Val)
}
func (v *TypeAssert) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *UnOp) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *Load) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (v *AggregateConst) Operands(rands []*Value) []*Value {
for i := range v.Values {
rands = append(rands, &v.Values[i])
}
return rands
}
func (v *CompositeValue) Operands(rands []*Value) []*Value {
for i := range v.Values {
rands = append(rands, &v.Values[i])
}
return rands
}
// Non-Instruction Values:
func (v *Builtin) Operands(rands []*Value) []*Value { return rands }
func (v *FreeVar) Operands(rands []*Value) []*Value { return rands }
func (v *Const) Operands(rands []*Value) []*Value { return rands }
func (v *ArrayConst) Operands(rands []*Value) []*Value { return rands }
func (v *GenericConst) Operands(rands []*Value) []*Value { return rands }
func (v *Function) Operands(rands []*Value) []*Value { return rands }
func (v *Global) Operands(rands []*Value) []*Value { return rands }
func (v *Parameter) Operands(rands []*Value) []*Value { return rands }
================================================
FILE: go/ir/stdlib_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
// Incomplete source tree on Android.
//go:build !android
package ir_test
// This file runs the IR builder in sanity-checking mode on all
// packages beneath $GOROOT and prints some summary information.
//
// Run with "go test -cpu=8 to" set GOMAXPROCS.
import (
"testing"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"golang.org/x/tools/go/packages"
)
func TestStdlib(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode; too slow (golang.org/issue/14113)")
}
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes,
}
pkgs, err := packages.Load(cfg, "std")
if err != nil {
t.Fatalf("Load failed: %v", err)
}
for _, pkg := range pkgs {
if len(pkg.Errors) != 0 {
t.Fatalf("Load failed: %v", pkg.Errors[0])
}
}
var mode ir.BuilderMode
mode |= ir.SanityCheckFunctions
mode |= ir.GlobalDebug
prog, _ := irutil.Packages(pkgs, mode, nil)
prog.Build()
}
================================================
FILE: go/ir/testdata/objlookup.go
================================================
//go:build ignore
// +build ignore
package main
// This file is the input to TestObjValueLookup in source_test.go,
// which ensures that each occurrence of an ident defining or
// referring to a func, var or const object can be mapped to its
// corresponding IR Value.
//
// For every reference to a var object, we use annotations in comments
// to denote both the expected IR Value kind, and whether to expect
// its value (x) or its address (&x).
//
// For const and func objects, the results don't vary by reference and
// are always values not addresses, so no annotations are needed. The
// declaration is enough.
import (
"fmt"
"os"
)
type J int
func (*J) method() {}
const globalConst = 0
var globalVar int //@ ir(globalVar,"&Global")
func globalFunc() {}
type I interface {
interfaceMethod()
}
type S struct {
x int //@ ir(x,"nil")
}
func main() {
print(globalVar) //@ ir(globalVar,"Load")
globalVar = 1 //@ ir(globalVar,"Const")
var v0 int = 1 //@ ir(v0,"Const") // simple local value spec
if v0 > 0 { //@ ir(v0,"Const")
v0 = 2 //@ ir(v0,"Const")
}
print(v0) //@ ir(v0,"Phi")
// v1 is captured and thus implicitly address-taken.
var v1 int = 1 //@ ir(v1,"Const")
v1 = 2 //@ ir(v1,"Const")
fmt.Println(v1) //@ ir(v1,"Const")
f := func(param int) { //@ ir(f,"MakeClosure"), ir(param,"Parameter")
if y := 1; y > 0 { //@ ir(y,"Const")
print(v1, param) //@ ir(v1,"Load") /*load*/, ir(param,"Sigma")
}
param = 2 //@ ir(param,"Const")
println(param) //@ ir(param,"Const")
}
fmt.Println(v1) //@ ir(v1,"Load") // load
f(0) //@ ir(f,"MakeClosure")
var v2 int //@ ir(v2,"Const") // implicitly zero-initialized local value spec
print(v2) //@ ir(v2,"Const")
m := make(map[string]int) //@ ir(m,"MakeMap")
// Local value spec with multi-valued RHS:
var v3, v4 = m[""] //@ ir(v3,"Extract"), ir(v4,"Extract"), ir(m,"MakeMap")
print(v3) //@ ir(v3,"Extract")
print(v4) //@ ir(v4,"Extract")
v3++ //@ ir(v3,"BinOp") // assign with op
v3 += 2 //@ ir(v3,"BinOp") // assign with op
v5, v6 := false, "" //@ ir(v5,"Const"), ir(v6,"Const") // defining assignment
print(v5) //@ ir(v5,"Const")
print(v6) //@ ir(v6,"Const")
var v7 S //@ ir(v7,"&Alloc")
v7.x = 1 //@ ir(v7,"&Alloc"), ir(x,"&FieldAddr")
print(v7.x) //@ ir(v7,"&Alloc"), ir(x,"&FieldAddr")
var v8 [1]int //@ ir(v8,"&Alloc")
v8[0] = 0 //@ ir(v8,"&Alloc")
print(v8[:]) //@ ir(v8,"&Alloc")
_ = v8[0] //@ ir(v8,"&Alloc")
_ = v8[:][0] //@ ir(v8,"&Alloc")
v8ptr := &v8 //@ ir(v8ptr,"Alloc"), ir(v8,"&Alloc")
_ = v8ptr[0] //@ ir(v8ptr,"Alloc")
_ = *v8ptr //@ ir(v8ptr,"Alloc")
v8a := make([]int, 1) //@ ir(v8a,"Slice")
v8a[0] = 0 //@ ir(v8a,"Slice")
print(v8a[:]) //@ ir(v8a,"Slice")
v9 := S{} //@ ir(v9,"&Alloc")
v10 := &v9 //@ ir(v10,"Alloc"), ir(v9,"&Alloc")
_ = v10 //@ ir(v10,"Alloc")
var v11 *J = nil //@ ir(v11,"Const")
v11.method() //@ ir(v11,"Const")
var v12 J //@ ir(v12,"&Alloc")
v12.method() //@ ir(v12,"&Alloc") // implicitly address-taken
// NB, in the following, 'method' resolves to the *types.Func
// of (*J).method, so it doesn't help us locate the specific
// ir.Values here: a bound-method closure and a promotion
// wrapper.
_ = v11.method //@ ir(v11,"Const")
_ = (*struct{ J }).method //@ ir(J,"nil")
// These vars are not optimised away.
if false {
v13 := 0 //@ ir(v13,"Const")
println(v13) //@ ir(v13,"Const")
}
switch x := 1; x { //@ ir(x,"Const")
case v0: //@ ir(v0,"Phi")
}
for k, v := range m { //@ ir(k,"Extract"), ir(v,"Extract"), ir(m,"MakeMap")
_ = k //@ ir(k,"Extract")
v++ //@ ir(v,"BinOp")
}
if y := 0; y > 1 { //@ ir(y,"Const"), ir(y,"Const")
}
var i interface{} //@ ir(i,"Const") // nil interface
i = 1 //@ ir(i,"MakeInterface")
switch i := i.(type) { //@ ir(i,"MakeInterface"), ir(i,"MakeInterface")
case int:
println(i) //@ ir(i,"Extract")
}
ch := make(chan int) //@ ir(ch,"MakeChan")
select {
case x := <-ch: //@ ir(x,"Recv") /*receive*/, ir(ch,"MakeChan")
_ = x //@ ir(x,"Recv")
}
// .Op is an inter-package FieldVal-selection.
var err os.PathError //@ ir(err,"&Alloc")
_ = err.Op //@ ir(err,"&Alloc"), ir(Op,"&FieldAddr")
_ = &err.Op //@ ir(err,"&Alloc"), ir(Op,"&FieldAddr")
// Exercise corner-cases of lvalues vs rvalues.
// (Guessing IsAddr from the 'pointerness' won't cut it here.)
type N *N
var n N //@ ir(n,"Const")
n1 := n //@ ir(n1,"Const"), ir(n,"Const")
_ = &n1 //@ ir(n1,"&Alloc") // make n1 escape right away, else our lifting is too good
n2 := &n1 //@ ir(n2,"Alloc"), ir(n1,"&Alloc")
n3 := *n2 //@ ir(n3,"Load"), ir(n2,"Alloc")
n4 := **n3 //@ ir(n4,"Load"), ir(n3,"Load")
_ = n4 //@ ir(n4,"Load")
}
================================================
FILE: go/ir/testdata/valueforexpr.go
================================================
//go:build ignore
// +build ignore
package main
// This file is the input to TestValueForExpr in source_test.go, which
// ensures that each expression e immediately following a /*@kind*/(x)
// annotation, when passed to Function.ValueForExpr(e), returns a
// non-nil Value of the same type as e and of kind 'kind'.
func f(spilled, unspilled int) {
_ = /*@Parameter*/ (spilled)
_ = /*@Parameter*/ (unspilled)
_ = /*@nil*/ (1 + 2) // (constant)
i := 0
f := func() (int, int) { return 0, 0 }
(print( /*@BinOp*/ (i + 1)))
_, _ = /*@Call*/ (f())
ch := /*@MakeChan*/ (make(chan int))
/*@Recv*/ (<-ch)
x := /*@Recv*/ (<-ch)
_ = x
select {
case /*@Extract*/ (<-ch):
case x := /*@Extract*/ (<-ch):
_ = x
}
defer /*@Function*/ (func() {
})()
go /*@Function*/ (func() {
})()
y := 0
if true && /*@BinOp*/ (bool(y > 0)) {
y = 1
}
_ = /*@Phi*/ (y)
map1 := /*@MakeMap*/ (make(map[string]string))
_ = map1
_ = /*@Slice*/ (make([]int, 0))
_ = /*@MakeClosure*/ (func() { print(spilled) })
_ = /*@Load*/ (spilled)
sl := []int{}
_ = /*@Slice*/ (sl[:0])
_ = /*@Alloc*/ (new(int))
tmp := /*@Alloc*/ (new(int))
_ = tmp
var iface interface{}
_ = /*@TypeAssert*/ (iface.(int))
_ = /*@Load*/ (sl[0])
_ = /*@IndexAddr*/ (&sl[0])
_ = /*@Index*/ ([2]int{}[0])
var p *int
_ = /*@Load*/ (*p)
_ = /*@Load*/ (global)
/*@Load*/ (global)[""] = ""
/*@Global*/ (global) = map[string]string{}
var local t
/*UnOp*/ (local.x) = 1
// Exercise corner-cases of lvalues vs rvalues.
type N *N
var n N
/*@Const*/ (n) = /*@Const*/ (n)
/*@ChangeType*/ (n) = /*@Alloc*/ (&n)
/*@Load*/ (n) = /*@Load*/ (n)
/*@Load*/ (n) = /*@Load*/ (*n)
/*@Load*/ (n) = /*@Load*/ (**n)
}
func complit() {
// Composite literals.
// We get different results for
// - composite literal as value (e.g. operand to print)
// - composite literal initializer for addressable value
// - composite literal value assigned to blank var
// 1. Slices
print( /*@Slice*/ ([]int{}))
print( /*@Alloc*/ (&[]int{}))
print(& /*@Slice*/ ([]int{}))
sl1 := /*@Slice*/ ([]int{})
sl2 := /*@Alloc*/ (&[]int{})
sl3 := & /*@Slice*/ ([]int{})
_, _, _ = sl1, sl2, sl3
_ = /*@Slice*/ ([]int{})
_ = /*@Alloc*/ (& /*@Slice*/ ([]int{}))
_ = & /*@Slice*/ ([]int{})
// 2. Arrays
print( /*@ArrayConst*/ ([1]int{}))
print( /*@Alloc*/ (&[1]int{}))
print(& /*@Alloc*/ ([1]int{}))
arr1 := /*@ArrayConst*/ ([1]int{})
arr2 := /*@Alloc*/ (&[1]int{})
arr3 := & /*@Alloc*/ ([1]int{})
_, _, _ = arr1, arr2, arr3
_ = /*@ArrayConst*/ ([1]int{})
_ = /*@Alloc*/ (& /*@Alloc*/ ([1]int{}))
_ = & /*@Alloc*/ ([1]int{})
// 3. Maps
type M map[int]int
print( /*@MakeMap*/ (M{}))
print( /*@Alloc*/ (&M{}))
print(& /*@MakeMap*/ (M{}))
m1 := /*@MakeMap*/ (M{})
m2 := /*@Alloc*/ (&M{})
m3 := & /*@MakeMap*/ (M{})
_, _, _ = m1, m2, m3
_ = /*@MakeMap*/ (M{})
_ = /*@Alloc*/ (& /*@MakeMap*/ (M{}))
_ = & /*@MakeMap*/ (M{})
// 4. Structs
print( /*@AggregateConst*/ (struct{}{}))
print( /*@Alloc*/ (&struct{}{}))
print(& /*@Alloc*/ (struct{}{}))
s1 := /*@AggregateConst*/ (struct{}{})
s2 := /*@Alloc*/ (&struct{}{})
s3 := & /*@Alloc*/ (struct{}{})
_, _, _ = s1, s2, s3
_ = /*@AggregateConst*/ (struct{}{})
_ = /*@Alloc*/ (& /*@Alloc*/ (struct{}{}))
_ = & /*@Alloc*/ (struct{}{})
}
type t struct{ x int }
// Ensure we can locate methods of named types.
func (t) f(param int) {
_ = /*@Parameter*/ (param)
}
// Ensure we can locate init functions.
func init() {
m := /*@MakeMap*/ (make(map[string]string))
_ = m
}
// Ensure we can locate variables in initializer expressions.
var global = /*@MakeMap*/ (make(map[string]string))
type t1 struct {
x int
}
type t2 struct {
x int `tag`
}
func main() {
var tv1 t1
var tv2 t2 = /*@ChangeType*/ (t2(tv1))
_ = tv2
}
================================================
FILE: go/ir/util.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines a number of miscellaneous utility functions.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"io"
"os"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/exp/typeparams"
)
//// AST utilities
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
// isBlankIdent returns true iff e is an Ident with name "_".
// They have no associated types.Object, and thus no type.
func isBlankIdent(e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && id.Name == "_"
}
//// Type utilities. Some of these belong in go/types.
// isPointer returns true for types whose underlying type is a pointer,
// and for type parameters whose core type is a pointer.
func isPointer(typ types.Type) bool {
if ctyp := typeutil.CoreType(typ); ctyp != nil {
_, ok := ctyp.(*types.Pointer)
return ok
}
_, ok := typ.Underlying().(*types.Pointer)
return ok
}
// deref returns a pointer's element type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
orig := typ
typ = types.Unalias(typ)
if t, ok := typ.(*types.TypeParam); ok {
if ctyp := typeutil.CoreType(t); ctyp != nil {
// This can happen, for example, with len(T) where T is a
// type parameter whose core type is a pointer to array.
typ = ctyp
}
}
if p, ok := typ.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return orig
}
// recvType returns the receiver type of method obj.
func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
}
// logStack prints the formatted "start" message to stderr and
// returns a closure that prints the corresponding "end" message.
// Call using 'defer logStack(...)()' to show builder stack on panic.
// Don't forget trailing parens!
func logStack(format string, args ...any) func() {
msg := fmt.Sprintf(format, args...)
io.WriteString(os.Stderr, msg)
io.WriteString(os.Stderr, "\n")
return func() {
io.WriteString(os.Stderr, msg)
io.WriteString(os.Stderr, " end\n")
}
}
// newVar creates a 'var' for use in a types.Tuple.
func newVar(name string, typ types.Type) *types.Var {
return types.NewParam(token.NoPos, nil, name, typ)
}
// anonVar creates an anonymous 'var' for use in a types.Tuple.
func anonVar(typ types.Type) *types.Var {
return newVar("", typ)
}
var lenResults = types.NewTuple(anonVar(tInt))
// makeLen returns the len builtin specialized to type func(T)int.
func makeLen(T types.Type) *Builtin {
lenParams := types.NewTuple(anonVar(T))
return &Builtin{
name: "len",
sig: types.NewSignatureType(nil, nil, nil, lenParams, lenResults, false),
}
}
type StackMap struct {
m []map[Value]Value
}
func (m *StackMap) Push() {
m.m = append(m.m, map[Value]Value{})
}
func (m *StackMap) Pop() {
m.m = m.m[:len(m.m)-1]
}
func (m *StackMap) Get(key Value) (Value, bool) {
for i := len(m.m) - 1; i >= 0; i-- {
if v, ok := m.m[i][key]; ok {
return v, true
}
}
return nil, false
}
func (m *StackMap) Set(k Value, v Value) {
m.m[len(m.m)-1][k] = v
}
// Unwrap recursively unwraps Sigma and Copy nodes.
func Unwrap(v Value) Value {
for {
switch vv := v.(type) {
case *Sigma:
v = vv.X
case *Copy:
v = vv.X
default:
return v
}
}
}
func assert(x bool) {
if !x {
panic("failed assertion")
}
}
// BlockMap is a mapping from basic blocks (identified by their indices) to values.
type BlockMap[T any] []T
// isBasic reports whether t is a basic type.
func isBasic(t types.Type) bool {
_, ok := t.(*types.Basic)
return ok
}
// isNonTypeParamInterface reports whether t is an interface type but not a type parameter.
func isNonTypeParamInterface(t types.Type) bool {
return !typeparams.IsTypeParam(t) && types.IsInterface(t)
}
================================================
FILE: go/ir/wrappers.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ir
// This file defines synthesis of Functions that delegate to declared
// methods; they come in three kinds:
//
// (1) wrappers: methods that wrap declared methods, performing
// implicit pointer indirections and embedded field selections.
//
// (2) thunks: funcs that wrap declared methods. Like wrappers,
// thunks perform indirections and field selections. The thunk's
// first parameter is used as the receiver for the method call.
//
// (3) bounds: funcs that wrap declared methods. The bound's sole
// free variable, supplied by a closure, is used as the receiver
// for the method call. No indirections or field selections are
// performed since they can be done before the call.
import (
"fmt"
"go/types"
)
// -- wrappers -----------------------------------------------------------
// makeWrapper returns a synthetic method that delegates to the
// declared method denoted by meth.Obj(), first performing any
// necessary pointer indirections or field selections implied by meth.
//
// The resulting method's receiver type is meth.Recv().
//
// This function is versatile but quite subtle! Consider the
// following axes of variation when making changes:
// - optional receiver indirection
// - optional implicit field selections
// - meth.Obj() may denote a concrete or an interface method
// - the result may be a thunk or a wrapper.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func makeWrapper(prog *Program, sel *types.Selection) *Function {
obj := sel.Obj().(*types.Func) // the declared function
sig := sel.Type().(*types.Signature) // type of this wrapper
var recv *types.Var // wrapper's receiver or thunk's params[0]
name := obj.Name()
var description Synthetic
var start int // first regular param
if sel.Kind() == types.MethodExpr {
name += "$thunk"
description = SyntheticThunk
recv = sig.Params().At(0)
start = 1
} else {
description = SyntheticWrapper
recv = sig.Recv()
}
if prog.mode&LogSource != 0 {
defer logStack("make %s to (%s)", description, recv.Type())()
}
fn := &Function{
name: name,
method: sel,
object: obj,
Signature: sig,
Synthetic: description,
Prog: prog,
functionBody: new(functionBody),
}
fn.initHTML(prog.PrintFunc)
fn.startBody()
fn.addSpilledParam(recv, nil)
createParams(fn, start)
indices := sel.Index()
var v Value = fn.Locals[0] // spilled receiver
if isPointer(sel.Recv()) {
v = emitLoad(fn, v, nil)
// For simple indirection wrappers, perform an informative nil-check:
// "value method (T).f called using nil *T pointer"
if len(indices) == 1 && !isPointer(recvType(obj)) {
var c Call
c.Call.Value = &Builtin{
name: "ir:wrapnilchk",
sig: types.NewSignatureType(nil, nil, nil,
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
types.NewTuple(anonVar(sel.Recv())), false),
}
c.Call.Args = []Value{
v,
emitConst(fn, stringConst(deref(sel.Recv()).String(), nil)),
emitConst(fn, stringConst(sel.Obj().Name(), nil)),
}
c.setType(v.Type())
v = fn.emit(&c, nil)
}
}
// Invariant: v is a pointer, either
// value of *A receiver param, or
// address of A spilled receiver.
// We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly
// preceded by Load).
v = emitImplicitSelections(fn, v, indices[:len(indices)-1], nil)
// Invariant: v is a pointer, either
// value of implicit *C field, or
// address of implicit C field.
var c Call
if r := recvType(obj); !types.IsInterface(r) { // concrete method
if !isPointer(r) {
v = emitLoad(fn, v, nil)
}
c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Method = obj
c.Call.Value = emitLoad(fn, v, nil)
}
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c, nil)
fn.finishBody()
return fn
}
// createParams creates parameters for wrapper method fn based on its
// Signature.Params, which do not include the receiver.
// start is the index of the first regular parameter to use.
func createParams(fn *Function, start int) {
tparams := fn.Signature.Params()
for i, n := start, tparams.Len(); i < n; i++ {
fn.addParamVar(tparams.At(i), nil)
}
}
// -- bounds -----------------------------------------------------------
// makeBound returns a bound method wrapper (or "bound"), a synthetic
// function that delegates to a concrete or interface method denoted
// by obj. The resulting function has no receiver, but has one free
// variable which will be used as the method's receiver in the
// tail-call.
//
// Use MakeClosure with such a wrapper to construct a bound method
// closure. e.g.:
//
// type T int or: type T interface { meth() }
// func (t T) meth()
// var t T
// f := t.meth
// f() // calls t.meth()
//
// f is a closure of a synthetic wrapper defined as if by:
//
// f := func() { return t.meth() }
//
// Unlike makeWrapper, makeBound need perform no indirection or field
// selections because that can be done before the closure is
// constructed.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
func makeBound(prog *Program, obj *types.Func) *Function {
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
if prog.mode&LogSource != 0 {
defer logStack("%s", SyntheticBound)()
}
fn := &Function{
name: obj.Name() + "$bound",
object: obj,
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
Synthetic: SyntheticBound,
Prog: prog,
functionBody: new(functionBody),
}
fn.initHTML(prog.PrintFunc)
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
fn.FreeVars = []*FreeVar{fv}
fn.startBody()
createParams(fn, 0)
var c Call
if !types.IsInterface(recvType(obj)) { // concrete
c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = []Value{fv}
} else {
c.Call.Value = fv
c.Call.Method = obj
}
for _, arg := range fn.Params {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c, nil)
fn.finishBody()
return fn
}
// -- thunks -----------------------------------------------------------
// makeThunk returns a thunk, a synthetic function that delegates to a
// concrete or interface method denoted by sel.Obj(). The resulting
// function has no receiver, but has an additional (first) regular
// parameter.
//
// Precondition: sel.Kind() == types.MethodExpr.
//
// type T int or: type T interface { meth() }
// func (t T) meth()
// f := T.meth
// var t T
// f(t) // calls t.meth()
//
// f is a synthetic wrapper defined as if by:
//
// f := func(t T) { return t.meth() }
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
func makeThunk(prog *Program, sel *types.Selection) *Function {
if sel.Kind() != types.MethodExpr {
panic(sel)
}
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
fn := makeWrapper(prog, sel)
if fn.Signature.Recv() != nil {
panic(fn) // unexpected receiver
}
return fn
}
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic())
}
// makeInstance creates a wrapper function with signature sig that calls the generic function fn.
// If targs is not nil, fn is a function and targs describes the concrete type arguments.
// If targs is nil, fn is a method and the type arguments are derived from the receiver.
func makeInstance(prog *Program, fn *Function, sig *types.Signature, targs *types.TypeList) *Function {
if sig.Recv() != nil {
assert(targs == nil)
// Methods don't have their own type parameters, but the receiver does
targs = types.Unalias(deref(sig.Recv().Type())).(*types.Named).TypeArgs()
} else {
assert(targs != nil)
}
wrapper := fn.generics.At(targs)
if wrapper != nil {
return wrapper
}
var name string
if sig.Recv() != nil {
name = fn.name
} else {
name = fmt.Sprintf("%s$generic#%d", fn.name, fn.generics.Len())
}
w := &Function{
name: name,
object: fn.object,
Signature: sig,
Synthetic: SyntheticGeneric,
Prog: prog,
functionBody: new(functionBody),
}
w.initHTML(prog.PrintFunc)
w.startBody()
if sig.Recv() != nil {
w.addParamVar(sig.Recv(), nil)
}
createParams(w, 0)
var c Call
c.Call.Value = fn
tresults := fn.Signature.Results()
if tresults.Len() == 1 {
c.typ = tresults.At(0).Type()
} else {
c.typ = tresults
}
changeType := func(v Value, typ types.Type) Value {
if types.Identical(v.Type(), typ) {
return v
}
var c ChangeType
c.X = v
c.typ = typ
return w.emit(&c, nil)
}
for i, arg := range w.Params {
if sig.Recv() != nil {
if i == 0 {
c.Call.Args = append(c.Call.Args, changeType(w.Params[0], fn.Signature.Recv().Type()))
} else {
c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i-1).Type()))
}
} else {
c.Call.Args = append(c.Call.Args, changeType(arg, fn.Signature.Params().At(i).Type()))
}
}
for arg := range targs.Types() {
c.Call.TypeArgs = append(c.Call.TypeArgs, arg)
}
results := w.emit(&c, nil)
var ret Return
switch tresults.Len() {
case 0:
case 1:
ret.Results = []Value{changeType(results, sig.Results().At(0).Type())}
default:
for i := 0; i < tresults.Len(); i++ {
v := emitExtract(w, results, i, nil)
ret.Results = append(ret.Results, changeType(v, sig.Results().At(i).Type()))
}
}
w.Exit = w.newBasicBlock("exit")
emitJump(w, w.Exit, nil)
w.currentBlock = w.Exit
w.emit(&ret, nil)
w.currentBlock = nil
w.finishBody()
fn.generics.Set(targs, w)
return w
}
================================================
FILE: go/ir/write.go
================================================
package ir
func NewJump(parent *BasicBlock) *Jump {
return &Jump{anInstruction{block: parent}}
}
================================================
FILE: go/loader/hash.go
================================================
package loader
import (
"fmt"
"runtime"
"sort"
"strings"
"honnef.co/go/tools/go/buildid"
"honnef.co/go/tools/lintcmd/cache"
)
// computeHash computes a package's hash. The hash is based on all Go
// files that make up the package, as well as the hashes of imported
// packages.
func computeHash(c *cache.Cache, pkg *PackageSpec) (cache.ActionID, error) {
key := c.NewHash("package " + pkg.PkgPath)
fmt.Fprintf(key, "goos %s goarch %s\n", runtime.GOOS, runtime.GOARCH)
fmt.Fprintf(key, "import %q\n", pkg.PkgPath)
// Compute the hashes of all files making up the package. As an
// optimization, we use the build ID that Go already computed for us,
// because it is virtually identical to hashing all CompiledGoFiles. It is
// also sensitive to the Go version declared in go.mod.
success := false
if pkg.ExportFile != "" {
id, err := getBuildid(pkg.ExportFile)
if err == nil {
if idx := strings.IndexRune(id, '/'); idx > -1 {
fmt.Fprintf(key, "files %s\n", id[:idx])
success = true
}
}
}
if !success {
for _, f := range pkg.CompiledGoFiles {
h, err := cache.FileHash(f)
if err != nil {
return cache.ActionID{}, err
}
fmt.Fprintf(key, "file %s %x\n", f, h)
}
if pkg.Module != nil && pkg.Module.GoMod != "" {
// The go.mod file specifies the language version, which affects how
// packages are analyzed.
h, err := cache.FileHash(pkg.Module.GoMod)
if err != nil {
// TODO(dh): this doesn't work for tests because the go.mod file doesn't
// exist on disk and is instead provided via an overlay. However, we're
// unlikely to get here in the first place, as reading the build ID from
// the export file is likely to succeed.
return cache.ActionID{}, fmt.Errorf("couldn't hash go.mod: %w", err)
} else {
fmt.Fprintf(key, "file %s %x\n", pkg.Module.GoMod, h)
}
}
}
imps := make([]*PackageSpec, 0, len(pkg.Imports))
for _, v := range pkg.Imports {
imps = append(imps, v)
}
sort.Slice(imps, func(i, j int) bool {
return imps[i].PkgPath < imps[j].PkgPath
})
for _, dep := range imps {
if dep.ExportFile == "" {
fmt.Fprintf(key, "import %s \n", dep.PkgPath)
} else {
id, err := getBuildid(dep.ExportFile)
if err == nil {
fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, id)
} else {
fh, err := cache.FileHash(dep.ExportFile)
if err != nil {
return cache.ActionID{}, err
}
fmt.Fprintf(key, "import %s %x\n", dep.PkgPath, fh)
}
}
}
return key.Sum(), nil
}
var buildidCache = map[string]string{}
func getBuildid(f string) (string, error) {
if h, ok := buildidCache[f]; ok {
return h, nil
}
h, err := buildid.ReadFile(f)
if err != nil {
return "", err
}
buildidCache[f] = h
return h, nil
}
================================================
FILE: go/loader/loader.go
================================================
package loader
import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"os"
"time"
"honnef.co/go/tools/config"
"honnef.co/go/tools/lintcmd/cache"
"golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/go/packages"
)
const MaxFileSize = 50 * 1024 * 1024 // 50 MB
var errMaxFileSize = errors.New("file exceeds max file size")
type PackageSpec struct {
ID string
Name string
PkgPath string
// Errors that occurred while building the import graph. These will
// primarily be parse errors or failure to resolve imports, but
// may also be other errors.
Errors []packages.Error
GoFiles []string
CompiledGoFiles []string
OtherFiles []string
ExportFile string
Imports map[string]*PackageSpec
TypesSizes types.Sizes
Hash cache.ActionID
Module *packages.Module
Config config.Config
}
func (spec *PackageSpec) String() string {
return spec.ID
}
type Package struct {
*PackageSpec
// Errors that occurred while loading the package. These will
// primarily be parse or type errors, but may also be lower-level
// failures such as file-system ones.
Errors []packages.Error
Types *types.Package
Fset *token.FileSet
Syntax []*ast.File
TypesInfo *types.Info
}
// Graph resolves patterns and returns packages with all the
// information required to later load type information, and optionally
// syntax trees.
//
// The provided config can set any setting with the exception of Mode.
func Graph(c *cache.Cache, cfg *packages.Config, patterns ...string) ([]*PackageSpec, error) {
var dcfg packages.Config
if cfg != nil {
dcfg = *cfg
}
dcfg.Mode = packages.NeedName |
packages.NeedImports |
packages.NeedDeps |
packages.NeedExportFile |
packages.NeedFiles |
packages.NeedCompiledGoFiles |
packages.NeedTypesSizes |
packages.NeedModule
pkgs, err := packages.Load(&dcfg, patterns...)
if err != nil {
return nil, err
}
m := map[*packages.Package]*PackageSpec{}
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
spec := &PackageSpec{
ID: pkg.ID,
Name: pkg.Name,
PkgPath: pkg.PkgPath,
Errors: pkg.Errors,
GoFiles: pkg.GoFiles,
CompiledGoFiles: pkg.CompiledGoFiles,
OtherFiles: pkg.OtherFiles,
ExportFile: pkg.ExportFile,
Imports: map[string]*PackageSpec{},
TypesSizes: pkg.TypesSizes,
Module: pkg.Module,
}
for path, imp := range pkg.Imports {
spec.Imports[path] = m[imp]
}
if cdir := config.Dir(pkg.GoFiles); cdir != "" {
cfg, err := config.Load(cdir)
if err != nil {
spec.Errors = append(spec.Errors, convertError(err)...)
}
spec.Config = cfg
} else {
spec.Config = config.DefaultConfig
}
spec.Hash, err = computeHash(c, spec)
if err != nil {
spec.Errors = append(spec.Errors, convertError(err)...)
}
m[pkg] = spec
})
out := make([]*PackageSpec, 0, len(pkgs))
for _, pkg := range pkgs {
if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" {
// If a package consists only of test files, then
// go/packages incorrectly(?) returns an empty package for
// the non-test variant. Get rid of those packages. See
// #646.
//
// Do not, however, skip packages that have errors. Those,
// too, may have no files, but we want to print the
// errors.
continue
}
out = append(out, m[pkg])
}
return out, nil
}
type program struct {
fset *token.FileSet
packages map[string]*types.Package
options *Options
}
type Stats struct {
Source time.Duration
Export map[*PackageSpec]time.Duration
}
type Options struct {
// The Go language version to use for the type checker. If unset, or if set
// to "module", it will default to the Go version specified in the module;
// if there is no module, it will default to the version of Go the
// executable was built with.
GoVersion string
}
// Load loads the package described in spec. Imports will be loaded
// from export data, while the package itself will be loaded from
// source.
//
// An error will only be returned for system failures, such as failure
// to read export data from disk. Syntax and type errors, among
// others, will only populate the returned package's Errors field.
func Load(spec *PackageSpec, opts *Options) (*Package, Stats, error) {
if opts == nil {
opts = &Options{}
}
if opts.GoVersion == "" {
opts.GoVersion = "module"
}
prog := &program{
fset: token.NewFileSet(),
packages: map[string]*types.Package{},
options: opts,
}
stats := Stats{
Export: map[*PackageSpec]time.Duration{},
}
for _, imp := range spec.Imports {
if imp.PkgPath == "unsafe" {
continue
}
t := time.Now()
_, err := prog.loadFromExport(imp)
stats.Export[imp] = time.Since(t)
if err != nil {
return nil, stats, err
}
}
t := time.Now()
pkg, err := prog.loadFromSource(spec)
if err == errMaxFileSize {
pkg, err = prog.loadFromExport(spec)
}
stats.Source = time.Since(t)
return pkg, stats, err
}
// loadFromExport loads a package from export data.
func (prog *program) loadFromExport(spec *PackageSpec) (*Package, error) {
// log.Printf("Loading package %s from export", spec)
if spec.ExportFile == "" {
return nil, fmt.Errorf("no export data for %q", spec.ID)
}
f, err := os.Open(spec.ExportFile)
if err != nil {
return nil, err
}
defer f.Close()
r, err := gcexportdata.NewReader(f)
if err != nil {
return nil, err
}
tpkg, err := gcexportdata.Read(r, prog.fset, prog.packages, spec.PkgPath)
if err != nil {
return nil, err
}
pkg := &Package{
PackageSpec: spec,
Types: tpkg,
Fset: prog.fset,
}
// runtime.SetFinalizer(pkg, func(pkg *Package) {
// log.Println("Unloading package", pkg.PkgPath)
// })
return pkg, nil
}
// loadFromSource loads a package from source. All of its dependencies
// must have been loaded already.
func (prog *program) loadFromSource(spec *PackageSpec) (*Package, error) {
if len(spec.Errors) > 0 {
panic("LoadFromSource called on package with errors")
}
pkg := &Package{
PackageSpec: spec,
Types: types.NewPackage(spec.PkgPath, spec.Name),
Syntax: make([]*ast.File, len(spec.CompiledGoFiles)),
Fset: prog.fset,
TypesInfo: &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Instances: make(map[*ast.Ident]types.Instance),
FileVersions: make(map[*ast.File]string),
},
}
// runtime.SetFinalizer(pkg, func(pkg *Package) {
// log.Println("Unloading package", pkg.PkgPath)
// })
// OPT(dh): many packages have few files, much fewer than there
// are CPU cores. Additionally, parsing each individual file is
// very fast. A naive parallel implementation of this loop won't
// be faster, and tends to be slower due to extra scheduling,
// bookkeeping and potentially false sharing of cache lines.
for i, file := range spec.CompiledGoFiles {
f, err := os.Open(file)
if err != nil {
return nil, err
}
fi, err := f.Stat()
if err != nil {
return nil, err
}
if fi.Size() >= MaxFileSize {
return nil, errMaxFileSize
}
af, err := parser.ParseFile(prog.fset, file, f, parser.ParseComments|parser.SkipObjectResolution)
f.Close()
if err != nil {
pkg.Errors = append(pkg.Errors, convertError(err)...)
return pkg, nil
}
pkg.Syntax[i] = af
}
importer := func(path string) (*types.Package, error) {
if path == "unsafe" {
return types.Unsafe, nil
}
if path == "C" {
// go/packages doesn't tell us that cgo preprocessing
// failed. When we subsequently try to parse the package,
// we'll encounter the raw C import.
return nil, errors.New("cgo preprocessing failed")
}
ispecpkg := spec.Imports[path]
if ispecpkg == nil {
return nil, fmt.Errorf("trying to import %q in the context of %q returned nil PackageSpec", path, spec)
}
ipkg := prog.packages[ispecpkg.PkgPath]
if ipkg == nil {
return nil, fmt.Errorf("trying to import %q (%q) in the context of %q returned nil PackageSpec", ispecpkg.PkgPath, path, spec)
}
return ipkg, nil
}
tc := &types.Config{
Importer: importerFunc(importer),
Error: func(err error) {
pkg.Errors = append(pkg.Errors, convertError(err)...)
},
}
if prog.options.GoVersion == "module" {
if spec.Module != nil && spec.Module.GoVersion != "" {
tc.GoVersion = "go" + spec.Module.GoVersion
} else {
tags := build.Default.ReleaseTags
tc.GoVersion = tags[len(tags)-1]
}
} else {
tc.GoVersion = prog.options.GoVersion
}
// Note that the type-checker can return a non-nil error even though the Go
// compiler has already successfully built this package (which is an
// invariant of getting to this point), for example because of the Go
// version passed to the type checker.
err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
return pkg, err
}
func convertError(err error) []packages.Error {
var errs []packages.Error
// taken from go/packages
switch err := err.(type) {
case packages.Error:
// from driver
errs = append(errs, err)
case *os.PathError:
// from parser
errs = append(errs, packages.Error{
Pos: err.Path + ":1",
Msg: err.Err.Error(),
Kind: packages.ParseError,
})
case scanner.ErrorList:
// from parser
for _, err := range err {
errs = append(errs, packages.Error{
Pos: err.Pos.String(),
Msg: err.Msg,
Kind: packages.ParseError,
})
}
case types.Error:
// from type checker
errs = append(errs, packages.Error{
Pos: err.Fset.Position(err.Pos).String(),
Msg: err.Msg,
Kind: packages.TypeError,
})
case config.ParseError:
errs = append(errs, packages.Error{
Pos: fmt.Sprintf("%s:%d:%d", err.Filename, err.Position.Line, err.Position.Col),
Msg: fmt.Sprintf("%s (last key parsed: %q)", err.Message, err.LastKey),
Kind: packages.ParseError,
})
default:
errs = append(errs, packages.Error{
Pos: "-",
Msg: err.Error(),
Kind: packages.UnknownError,
})
}
return errs
}
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
================================================
FILE: go/types/typeutil/ext.go
================================================
package typeutil
import (
"fmt"
"go/types"
)
type Iterator struct {
elem types.Type
}
func (t *Iterator) Underlying() types.Type { return t }
func (t *Iterator) String() string { return fmt.Sprintf("iterator(%s)", t.elem) }
func (t *Iterator) Elem() types.Type { return t.elem }
func NewIterator(elem types.Type) *Iterator {
return &Iterator{elem: elem}
}
type DeferStack struct{}
func (t *DeferStack) Underlying() types.Type { return t }
func (t *DeferStack) String() string { return "deferStack" }
func NewDeferStack() *DeferStack {
return &DeferStack{}
}
================================================
FILE: go/types/typeutil/typeparams.go
================================================
package typeutil
import (
"errors"
"go/types"
"slices"
"golang.org/x/exp/typeparams"
)
type TypeSet struct {
Terms []*types.Term
empty bool
}
func NewTypeSet(typ types.Type) TypeSet {
terms, err := typeparams.NormalTerms(typ)
if err != nil {
if errors.Is(err, typeparams.ErrEmptyTypeSet) {
return TypeSet{nil, true}
} else {
// We couldn't determine the type set. Assume it's all types.
return TypeSet{nil, false}
}
}
return TypeSet{terms, false}
}
// CoreType returns the type set's core type, or nil if it has none.
// The function only looks at type terms and may thus return core types for some empty type sets, such as
// 'interface { map[int]string; foo() }'
func (ts TypeSet) CoreType() types.Type {
if len(ts.Terms) == 0 {
// Either the type set is empty, or it isn't constrained. Either way it doesn't have a core type.
return nil
}
typ := ts.Terms[0].Type().Underlying()
for _, term := range ts.Terms[1:] {
ut := term.Type().Underlying()
if types.Identical(typ, ut) {
continue
}
ch1, ok := typ.(*types.Chan)
if !ok {
return nil
}
ch2, ok := ut.(*types.Chan)
if !ok {
return nil
}
if ch1.Dir() == types.SendRecv {
// typ is currently a bidirectional channel. The term's type is either also bidirectional, or
// unidirectional. Use the term's type.
typ = ut
} else if ch2.Dir() == types.SendRecv {
// typ is currently a unidirectional channel and the term's type is bidirectional, which means it has no
// effect.
continue
} else if ch1.Dir() != ch2.Dir() {
// typ is not bidirectional and typ and term disagree about the direction
return nil
}
}
return typ
}
// CoreType is a wrapper for NewTypeSet(typ).CoreType()
func CoreType(typ types.Type) types.Type {
return NewTypeSet(typ).CoreType()
}
// All calls fn for each term in the type set and reports whether all invocations returned true.
// If the type set is empty or unconstrained, All immediately returns false.
func (ts TypeSet) All(fn func(*types.Term) bool) bool {
if len(ts.Terms) == 0 {
return false
}
for _, term := range ts.Terms {
if !fn(term) {
return false
}
}
return true
}
// Any calls fn for each term in the type set and reports whether any invocation returned true.
// It stops after the first call that returned true.
func (ts TypeSet) Any(fn func(*types.Term) bool) bool {
return slices.ContainsFunc(ts.Terms, fn)
}
// All is a wrapper for NewTypeSet(typ).All(fn).
func All(typ types.Type, fn func(*types.Term) bool) bool {
return NewTypeSet(typ).All(fn)
}
// Any is a wrapper for NewTypeSet(typ).Any(fn).
func Any(typ types.Type, fn func(*types.Term) bool) bool {
return NewTypeSet(typ).Any(fn)
}
func IsSlice(term *types.Term) bool {
_, ok := term.Type().Underlying().(*types.Slice)
return ok
}
================================================
FILE: go/types/typeutil/typeparams_test.go
================================================
//go:build go1.18
package typeutil
import (
"go/types"
"testing"
)
func simpleUnionIface(terms ...*types.Term) *types.Interface {
return types.NewInterfaceType(nil, []types.Type{types.NewUnion(terms)})
}
func newChannelIface(chans ...types.Type) *types.Interface {
var terms []*types.Term
for _, ch := range chans {
terms = append(terms, types.NewTerm(false, ch))
}
return simpleUnionIface(terms...)
}
func TestTypeSetCoreType(t *testing.T) {
pkg := types.NewPackage("pkg", "pkg")
TInt := types.Universe.Lookup("int").Type()
TUint := types.Universe.Lookup("uint").Type()
TMyInt1 := types.NewNamed(types.NewTypeName(0, pkg, "MyInt1", nil), TInt, nil)
TMyInt2 := types.NewNamed(types.NewTypeName(0, pkg, "MyInt2", nil), TInt, nil)
TChanInt := types.NewChan(types.SendRecv, TInt)
TChanIntRecv := types.NewChan(types.RecvOnly, TInt)
TChanIntSend := types.NewChan(types.SendOnly, TInt)
TNamedChanInt := types.NewNamed(types.NewTypeName(0, pkg, "NamedChan", nil), TChanInt, nil)
tt := []struct {
iface *types.Interface
want types.Type
}{
// same underlying type
{
simpleUnionIface(types.NewTerm(false, TMyInt1), types.NewTerm(false, TMyInt2)),
types.Universe.Lookup("int").Type(),
},
// different underlying types
{
simpleUnionIface(types.NewTerm(false, TInt), types.NewTerm(false, TUint)),
nil,
},
// empty type set
{
types.NewInterfaceType(nil, []types.Type{
types.NewUnion([]*types.Term{types.NewTerm(false, TInt)}),
types.NewUnion([]*types.Term{types.NewTerm(false, TUint)}),
}),
nil,
},
}
for _, tc := range tt {
ts := NewTypeSet(tc.iface)
if !types.Identical(ts.CoreType(), tc.want) {
t.Errorf("CoreType(%s) = %s, want %s", tc.iface, ts.CoreType(), tc.want)
}
}
tt2 := []struct {
iface *types.Interface
want types.ChanDir
}{
{
// named sr + unnamed sr = sr
// sr + sr = sr
newChannelIface(TNamedChanInt, TChanInt),
types.SendRecv,
},
{
// sr + sr = sr
newChannelIface(TChanInt, TChanInt),
types.SendRecv,
},
{
// s + s = s
newChannelIface(TChanIntSend, TChanIntSend),
types.SendOnly,
},
{
// r + r = r
newChannelIface(TChanIntRecv, TChanIntRecv),
types.RecvOnly,
},
{
// s + r = nil
newChannelIface(TChanIntSend, TChanIntRecv),
-1,
},
{
// sr + r = r
newChannelIface(TChanInt, TChanIntRecv),
types.RecvOnly,
},
{
// sr + s = s
newChannelIface(TChanInt, TChanIntSend),
types.SendOnly,
},
}
for _, tc := range tt2 {
ts := NewTypeSet(tc.iface)
core := ts.CoreType()
if (core == nil) != (tc.want == -1) {
t.Errorf("CoreType(%s) = %s, want %d", tc.iface, core, tc.want)
}
if core == nil {
continue
}
dir := core.(*types.Chan).Dir()
if dir != tc.want {
t.Errorf("direction of %s is %d, want %d", tc.iface, dir, tc.want)
}
}
}
================================================
FILE: go/types/typeutil/upstream.go
================================================
package typeutil
import (
"go/ast"
"go/types"
_ "unsafe"
"golang.org/x/tools/go/types/typeutil"
)
type MethodSetCache = typeutil.MethodSetCache
type Hasher = typeutil.Hasher
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
return typeutil.Callee(info, call)
}
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
return typeutil.IntuitiveMethodSet(T, msets)
}
func MakeHasher() Hasher {
return typeutil.MakeHasher()
}
type Map[V any] struct {
m typeutil.Map
}
func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) }
func (m *Map[V]) At(key types.Type) (V, bool) {
v := m.m.At(key)
if v == nil {
var zero V
return zero, false
} else {
return v.(V), true
}
}
func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, value) }
func (m *Map[V]) Len() int { return m.m.Len() }
func (m *Map[V]) Iterate(f func(key types.Type, value V)) {
ff := func(key types.Type, value any) {
f(key, value.(V))
}
m.m.Iterate(ff)
}
func (m *Map[V]) Keys() []types.Type { return m.m.Keys() }
func (m *Map[V]) String() string { return m.m.String() }
func (m *Map[V]) KeysString() string { return m.m.KeysString() }
func (m *Map[V]) SetHasher(h typeutil.Hasher) { m.m.SetHasher(h) }
================================================
FILE: go/types/typeutil/util.go
================================================
package typeutil
import (
"bytes"
"go/types"
"strings"
"sync"
"golang.org/x/exp/typeparams"
)
var bufferPool = &sync.Pool{
New: func() any {
buf := bytes.NewBuffer(nil)
buf.Grow(64)
return buf
},
}
func FuncName(f *types.Func) string {
// We don't care about aliases in this function because we use FuncName to check calls
// to known methods, and method receivers are determined by the method declaration,
// not the call. Thus, even if a user does 'type Alias = *sync.Mutex' and calls
// Alias.Lock, we'll still see it as (*sync.Mutex).Lock.
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
if f.Type() != nil {
sig := f.Type().(*types.Signature)
if recv := sig.Recv(); recv != nil {
buf.WriteByte('(')
if _, ok := recv.Type().(*types.Interface); ok {
// gcimporter creates abstract methods of
// named interfaces using the interface type
// (not the named type) as the receiver.
// Don't print it in full.
buf.WriteString("interface")
} else {
types.WriteType(buf, recv.Type(), nil)
}
buf.WriteByte(')')
buf.WriteByte('.')
} else if f.Pkg() != nil {
writePackage(buf, f.Pkg())
}
}
buf.WriteString(f.Name())
s := buf.String()
bufferPool.Put(buf)
return s
}
func writePackage(buf *bytes.Buffer, pkg *types.Package) {
if pkg == nil {
return
}
s := pkg.Path()
if s != "" {
buf.WriteString(s)
buf.WriteByte('.')
}
}
// Dereference returns a pointer's element type; otherwise it returns
// T.
func Dereference(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return T
}
// DereferenceR returns a pointer's element type; otherwise it returns
// T. If the element type is itself a pointer, DereferenceR will be
// applied recursively.
func DereferenceR(T types.Type) types.Type {
if p, ok := T.Underlying().(*types.Pointer); ok {
return DereferenceR(p.Elem())
}
return T
}
func IsObject(obj types.Object, name string) bool {
var path string
if pkg := obj.Pkg(); pkg != nil {
path = pkg.Path() + "."
}
return path+obj.Name() == name
}
// IsTypeName reports whether obj represents the qualified name. If obj is a type alias,
// IsTypeName checks both the alias and the aliased type, if the aliased type has a type
// name.
func IsTypeName(obj *types.TypeName, name string) bool {
var qf string
if idx := strings.LastIndex(name, "."); idx != -1 {
qf = name[:idx]
name = name[idx+1:]
}
if obj.Name() == name &&
((qf == "" && obj.Pkg() == nil) || (obj.Pkg() != nil && obj.Pkg().Path() == qf)) {
return true
}
if !obj.IsAlias() {
return false
}
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
// github.com/golang/go/issues/66559
if typ, ok := types.Unalias(obj.Type()).(interface{ Obj() *types.TypeName }); ok {
return IsTypeName(typ.Obj(), name)
}
return false
}
func IsPointerToTypeWithName(typ types.Type, name string) bool {
ptr, ok := types.Unalias(typ).(*types.Pointer)
if !ok {
return false
}
return IsTypeWithName(ptr.Elem(), name)
}
// IsTypeWithName reports whether typ represents a type with the qualified name, If typ is
// a type alias, IsTypeWithName checks both the alias and the aliased type. The following
// types can have names: Basic, Named, Alias.
func IsTypeWithName(typ types.Type, name string) bool {
switch typ := typ.(type) {
case *types.Basic:
return typ.Name() == name
case *types.Named:
return IsTypeName(typ.Obj(), name)
case *types.Alias:
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
// github.com/golang/go/issues/66559
// IsTypeName already handles aliases to other aliases or named types; our
// fallback is required for aliases to basic types.
return IsTypeName(typ.Obj(), name) || IsTypeWithName(types.Unalias(typ), name)
default:
return false
}
}
// IsPointerLike returns true if type T is like a pointer. This returns true for all nillable types,
// unsafe.Pointer, and type sets where at least one term is pointer-like.
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface:
if T.IsMethodSet() {
return true
} else {
terms, err := typeparams.NormalTerms(T)
if err != nil {
return false
}
for _, term := range terms {
if IsPointerLike(term.Type()) {
return true
}
}
return false
}
case *types.Chan, *types.Map, *types.Signature, *types.Pointer, *types.Slice:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
}
return false
}
type Field struct {
Var *types.Var
Tag string
Path []int
}
// FlattenFields recursively flattens T and embedded structs,
// returning a list of fields. If multiple fields with the same name
// exist, all will be returned.
func FlattenFields(T *types.Struct) []Field {
return flattenFields(T, nil, nil)
}
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
if seen == nil {
seen = map[types.Type]bool{}
}
if seen[T] {
return nil
}
seen[T] = true
var out []Field
for i := 0; i < T.NumFields(); i++ {
field := T.Field(i)
tag := T.Tag(i)
np := append(path[:len(path):len(path)], i)
if field.Anonymous() {
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
out = append(out, flattenFields(s, np, seen)...)
} else {
out = append(out, Field{field, tag, np})
}
} else {
out = append(out, Field{field, tag, np})
}
}
return out
}
================================================
FILE: go.mod
================================================
module honnef.co/go/tools
go 1.25.0
require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678
golang.org/x/sys v0.39.0
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054
golang.org/x/tools/go/expect v0.1.1-deprecated
)
require (
golang.org/x/mod v0.31.0 // indirect
golang.org/x/sync v0.19.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054 h1:CHVDrNHx9ZoOrNN9kKWYIbT5Rj+WF2rlwPkhbQQ5V4U=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
================================================
FILE: internal/analysisinternal/typeindex/typeindex.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package typeindex defines an analyzer that provides a
// [golang.org/x/tools/internal/typesinternal/typeindex.Index].
//
// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
// intended to be used as a helper by other analyzers; it reports no
// diagnostics of its own.
package typeindex
import (
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"honnef.co/go/tools/internal/typesinternal/typeindex"
)
var Analyzer = &analysis.Analyzer{
Name: "typeindex",
Doc: "indexes of type information for later passes",
URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex",
Run: func(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
},
RunDespiteErrors: true,
Requires: []*analysis.Analyzer{inspect.Analyzer},
ResultType: reflect.TypeFor[*typeindex.Index](),
}
================================================
FILE: internal/cmd/ast-to-pattern/main.go
================================================
package main
import (
"fmt"
"go/ast"
"go/token"
"io"
"os"
"honnef.co/go/tools/pattern"
)
func main() {
src, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fset := token.NewFileSet()
node, err := parseDetectingNode(fset, string(src))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if _, ok := node.(*ast.File); ok {
fmt.Fprintln(os.Stderr, "cannot convert entire file to Node")
os.Exit(1)
}
fmt.Println(pattern.ASTToNode(node))
}
================================================
FILE: internal/cmd/ast-to-pattern/parse.go
================================================
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"text/template"
)
var tmplDecl = template.Must(template.New("").Parse(`` +
`package p; {{ . }}`))
var tmplExprs = template.Must(template.New("").Parse(`` +
`package p; var _ = []interface{}{ {{ . }}, }`))
var tmplStmts = template.Must(template.New("").Parse(`` +
`package p; func _() { {{ . }} }`))
var tmplType = template.Must(template.New("").Parse(`` +
`package p; var _ {{ . }}`))
var tmplValSpec = template.Must(template.New("").Parse(`` +
`package p; var {{ . }}`))
func execTmpl(tmpl *template.Template, src string) string {
var buf bytes.Buffer
if err := tmpl.Execute(&buf, src); err != nil {
panic(err)
}
return buf.String()
}
func noBadNodes(node ast.Node) bool {
any := false
ast.Inspect(node, func(n ast.Node) bool {
if any {
return false
}
switch n.(type) {
case *ast.BadExpr, *ast.BadDecl:
any = true
}
return true
})
return !any
}
func parseType(fset *token.FileSet, src string) (ast.Expr, *ast.File, error) {
asType := execTmpl(tmplType, src)
f, err := parser.ParseFile(fset, "", asType, parser.SkipObjectResolution)
if err != nil {
return nil, nil, err
}
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
return vs.Type, f, nil
}
// parseDetectingNode tries its best to parse the ast.Node contained in src, as
// one of: *ast.File, ast.Decl, ast.Expr, ast.Stmt, *ast.ValueSpec.
// It also returns the *ast.File used for the parsing, so that the returned node
// can be easily type-checked.
func parseDetectingNode(fset *token.FileSet, src string) (any, error) {
file := fset.AddFile("", fset.Base(), len(src))
scan := scanner.Scanner{}
scan.Init(file, []byte(src), nil, 0)
if _, tok, _ := scan.Scan(); tok == token.EOF {
return nil, fmt.Errorf("empty source code")
}
var mainErr error
// first try as a whole file
if f, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution); err == nil && noBadNodes(f) {
return f, nil
}
// then as a single declaration, or many
asDecl := execTmpl(tmplDecl, src)
if f, err := parser.ParseFile(fset, "", asDecl, parser.SkipObjectResolution); err == nil && noBadNodes(f) {
if len(f.Decls) == 1 {
return f.Decls[0], nil
}
return f, nil
}
// then as value expressions
asExprs := execTmpl(tmplExprs, src)
if f, err := parser.ParseFile(fset, "", asExprs, parser.SkipObjectResolution); err == nil && noBadNodes(f) {
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
cl := vs.Values[0].(*ast.CompositeLit)
if len(cl.Elts) == 1 {
return cl.Elts[0], nil
}
return cl.Elts, nil
}
// then try as statements
asStmts := execTmpl(tmplStmts, src)
if f, err := parser.ParseFile(fset, "", asStmts, parser.SkipObjectResolution); err == nil && noBadNodes(f) {
bl := f.Decls[0].(*ast.FuncDecl).Body
if len(bl.List) == 1 {
return bl.List[0], nil
}
return bl.List, nil
} else {
// Statements is what covers most cases, so it will give
// the best overall error message. Show positions
// relative to where the user's code is put in the
// template.
mainErr = err
}
// type expressions not yet picked up, for e.g. chans and interfaces
if typ, f, err := parseType(fset, src); err == nil && noBadNodes(f) {
return typ, nil
}
// value specs
asValSpec := execTmpl(tmplValSpec, src)
if f, err := parser.ParseFile(fset, "", asValSpec, parser.SkipObjectResolution); err == nil && noBadNodes(f) {
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
return vs, nil
}
return nil, mainErr
}
================================================
FILE: internal/cmd/gogrep/gogrep.go
================================================
package main
import (
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"
"reflect"
"strings"
"honnef.co/go/tools/pattern"
)
func match(fset *token.FileSet, pat pattern.Pattern, f *ast.File) {
entryNodesMap := make(map[reflect.Type]struct{})
for _, node := range pat.EntryNodes {
entryNodesMap[reflect.TypeOf(node)] = struct{}{}
}
ast.Inspect(f, func(node ast.Node) bool {
if node == nil {
return true
}
if _, ok := entryNodesMap[reflect.TypeOf(node)]; ok {
m := &pattern.Matcher{}
if m.Match(pat, node) {
fmt.Printf("%s: ", fset.Position(node.Pos()))
format.Node(os.Stdout, fset, node)
fmt.Println()
}
// OPT(dh): we could further speed this up by not
// chasing down impossible subtrees. For example,
// we'll never find an ImportSpec beneath a FuncLit.
}
return true
})
}
func main() {
flag.Parse()
// XXX don't use MustParse, handle error
p := &pattern.Parser{}
q, err := p.Parse(flag.Args()[0])
if err != nil {
fmt.Println(err)
os.Exit(1)
}
dir := flag.Args()[1]
// XXX should we create a new fileset per file? what if we're
// checking millions of files, will this use up a lot of memory?
fset := token.NewFileSet()
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
// XXX error handling
panic(err)
}
if !strings.HasSuffix(path, ".go") {
return nil
}
// XXX don't try to parse irregular files or directories
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
// XXX log error?
return nil
}
match(fset, q, f)
return nil
})
}
================================================
FILE: internal/cmd/irdump/main.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// irdump: a tool for displaying the IR form of Go programs.
package main
import (
"flag"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis/singlechecker"
)
// flags
var (
dot bool
html string
)
func init() {
flag.BoolVar(&dot, "dot", false, "Print Graphviz dot of CFG")
flag.StringVar(&html, "html", "", "Print HTML for 'function'")
}
func main() {
buildir.Debug.Mode = ir.PrintFunctions | ir.PrintPackages
flag.Func("build", ir.BuilderModeDoc, func(s string) error {
return buildir.Debug.Mode.Set(s)
})
singlechecker.Main(buildir.Analyzer)
// if dot {
// for _, p := range pkgs {
// for _, m := range p.Members {
// if fn, ok := m.(*ir.Function); ok {
// fmt.Println("digraph{")
// fmt.Printf("label = %q;\n", fn.Name())
// for _, b := range fn.Blocks {
// fmt.Printf("n%d [label=\"%d: %s\"]\n", b.Index, b.Index, b.Comment)
// for _, succ := range b.Succs {
// fmt.Printf("n%d -> n%d\n", b.Index, succ.Index)
// }
// }
// fmt.Println("}")
// }
// }
// }
// }
}
================================================
FILE: internal/cmd/unused/unused.go
================================================
package main
import (
"flag"
"fmt"
"log"
"os"
"golang.org/x/tools/go/packages"
"honnef.co/go/tools/go/loader"
"honnef.co/go/tools/lintcmd/cache"
"honnef.co/go/tools/unused"
)
// OPT(dh): we don't need full graph merging if we're not flagging exported objects. In that case, we can reuse the old
// list-based merging approach.
// OPT(dh): we can either merge graphs as we process packages, or we can merge them all in one go afterwards (then
// reloading them from cache). The first approach will likely lead to higher peak memory usage, but the latter may take
// more wall time to finish if we had spare CPU resources while processing packages.
func main() {
opts := unused.DefaultOptions
flag.BoolVar(&opts.FieldWritesAreUses, "field-writes-are-uses", opts.FieldWritesAreUses, "")
flag.BoolVar(&opts.PostStatementsAreReads, "post-statements-are-reads", opts.PostStatementsAreReads, "")
flag.BoolVar(&opts.ExportedIsUsed, "exported-is-used", opts.ExportedIsUsed, "")
flag.BoolVar(&opts.ExportedFieldsAreUsed, "exported-fields-are-used", opts.ExportedFieldsAreUsed, "")
flag.BoolVar(&opts.ParametersAreUsed, "parameters-are-used", opts.ParametersAreUsed, "")
flag.BoolVar(&opts.LocalVariablesAreUsed, "local-variables-are-used", opts.LocalVariablesAreUsed, "")
flag.BoolVar(&opts.GeneratedIsUsed, "generated-is-used", opts.GeneratedIsUsed, "")
flag.Parse()
// pprof.StartCPUProfile(os.Stdout)
// defer pprof.StopCPUProfile()
// XXX set cache key for this tool
c, err := cache.Default()
if err != nil {
log.Fatal(err)
}
cfg := &packages.Config{
Tests: true,
}
specs, err := loader.Graph(c, cfg, flag.Args()...)
if err != nil {
log.Fatal(err)
}
var sg unused.SerializedGraph
ourPkgs := map[string]struct{}{}
for _, spec := range specs {
if len(spec.Errors) != 0 {
// XXX priunt errors
continue
}
lpkg, _, err := loader.Load(spec, nil)
if err != nil {
continue
}
if len(lpkg.Errors) != 0 {
continue
}
// XXX get directives and generated
g := unused.Graph(lpkg.Fset, lpkg.Syntax, lpkg.Types, lpkg.TypesInfo, nil, nil, opts)
sg.Merge(g)
ourPkgs[spec.PkgPath] = struct{}{}
}
res := sg.Results()
for _, obj := range res.Unused {
// XXX format paths like staticcheck does
if _, ok := ourPkgs[obj.Path.PkgPath]; !ok {
continue
}
fmt.Printf("%s: %s %s is unused\n", obj.DisplayPosition, obj.Kind, obj.Name)
}
fmt.Fprintln(os.Stderr, sg.Dot())
}
================================================
FILE: internal/diff/myers/diff.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This code is a modified copy of code in gopls.
// It's not as efficient as it could be, it allocates way too much, the API is sloppy.
// But it's only used to print failed tests, so we don't care.
package myers
import (
"fmt"
"strings"
)
// OpKind is used to denote the type of operation a line represents.
type OpKind int
const (
// Delete is the operation kind for a line that is present in the input
// but not in the output.
Delete OpKind = iota
// Insert is the operation kind for a line that is new in the output.
Insert
// Equal is the operation kind for a line that is the same in the input and
// output, often used to provide context around edited lines.
Equal
)
// String returns a human readable representation of an OpKind. It is not
// intended for machine processing.
func (k OpKind) String() string {
switch k {
case Delete:
return "delete"
case Insert:
return "insert"
case Equal:
return "equal"
default:
panic("unknown operation kind")
}
}
// Sources:
// https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/
// https://www.codeproject.com/Articles/42279/%2FArticles%2F42279%2FInvestigating-Myers-diff-algorithm-Part-1-of-2
func ComputeEdits(before, after string) []*operation {
ops := operations(splitLines(before), splitLines(after))
return ops
}
type operation struct {
Kind OpKind
Content []string // content from b
I1, I2 int // indices of the line in a
J1 int // indices of the line in b, J2 implied by len(Content)
}
func (op *operation) String() string {
var prefix string
switch op.Kind {
case Delete:
prefix = "-"
case Insert:
prefix = "+"
case Equal:
prefix = " "
}
var out strings.Builder
for _, line := range op.Content {
out.WriteString(fmt.Sprintf("%s%s", prefix, line))
}
return out.String()
}
// operations returns the list of operations to convert a into b, consolidating
// operations for multiple lines and not including equal lines.
func operations(a, b []string) []*operation {
if len(a) == 0 && len(b) == 0 {
return nil
}
trace, offset := shortestEditSequence(a, b)
snakes := backtrack(trace, len(a), len(b), offset)
M, N := len(a), len(b)
var i int
solution := make([]*operation, len(a)+len(b))
add := func(op *operation, i2, j2 int) {
if op == nil {
return
}
op.I2 = i2
switch op.Kind {
case Insert:
op.Content = b[op.J1:j2]
case Delete:
op.Content = a[op.I1:op.I2]
case Equal:
op.Content = a[op.I1:op.I2]
}
solution[i] = op
i++
}
x, y := 0, 0
for _, snake := range snakes {
if len(snake) < 2 {
continue
}
var op *operation
// delete (horizontal)
for snake[0]-snake[1] > x-y {
if op == nil {
op = &operation{
Kind: Delete,
I1: x,
J1: y,
}
}
x++
if x == M {
break
}
}
add(op, x, y)
op = nil
// insert (vertical)
for snake[0]-snake[1] < x-y {
if op == nil {
op = &operation{
Kind: Insert,
I1: x,
J1: y,
}
}
y++
}
add(op, x, y)
op = nil
// equal (diagonal)
for x < snake[0] {
if op == nil {
op = &operation{
Kind: Equal,
I1: x,
J1: y,
}
}
x++
y++
}
add(op, x, y)
if x >= M && y >= N {
break
}
}
return solution[:i]
}
// backtrack uses the trace for the edit sequence computation and returns the
// "snakes" that make up the solution. A "snake" is a single deletion or
// insertion followed by zero or diagonals.
func backtrack(trace [][]int, x, y, offset int) [][]int {
snakes := make([][]int, len(trace))
d := len(trace) - 1
for ; x > 0 && y > 0 && d > 0; d-- {
V := trace[d]
if len(V) == 0 {
continue
}
snakes[d] = []int{x, y}
k := x - y
var kPrev int
if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) {
kPrev = k + 1
} else {
kPrev = k - 1
}
x = V[kPrev+offset]
y = x - kPrev
}
if x < 0 || y < 0 {
return snakes
}
snakes[d] = []int{x, y}
return snakes
}
// shortestEditSequence returns the shortest edit sequence that converts a into b.
func shortestEditSequence(a, b []string) ([][]int, int) {
M, N := len(a), len(b)
V := make([]int, 2*(N+M)+1)
offset := N + M
trace := make([][]int, N+M+1)
// Iterate through the maximum possible length of the SES (N+M).
for d := 0; d <= N+M; d++ {
copyV := make([]int, len(V))
// k lines are represented by the equation y = x - k. We move in
// increments of 2 because end points for even d are on even k lines.
for k := -d; k <= d; k += 2 {
// At each point, we either go down or to the right. We go down if
// k == -d, and we go to the right if k == d. We also prioritize
// the maximum x value, because we prefer deletions to insertions.
var x int
if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) {
x = V[k+1+offset] // down
} else {
x = V[k-1+offset] + 1 // right
}
y := x - k
// Diagonal moves while we have equal contents.
for x < M && y < N && a[x] == b[y] {
x++
y++
}
V[k+offset] = x
// Return if we've exceeded the maximum values.
if x == M && y == N {
// Makes sure to save the state of the array before returning.
copy(copyV, V)
trace[d] = copyV
return trace, offset
}
}
// Save the state of the array.
copy(copyV, V)
trace[d] = copyV
}
return nil, 0
}
func splitLines(text string) []string {
lines := strings.SplitAfter(text, "\n")
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
return lines
}
================================================
FILE: internal/passes/buildir/buildir.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package buildir defines an Analyzer that constructs the IR
// of an error-free package and returns the set of all
// functions within it. It does not report any diagnostics itself but
// may be used as an input to other analyzers.
//
// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE.
package buildir
import (
"go/types"
"reflect"
"honnef.co/go/tools/go/ir"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
)
var Debug = struct {
Mode ir.BuilderMode
}{}
var Analyzer = &analysis.Analyzer{
Name: "buildir",
Doc: "build IR for later passes",
Run: run,
ResultType: reflect.TypeFor[*IR](),
Requires: []*analysis.Analyzer{ctrlflow.Analyzer},
}
// IR provides intermediate representation for all the
// source functions in the current package.
type IR struct {
Pkg *ir.Package
SrcFuncs []*ir.Function
}
func run(pass *analysis.Pass) (any, error) {
cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
// Plundered from ssautil.BuildPackage.
// We must create a new Program for each Package because the
// analysis API provides no place to hang a Program shared by
// all Packages. Consequently, IR Packages and Functions do not
// have a canonical representation across an analysis session of
// multiple packages. This is unlikely to be a problem in
// practice because the analysis API essentially forces all
// packages to be analysed independently, so any given call to
// Analysis.Run on a package will see only IR objects belonging
// to a single Program.
mode := ir.GlobalDebug
if Debug.Mode != 0 {
mode = Debug.Mode
}
prog := ir.NewProgram(pass.Fset, mode)
prog.SetNoReturn(cfgs.NoReturn)
// Create IR packages for all imports.
// Order is not significant.
created := make(map[*types.Package]bool)
var createAll func(pkgs []*types.Package)
createAll = func(pkgs []*types.Package) {
for _, p := range pkgs {
if !created[p] {
created[p] = true
prog.CreatePackage(p, nil, nil, true)
createAll(p.Imports())
}
}
}
createAll(pass.Pkg.Imports())
// Create and build the primary package.
irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
irpkg.Build()
// Compute list of source functions, including literals,
// in source order.
var addAnons func(f *ir.Function)
funcs := make([]*ir.Function, len(irpkg.Functions))
copy(funcs, irpkg.Functions)
addAnons = func(f *ir.Function) {
for _, anon := range f.AnonFuncs {
funcs = append(funcs, anon)
addAnons(anon)
}
}
for _, fn := range irpkg.Functions {
addAnons(fn)
}
return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil
}
================================================
FILE: internal/passes/buildir/buildir_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildir_test
import (
"fmt"
"os"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"honnef.co/go/tools/internal/passes/buildir"
)
func Test(t *testing.T) {
result := analysistest.Run(t, analysistest.TestData(), buildir.Analyzer, "a")[0].Result
irinfo := result.(*buildir.IR)
got := fmt.Sprint(irinfo.SrcFuncs)
want := `[a.init a.Fib (a.T).fib a._ a._]`
if got != want {
t.Errorf("IR.SrcFuncs = %s, want %s", got, want)
for _, f := range irinfo.SrcFuncs {
f.WriteTo(os.Stderr)
}
}
}
================================================
FILE: internal/passes/buildir/testdata/src/a/a.go
================================================
package a
func Fib(x int) int {
if x < 2 {
return x
}
return Fib(x-1) + Fib(x-2)
}
type T int
func (T) fib(x int) int { return Fib(x) }
func _() {
print("hi")
}
func _() {
print("hi")
}
================================================
FILE: internal/renameio/UPSTREAM
================================================
This package is a copy of cmd/go/internal/renameio.
The upstream package no longer exists, as the Go project replaced all of its uses with the lockedfile package.
================================================
FILE: internal/renameio/renameio.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package renameio writes files atomically by renaming temporary files.
package renameio
import (
"bytes"
"io"
"math/rand"
"os"
"path/filepath"
"strconv"
"honnef.co/go/tools/internal/robustio"
)
const patternSuffix = ".tmp"
// Pattern returns a glob pattern that matches the unrenamed temporary files
// created when writing to filename.
func Pattern(filename string) string {
return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix)
}
// WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary
// file in the same directory as filename, then renames it atomically to the
// final name.
//
// That ensures that the final location, if it exists, is always a complete file.
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
return WriteToFile(filename, bytes.NewReader(data), perm)
}
// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader
// instead of a slice.
func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) {
f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm)
if err != nil {
return err
}
defer func() {
// Only call os.Remove on f.Name() if we failed to rename it: otherwise,
// some other process may have created a new file with the same name after
// that.
if err != nil {
f.Close()
os.Remove(f.Name())
}
}()
if _, err := io.Copy(f, data); err != nil {
return err
}
// Sync the file before renaming it: otherwise, after a crash the reader may
// observe a 0-length file instead of the actual contents.
// See https://golang.org/issue/22397#issuecomment-380831736.
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return robustio.Rename(f.Name(), filename)
}
// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that
// may occur if the file is concurrently replaced.
//
// Errors are classified heuristically and retries are bounded, so even this
// function may occasionally return a spurious error on Windows.
// If so, the error will likely wrap one of:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
func ReadFile(filename string) ([]byte, error) {
return robustio.ReadFile(filename)
}
// tempFile creates a new temporary file with given permission bits.
func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) {
for range 10000 {
name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
if os.IsExist(err) {
continue
}
break
}
return
}
================================================
FILE: internal/renameio/renameio_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !plan9
package renameio
import (
"encoding/binary"
"errors"
"math/rand"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"syscall"
"testing"
"time"
"honnef.co/go/tools/internal/robustio"
)
func TestConcurrentReadsAndWrites(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "blob.bin")
const chunkWords = 8 << 10
buf := make([]byte, 2*chunkWords*8)
for i := range uint64(2 * chunkWords) {
binary.LittleEndian.PutUint64(buf[i*8:], i)
}
var attempts int64 = 128
if !testing.Short() {
attempts *= 16
}
const parallel = 32
var sem = make(chan bool, parallel)
var (
writeSuccesses, readSuccesses int64 // atomic
writeErrnoSeen, readErrnoSeen sync.Map
)
for n := attempts; n > 0; n-- {
sem <- true
go func() {
defer func() { <-sem }()
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
offset := rand.Intn(chunkWords)
chunk := buf[offset*8 : (offset+chunkWords)*8]
if err := WriteFile(path, chunk, 0666); err == nil {
atomic.AddInt64(&writeSuccesses, 1)
} else if robustio.IsEphemeralError(err) {
var (
errno syscall.Errno
dup bool
)
if errors.As(err, &errno) {
_, dup = writeErrnoSeen.LoadOrStore(errno, true)
}
if !dup {
t.Logf("ephemeral error: %v", err)
}
} else {
t.Errorf("unexpected error: %v", err)
}
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
data, err := ReadFile(path)
if err == nil {
atomic.AddInt64(&readSuccesses, 1)
} else if robustio.IsEphemeralError(err) {
var (
errno syscall.Errno
dup bool
)
if errors.As(err, &errno) {
_, dup = readErrnoSeen.LoadOrStore(errno, true)
}
if !dup {
t.Logf("ephemeral error: %v", err)
}
return
} else {
t.Errorf("unexpected error: %v", err)
return
}
if len(data) != 8*chunkWords {
t.Errorf("read %d bytes, but each write is a %d-byte file", len(data), 8*chunkWords)
return
}
u := binary.LittleEndian.Uint64(data)
for i := 1; i < chunkWords; i++ {
next := binary.LittleEndian.Uint64(data[i*8:])
if next != u+1 {
t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i)
return
}
u = next
}
}()
}
for n := parallel; n > 0; n-- {
sem <- true
}
var minWriteSuccesses int64 = attempts
if runtime.GOOS == "windows" {
// Windows produces frequent "Access is denied" errors under heavy rename load.
// As long as those are the only errors and *some* of the writes succeed, we're happy.
minWriteSuccesses = attempts / 4
}
if writeSuccesses < minWriteSuccesses {
t.Errorf("%d (of %d) writes succeeded; want ≥ %d", writeSuccesses, attempts, minWriteSuccesses)
} else {
t.Logf("%d (of %d) writes succeeded (ok: ≥ %d)", writeSuccesses, attempts, minWriteSuccesses)
}
var minReadSuccesses int64 = attempts
switch runtime.GOOS {
case "windows":
// Windows produces frequent "Access is denied" errors under heavy rename load.
// As long as those are the only errors and *some* of the reads succeed, we're happy.
minReadSuccesses = attempts / 4
case "darwin":
// The filesystem on macOS 10.14 occasionally fails with "no such file or
// directory" errors. See https://golang.org/issue/33041. The flake rate is
// fairly low, so ensure that at least 75% of attempts succeed.
minReadSuccesses = attempts - (attempts / 4)
}
if readSuccesses < minReadSuccesses {
t.Errorf("%d (of %d) reads succeeded; want ≥ %d", readSuccesses, attempts, minReadSuccesses)
} else {
t.Logf("%d (of %d) reads succeeded (ok: ≥ %d)", readSuccesses, attempts, minReadSuccesses)
}
}
================================================
FILE: internal/renameio/umask_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !plan9 && !windows && !js
package renameio
import (
"os"
"path/filepath"
"syscall"
"testing"
)
func TestWriteFileModeAppliesUmask(t *testing.T) {
dir := t.TempDir()
const mode = 0644
const umask = 0007
defer syscall.Umask(syscall.Umask(umask))
file := filepath.Join(dir, "testWrite")
err := WriteFile(file, []byte("go-build"), mode)
if err != nil {
t.Fatalf("Failed to write file: %v", err)
}
fi, err := os.Stat(file)
if err != nil {
t.Fatalf("Stat %q (looking for mode %#o): %s", file, mode, err)
}
if fi.Mode()&os.ModePerm != 0640 {
t.Errorf("Stat %q: mode %#o want %#o", file, fi.Mode()&os.ModePerm, 0640)
}
}
================================================
FILE: internal/robustio/UPSTREAM
================================================
This package is a copy of cmd/go/internal/robustio.
It is mostly in sync with upstream according to the last commit we've looked at,
with the exception of still using I/O functions that work with older Go versions.
The last upstream commit we've looked at was:
dc04f3ba1f25313bc9c97e728620206c235db9ee
================================================
FILE: internal/robustio/robustio.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package robustio wraps I/O functions that are prone to failure on Windows,
// transparently retrying errors up to an arbitrary timeout.
//
// Errors are classified heuristically and retries are bounded, so the functions
// in this package do not completely eliminate spurious errors. However, they do
// significantly reduce the rate of failure in practice.
//
// If so, the error will likely wrap one of:
// The functions in this package do not completely eliminate spurious errors,
// but substantially reduce their rate of occurrence in practice.
package robustio
// Rename is like os.Rename, but on Windows retries errors that may occur if the
// file is concurrently read or overwritten.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func Rename(oldpath, newpath string) error {
return rename(oldpath, newpath)
}
// ReadFile is like os.ReadFile, but on Windows retries errors that may
// occur if the file is concurrently replaced.
//
// (See golang.org/issue/31247 and golang.org/issue/32188.)
func ReadFile(filename string) ([]byte, error) {
return readFile(filename)
}
// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur
// if an executable file in the directory has recently been executed.
//
// (See golang.org/issue/19491.)
func RemoveAll(path string) error {
return removeAll(path)
}
// IsEphemeralError reports whether err is one of the errors that the functions
// in this package attempt to mitigate.
//
// Errors considered ephemeral include:
// - syscall.ERROR_ACCESS_DENIED
// - syscall.ERROR_FILE_NOT_FOUND
// - internal/syscall/windows.ERROR_SHARING_VIOLATION
//
// This set may be expanded in the future; programs must not rely on the
// non-ephemerality of any given error.
func IsEphemeralError(err error) bool {
return isEphemeralError(err)
}
================================================
FILE: internal/robustio/robustio_darwin.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio
import (
"errors"
"syscall"
)
const errFileNotFound = syscall.ENOENT
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == errFileNotFound
}
return false
}
================================================
FILE: internal/robustio/robustio_flaky.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows || darwin
package robustio
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const arbitraryTimeout = 2000 * time.Millisecond
// retry retries ephemeral errors from f up to an arbitrary timeout
// to work around filesystem flakiness on Windows and Darwin.
func retry(f func() (err error, mayRetry bool)) error {
var (
bestErr error
lowestErrno syscall.Errno
start time.Time
nextSleep time.Duration = 1 * time.Millisecond
)
for {
err, mayRetry := f()
if err == nil || !mayRetry {
return err
}
var errno syscall.Errno
if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) {
bestErr = err
lowestErrno = errno
} else if bestErr == nil {
bestErr = err
}
if start.IsZero() {
start = time.Now()
} else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout {
break
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
return bestErr
}
// rename is like os.Rename, but retries ephemeral errors.
//
// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with
// MOVEFILE_REPLACE_EXISTING.
//
// Windows also provides a different system call, ReplaceFile,
// that provides similar semantics, but perhaps preserves more metadata. (The
// documentation on the differences between the two is very sparse.)
//
// Empirical error rates with MoveFileEx are lower under modest concurrency, so
// for now we're sticking with what the os package already provides.
func rename(oldpath, newpath string) (err error) {
return retry(func() (err error, mayRetry bool) {
err = os.Rename(oldpath, newpath)
return err, isEphemeralError(err)
})
}
// readFile is like os.ReadFile, but retries ephemeral errors.
func readFile(filename string) ([]byte, error) {
var b []byte
err := retry(func() (err error, mayRetry bool) {
b, err = os.ReadFile(filename)
// Unlike in rename, we do not retry errFileNotFound here: it can occur
// as a spurious error, but the file may also genuinely not exist, so the
// increase in robustness is probably not worth the extra latency.
return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound)
})
return b, err
}
func removeAll(path string) error {
return retry(func() (err error, mayRetry bool) {
err = os.RemoveAll(path)
return err, isEphemeralError(err)
})
}
================================================
FILE: internal/robustio/robustio_other.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows && !darwin
package robustio
import (
"os"
)
func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
func readFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
func removeAll(path string) error {
return os.RemoveAll(path)
}
func isEphemeralError(err error) bool {
return false
}
================================================
FILE: internal/robustio/robustio_windows.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package robustio
import (
"errors"
"syscall"
)
const ERROR_SHARING_VIOLATION = 32
const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND
// isEphemeralError returns true if err may be resolved by waiting.
func isEphemeralError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.ERROR_ACCESS_DENIED,
syscall.ERROR_FILE_NOT_FOUND,
ERROR_SHARING_VIOLATION:
return true
}
}
return false
}
================================================
FILE: internal/sharedcheck/lint.go
================================================
package sharedcheck
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/facts/tokenfile"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
func CheckRangeStringRunes(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
cb := func(node ast.Node) bool {
rng, ok := node.(*ast.RangeStmt)
if !ok || !astutil.IsBlank(rng.Key) {
return true
}
v, _ := fn.ValueForExpr(rng.X)
// Check that we're converting from string to []rune
val, _ := v.(*ir.Convert)
if val == nil {
return true
}
Tsrc, ok := typeutil.CoreType(val.X.Type()).(*types.Basic)
if !ok || Tsrc.Kind() != types.String {
return true
}
Tdst, ok := typeutil.CoreType(val.Type()).(*types.Slice)
if !ok {
return true
}
TdstElem, ok := types.Unalias(Tdst.Elem()).(*types.Basic)
if !ok || TdstElem.Kind() != types.Int32 {
return true
}
// Check that the result of the conversion is only used to
// range over
refs := val.Referrers()
if refs == nil {
return true
}
// Expect two refs: one for obtaining the length of the slice,
// one for accessing the elements
if len(irutil.FilterDebug(*refs)) != 2 {
// TODO(dh): right now, we check that only one place
// refers to our slice. This will miss cases such as
// ranging over the slice twice. Ideally, we'd ensure that
// the slice is only used for ranging over (without
// accessing the key), but that is harder to do because in
// IR form, ranging over a slice looks like an ordinary
// loop with index increments and slice accesses. We'd
// have to look at the associated AST node to check that
// it's a range statement.
return true
}
pass.Reportf(rng.Pos(), "should range over string, not []rune(string)")
return true
}
if source := fn.Source(); source != nil {
ast.Inspect(source, cb)
}
}
return nil, nil
}
// RedundantTypeInDeclarationChecker returns a checker that flags variable declarations with redundantly specified types.
// That is, it flags 'var v T = e' where e's type is identical to T and 'var v = e' (or 'v := e') would have the same effect.
//
// It does not flag variables under the following conditions, to reduce the number of false positives:
// - global variables – these often specify types to aid godoc
// - files that use cgo – cgo code generation and pointer checking emits redundant types
//
// It does not flag variables under the following conditions, unless flagHelpfulTypes is true, to reduce the number of noisy positives:
// - packages that import syscall or unsafe – these sometimes use this form of assignment to make sure types are as expected
// - variables named the blank identifier – a pattern used to confirm the types of variables
// - untyped expressions on the rhs – the explicitness might aid readability
func RedundantTypeInDeclarationChecker(verb string, flagHelpfulTypes bool) *analysis.Analyzer {
fn := func(pass *analysis.Pass) (any, error) {
eval := func(expr ast.Expr) (types.TypeAndValue, error) {
info := &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
}
err := types.CheckExpr(pass.Fset, pass.Pkg, expr.Pos(), expr, info)
return info.Types[expr], err
}
if !flagHelpfulTypes {
// Don't look at code in low-level packages
for _, imp := range pass.Pkg.Imports() {
if imp.Path() == "syscall" || imp.Path() == "unsafe" {
return nil, nil
}
}
}
fn := func(node ast.Node) {
decl := node.(*ast.GenDecl)
if decl.Tok != token.VAR {
return
}
gen, _ := code.Generator(pass, decl.Pos())
if gen == generated.Cgo {
// TODO(dh): remove this exception once we can use UsesCgo
return
}
// Delay looking up parent AST nodes until we have to
checkedDecl := false
specLoop:
for _, spec := range decl.Specs {
spec := spec.(*ast.ValueSpec)
if spec.Type == nil {
continue
}
if len(spec.Names) != len(spec.Values) {
continue
}
Tlhs := pass.TypesInfo.TypeOf(spec.Type)
for i, v := range spec.Values {
if !flagHelpfulTypes && spec.Names[i].Name == "_" {
continue specLoop
}
Trhs := pass.TypesInfo.TypeOf(v)
if !types.Identical(Tlhs, Trhs) {
continue specLoop
}
// Some expressions are untyped and get converted to the lhs type implicitly.
// This applies to untyped constants, shift operations with an untyped lhs, and possibly others.
//
// Check if the type is truly redundant, i.e. if the type on the lhs doesn't match the default type of the untyped constant.
tv, err := eval(v)
if err != nil {
panic(err)
}
if b, ok := types.Unalias(tv.Type).(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
if Tlhs != types.Default(b) {
// The rhs is untyped and its default type differs from the explicit type on the lhs
continue specLoop
}
switch v := v.(type) {
case *ast.Ident:
// Only flag named constant rhs if it's a predeclared identifier.
// Don't flag other named constants, as the explicit type may aid readability.
if pass.TypesInfo.ObjectOf(v).Pkg() != nil && !flagHelpfulTypes {
continue specLoop
}
case *ast.BasicLit:
// Do flag basic literals
default:
// Don't flag untyped rhs expressions unless flagHelpfulTypes is set
if !flagHelpfulTypes {
continue specLoop
}
}
}
}
if !checkedDecl {
// Don't flag global variables. These often have explicit types for godoc's sake.
path, _ := astutil.PathEnclosingInterval(code.File(pass, decl), decl.Pos(), decl.Pos())
pathLoop:
for _, el := range path {
switch el.(type) {
case *ast.FuncDecl, *ast.FuncLit:
checkedDecl = true
break pathLoop
}
}
if !checkedDecl {
// decl is not inside a function
break specLoop
}
}
report.Report(pass, spec.Type, fmt.Sprintf("%s omit type %s from declaration; it will be inferred from the right-hand side", verb, report.Render(pass, spec.Type)), report.FilterGenerated(),
report.Fixes(edit.Fix("Remove redundant type", edit.Delete(spec.Type))))
}
}
code.Preorder(pass, fn, (*ast.GenDecl)(nil))
return nil, nil
}
return &analysis.Analyzer{
Run: fn,
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer, tokenfile.Analyzer},
}
}
================================================
FILE: internal/sync/sync.go
================================================
package sync
type Semaphore struct {
ch chan struct{}
}
func NewSemaphore(size int) Semaphore {
return Semaphore{
ch: make(chan struct{}, size),
}
}
func (sem Semaphore) Acquire() {
sem.ch <- struct{}{}
}
func (sem Semaphore) AcquireMaybe() bool {
select {
case sem.ch <- struct{}{}:
return true
default:
return false
}
}
func (sem Semaphore) Release() {
<-sem.ch
}
func (sem Semaphore) Len() int {
return len(sem.ch)
}
func (sem Semaphore) Cap() int {
return cap(sem.ch)
}
================================================
FILE: internal/testenv/UPSTREAM
================================================
This package is a copy of golang.org/x/tools/internal/testenv,
imported from commit 33e937220d8f91f1d242ad15aebc3e245aca5515.
The last upstream commit we've looked at was: 414ec9c3f0ea84250efb8f18f51c607184b7631e
================================================
FILE: internal/testenv/testenv.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package testenv contains helper functions for skipping tests
// based on which tools are present in the environment.
package testenv
import (
"bytes"
"fmt"
"go/build"
"os"
"runtime"
"strings"
"sync"
"time"
exec "golang.org/x/sys/execabs"
)
// Testing is an abstraction of a *testing.T.
type Testing interface {
Skipf(format string, args ...any)
Fatalf(format string, args ...any)
}
type helperer interface {
Helper()
}
// packageMainIsDevel reports whether the module containing package main
// is a development version (if module information is available).
//
// Builds in GOPATH mode and builds that lack module information are assumed to
// be development versions.
var packageMainIsDevel = func() bool { return true }
var checkGoGoroot struct {
once sync.Once
err error
}
func hasTool(tool string) error {
if tool == "cgo" {
enabled, err := cgoEnabled(false)
if err != nil {
return fmt.Errorf("checking cgo: %v", err)
}
if !enabled {
return fmt.Errorf("cgo not enabled")
}
return nil
}
_, err := exec.LookPath(tool)
if err != nil {
return err
}
switch tool {
case "patch":
// check that the patch tools supports the -o argument
temp, err := os.CreateTemp("", "patch-test")
if err != nil {
return err
}
temp.Close()
defer os.Remove(temp.Name())
cmd := exec.Command(tool, "-o", temp.Name())
if err := cmd.Run(); err != nil {
return err
}
case "go":
checkGoGoroot.once.Do(func() {
// Ensure that the 'go' command found by exec.LookPath is from the correct
// GOROOT. Otherwise, 'some/path/go test ./...' will test against some
// version of the 'go' binary other than 'some/path/go', which is almost
// certainly not what the user intended.
out, err := exec.Command(tool, "env", "GOROOT").CombinedOutput()
if err != nil {
checkGoGoroot.err = err
return
}
GOROOT := strings.TrimSpace(string(out))
//lint:ignore SA1019 runtime.GOROOT is deprecated, specifically to allow copying binaries. What should we do
// about this?
if runtimeGOROOT := runtime.GOROOT(); GOROOT != runtimeGOROOT {
checkGoGoroot.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtimeGOROOT)
}
})
if checkGoGoroot.err != nil {
return checkGoGoroot.err
}
case "diff":
// Check that diff is the GNU version, needed for the -u argument and
// to report missing newlines at the end of files.
out, err := exec.Command(tool, "-version").Output()
if err != nil {
return err
}
if !bytes.Contains(out, []byte("GNU diffutils")) {
return fmt.Errorf("diff is not the GNU version")
}
}
return nil
}
func cgoEnabled(bypassEnvironment bool) (bool, error) {
cmd := exec.Command("go", "env", "CGO_ENABLED")
if bypassEnvironment {
cmd.Env = append(os.Environ(), "CGO_ENABLED=")
}
out, err := cmd.CombinedOutput()
if err != nil {
return false, err
}
enabled := strings.TrimSpace(string(out))
return enabled == "1", nil
}
func allowMissingTool(tool string) bool {
if runtime.GOOS == "android" {
// Android builds generally run tests on a separate machine from the build,
// so don't expect any external tools to be available.
return true
}
switch tool {
case "cgo":
if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") {
// Explicitly disabled on -nocgo builders.
return true
}
if enabled, err := cgoEnabled(true); err == nil && !enabled {
// No platform support.
return true
}
case "go":
if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
// Work around a misconfigured builder (see https://golang.org/issue/33950).
return true
}
case "diff":
if os.Getenv("GO_BUILDER_NAME") != "" {
return true
}
case "patch":
if os.Getenv("GO_BUILDER_NAME") != "" {
return true
}
}
// If a developer is actively working on this test, we expect them to have all
// of its dependencies installed. However, if it's just a dependency of some
// other module (for example, being run via 'go test all'), we should be more
// tolerant of unusual environments.
return !packageMainIsDevel()
}
// NeedsTool skips t if the named tool is not present in the path.
// As a special case, "cgo" means "go" is present and can compile cgo programs.
func NeedsTool(t Testing, tool string) {
if t, ok := t.(helperer); ok {
t.Helper()
}
err := hasTool(tool)
if err == nil {
return
}
if allowMissingTool(tool) {
t.Skipf("skipping because %s tool not available: %v", tool, err)
} else {
t.Fatalf("%s tool not available: %v", tool, err)
}
}
// NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
// the current process environment is not present in the path.
func NeedsGoPackages(t Testing) {
if t, ok := t.(helperer); ok {
t.Helper()
}
tool := os.Getenv("GOPACKAGESDRIVER")
switch tool {
case "off":
// "off" forces go/packages to use the go command.
tool = "go"
case "":
if _, err := exec.LookPath("gopackagesdriver"); err == nil {
tool = "gopackagesdriver"
} else {
tool = "go"
}
}
NeedsTool(t, tool)
}
// NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied
// by env is not present in the path.
func NeedsGoPackagesEnv(t Testing, env []string) {
if t, ok := t.(helperer); ok {
t.Helper()
}
for _, v := range env {
if after, ok := strings.CutPrefix(v, "GOPACKAGESDRIVER="); ok {
tool := after
if tool == "off" {
NeedsTool(t, "go")
} else {
NeedsTool(t, tool)
}
return
}
}
NeedsGoPackages(t)
}
// NeedsGoBuild skips t if the current system can't build programs with “go build”
// and then run them with os.StartProcess or exec.Command.
// android, and darwin/arm systems don't have the userspace go build needs to run,
// and js/wasm doesn't support running subprocesses.
func NeedsGoBuild(t Testing) {
if t, ok := t.(helperer); ok {
t.Helper()
}
NeedsTool(t, "go")
switch runtime.GOOS {
case "android", "js":
t.Skipf("skipping test: %v can't build and run Go binaries", runtime.GOOS)
case "darwin":
if strings.HasPrefix(runtime.GOARCH, "arm") {
t.Skipf("skipping test: darwin/arm can't build and run Go binaries")
}
}
}
// ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the
// current machine is a builder known to have scarce resources.
//
// It should be called from within a TestMain function.
func ExitIfSmallMachine() {
switch b := os.Getenv("GO_BUILDER_NAME"); b {
case "linux-arm-scaleway":
// "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230.
fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)")
case "plan9-arm":
fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)")
case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert":
// As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10,
// and there is only one of each. We shouldn't waste those scarce resources
// running very slow tests.
fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b)
case "dragonfly-amd64":
// As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2,
// and seems to have unusually slow disk performance.
fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)")
case "linux-riscv64-unmatched":
// As of 2021-11-03, this builder is empirically not fast enough to run
// gopls tests. Ideally we should make the tests faster in short mode
// and/or fix them to not assume arbitrary deadlines.
// For now, we'll skip them instead.
fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b)
default:
return
}
os.Exit(0)
}
// Go1Point returns the x in Go 1.x.
func Go1Point() int {
for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
var version int
if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
continue
}
return version
}
panic("bad release tags")
}
// NeedsGo1Point skips t if the Go version used to run the test is older than
// 1.x.
func NeedsGo1Point(t Testing, x int) {
if t, ok := t.(helperer); ok {
t.Helper()
}
if Go1Point() < x {
t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x)
}
}
// SkipAfterGo1Point skips t if the Go version used to run the test is newer than
// 1.x.
func SkipAfterGo1Point(t Testing, x int) {
if t, ok := t.(helperer); ok {
t.Helper()
}
if Go1Point() > x {
t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x)
}
}
// Deadline returns the deadline of t, if known,
// using the Deadline method added in Go 1.15.
func Deadline(t Testing) (time.Time, bool) {
td, ok := t.(interface {
Deadline() (time.Time, bool)
})
if !ok {
return time.Time{}, false
}
return td.Deadline()
}
================================================
FILE: internal/testenv/testenv_112.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.12
// +build go1.12
package testenv
import "runtime/debug"
func packageMainIsDevelModule() bool {
info, ok := debug.ReadBuildInfo()
if !ok {
// Most test binaries currently lack build info, but this should become more
// permissive once https://golang.org/issue/33976 is fixed.
return true
}
// Note: info.Main.Version describes the version of the module containing
// package main, not the version of “the main module”.
// See https://golang.org/issue/33975.
return info.Main.Version == "(devel)"
}
func init() {
packageMainIsDevel = packageMainIsDevelModule
}
================================================
FILE: internal/typesinternal/typeindex/typeindex.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package typeindex provides an [Index] of type information for a
// package, allowing efficient lookup of, say, whether a given symbol
// is referenced and, if so, where from; or of the [inspector.Cursor] for
// the declaration of a particular [types.Object] symbol.
package typeindex
import (
"encoding/binary"
"go/ast"
"go/types"
"iter"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
)
// IsPackageLevel reports whether obj is a package-level symbol.
func IsPackageLevel(obj types.Object) bool {
return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope()
}
// New constructs an Index for the package of type-annotated syntax
//
// TODO(adonovan): accept a FileSet too?
// We regret not requiring one in inspector.New.
func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *Index {
ix := &Index{
inspect: inspect,
info: info,
packages: make(map[string]*types.Package),
def: make(map[types.Object]inspector.Cursor),
uses: make(map[types.Object]*uses),
}
addPackage := func(pkg2 *types.Package) {
if pkg2 != nil && pkg2 != pkg {
ix.packages[pkg2.Path()] = pkg2
}
}
for cur := range inspect.Root().Preorder((*ast.ImportSpec)(nil), (*ast.Ident)(nil)) {
switch n := cur.Node().(type) {
case *ast.ImportSpec:
// Index direct imports, including blank ones.
if pkgname := info.PkgNameOf(n); pkgname != nil {
addPackage(pkgname.Imported())
}
case *ast.Ident:
// Index all defining and using identifiers.
if obj := info.Defs[n]; obj != nil {
ix.def[obj] = cur
}
if obj := info.Uses[n]; obj != nil {
// Index indirect dependencies (via fields and methods).
if !IsPackageLevel(obj) {
addPackage(obj.Pkg())
}
us, ok := ix.uses[obj]
if !ok {
us = &uses{}
us.code = us.initial[:0]
ix.uses[obj] = us
}
delta := cur.Index() - us.last
if delta < 0 {
panic("non-monotonic")
}
us.code = binary.AppendUvarint(us.code, uint64(delta))
us.last = cur.Index()
}
}
}
return ix
}
// An Index holds an index mapping [types.Object] symbols to their syntax.
// In effect, it is the inverse of [types.Info].
type Index struct {
inspect *inspector.Inspector
info *types.Info
packages map[string]*types.Package // packages of all symbols referenced from this package
def map[types.Object]inspector.Cursor // Cursor of *ast.Ident that defines the Object
uses map[types.Object]*uses // Cursors of *ast.Idents that use the Object
}
// A uses holds the list of Cursors of Idents that use a given symbol.
//
// The Uses map of [types.Info] is substantial, so it pays to compress
// its inverse mapping here, both in space and in CPU due to reduced
// allocation. A Cursor is 2 words; a Cursor.Index is 4 bytes; but
// since Cursors are naturally delivered in ascending order, we can
// use varint-encoded deltas at a cost of only ~1.7-2.2 bytes per use.
//
// Many variables have only one or two uses, so their encoded uses may
// fit in the 4 bytes of initial, saving further CPU and space
// essentially for free since the struct's size class is 4 words.
type uses struct {
code []byte // varint-encoded deltas of successive Cursor.Index values
last int32 // most recent Cursor.Index value; used during encoding
initial [4]byte // use slack in size class as initial space for code
}
// Uses returns the sequence of Cursors of [*ast.Ident]s in this package
// that refer to obj. If obj is nil, the sequence is empty.
func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
if uses := ix.uses[obj]; uses != nil {
var last int32
for code := uses.code; len(code) > 0; {
delta, n := binary.Uvarint(code)
last += int32(delta)
if !yield(ix.inspect.At(last)) {
return
}
code = code[n:]
}
}
}
}
// Used reports whether any of the specified objects are used, in
// other words, obj != nil && Uses(obj) is non-empty for some obj in objs.
//
// (This treatment of nil allows Used to be called directly on the
// result of [Index.Object] so that analyzers can conveniently skip
// packages that don't use a symbol of interest.)
func (ix *Index) Used(objs ...types.Object) bool {
for _, obj := range objs {
if obj != nil && ix.uses[obj] != nil {
return true
}
}
return false
}
// Def returns the Cursor of the [*ast.Ident] in this package
// that declares the specified object, if any.
func (ix *Index) Def(obj types.Object) (inspector.Cursor, bool) {
cur, ok := ix.def[obj]
return cur, ok
}
// Package returns the package of the specified path,
// or nil if it is not referenced from this package.
func (ix *Index) Package(path string) *types.Package {
return ix.packages[path]
}
// Object returns the package-level symbol name within the package of
// the specified path, or nil if the package or symbol does not exist
// or is not visible from this package.
func (ix *Index) Object(path, name string) types.Object {
if pkg := ix.Package(path); pkg != nil {
return pkg.Scope().Lookup(name)
}
return nil
}
// Selection returns the named method or field belonging to the
// package-level type returned by Object(path, typename).
func (ix *Index) Selection(path, typename, name string) types.Object {
if obj := ix.Object(path, typename); obj != nil {
if tname, ok := obj.(*types.TypeName); ok {
obj, _, _ := types.LookupFieldOrMethod(tname.Type(), true, obj.Pkg(), name)
return obj
}
}
return nil
}
// Calls returns the sequence of cursors for *ast.CallExpr nodes that
// call the specified callee, as defined by [typeutil.Callee].
// If callee is nil, the sequence is empty.
func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
for cur := range ix.Uses(callee) {
ek, _ := cur.ParentEdge()
// The call may be of the form f() or x.f(),
// optionally with parens; ascend from f to call.
//
// It is tempting but wrong to use the first
// CallExpr ancestor: we have to make sure the
// ident is in the CallExpr.Fun position, otherwise
// f(f, f) would have two spurious matches.
// Avoiding Enclosing is also significantly faster.
// inverse unparen: f -> (f)
for ek == edge.ParenExpr_X {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// ascend selector: f -> x.f
if ek == edge.SelectorExpr_Sel {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// inverse unparen again
for ek == edge.ParenExpr_X {
cur = cur.Parent()
ek, _ = cur.ParentEdge()
}
// ascend from f or x.f to call
if ek == edge.CallExpr_Fun {
curCall := cur.Parent()
call := curCall.Node().(*ast.CallExpr)
if typeutil.Callee(ix.info, call) == callee {
if !yield(curCall) {
return
}
}
}
}
}
}
================================================
FILE: knowledge/arg.go
================================================
package knowledge
var Args = map[string]int{
"(*sync.Pool).Put.x": 0,
"(*text/template.Template).Parse.text": 0,
"(io.Seeker).Seek.offset": 0,
"(time.Time).Sub.u": 0,
"append.elems": 1,
"append.slice": 0,
"bytes.Equal.a": 0,
"bytes.Equal.b": 1,
"encoding/ascii85.Encode.dst": 0,
"encoding/ascii85.Encode.src": 1,
"(*encoding/base32.Encoding).Encode.dst": 0,
"(*encoding/base32.Encoding).Encode.src": 1,
"(*encoding/base64.Encoding).Encode.dst": 0,
"(*encoding/base64.Encoding).Encode.src": 1,
"encoding/binary.Write.data": 2,
"encoding/hex.Encode.dst": 0,
"encoding/hex.Encode.src": 1,
"(*encoding/json.Decoder).Decode.v": 0,
"(*encoding/json.Encoder).Encode.v": 0,
"(*encoding/xml.Decoder).Decode.v": 0,
"(*encoding/xml.Encoder).Encode.v": 0,
"errors.New.text": 0,
"fmt.Fprintf.format": 1,
"fmt.Printf.format": 0,
"fmt.Sprintf.a[0]": 1,
"fmt.Sprintf.format": 0,
"json.Marshal.v": 0,
"json.Unmarshal.v": 1,
"len.v": 0,
"make.size[0]": 1,
"make.size[1]": 2,
"make.t": 0,
"net/url.Parse.rawurl": 0,
"os.OpenFile.flag": 1,
"os/exec.Command.name": 0,
"os/signal.Notify.c": 0,
"regexp.Compile.expr": 0,
"runtime.SetFinalizer.finalizer": 1,
"runtime.SetFinalizer.obj": 0,
"sort.Sort.data": 0,
"strconv.AppendFloat.bitSize": 4,
"strconv.AppendFloat.fmt": 2,
"strconv.AppendInt.base": 2,
"strconv.AppendUint.base": 2,
"strconv.FormatComplex.bitSize": 3,
"strconv.FormatComplex.fmt": 1,
"strconv.FormatFloat.bitSize": 3,
"strconv.FormatFloat.fmt": 1,
"strconv.FormatInt.base": 1,
"strconv.FormatUint.base": 1,
"strconv.ParseComplex.bitSize": 1,
"strconv.ParseFloat.bitSize": 1,
"strconv.ParseInt.base": 1,
"strconv.ParseInt.bitSize": 2,
"strconv.ParseUint.base": 1,
"strconv.ParseUint.bitSize": 2,
"time.Parse.layout": 0,
"time.Sleep.d": 0,
"xml.Marshal.v": 0,
"xml.Unmarshal.v": 1,
}
// Arg turns the name of an argument into an argument index.
// Indices are zero-based and method receivers do not count as arguments.
//
// Arg refers to a manually compiled mapping (see the Args variable.)
// Modify the knowledge package to add new arguments.
func Arg(name string) int {
n, ok := Args[name]
if !ok {
panic("unknown argument " + name)
}
return n
}
================================================
FILE: knowledge/deprecated.go
================================================
package knowledge
const (
// DeprecatedNeverUse indicates that an API should never be used, regardless of Go version.
DeprecatedNeverUse = "never"
// DeprecatedUseNoLonger indicates that an API has no use anymore.
DeprecatedUseNoLonger = "no longer"
)
// Deprecation describes when a Go API has been deprecated.
type Deprecation struct {
// The minor Go version since which this API has been deprecated.
DeprecatedSince string
// The minor Go version since which an alternative API has been available.
// May also be one of DeprecatedNeverUse or DeprecatedUseNoLonger.
AlternativeAvailableSince string
}
// go/importer.ForCompiler contains "Deprecated:", but it refers to a single argument, not the whole function.
// Luckily, the notice starts in the middle of a paragraph, and as such isn't detected by us.
// TODO(dh): StdlibDeprecations doesn't contain entries for internal packages and unexported API. That's fine for normal
// users, but makes the Deprecated check less useful for people working on Go itself.
// StdlibDeprecations contains a mapping of Go API (such as variables, methods, or fields, among others)
// to information about when it has been deprecated.
var StdlibDeprecations = map[string]Deprecation{
// FIXME(dh): AllowBinary isn't being detected as deprecated
// because the comment has a newline right after "Deprecated:"
"go/build.AllowBinary": {"go1.7", "go1.7"},
"(archive/zip.FileHeader).CompressedSize": {"go1.1", "go1.1"},
"(archive/zip.FileHeader).UncompressedSize": {"go1.1", "go1.1"},
"(archive/zip.FileHeader).ModifiedTime": {"go1.10", "go1.10"},
"(archive/zip.FileHeader).ModifiedDate": {"go1.10", "go1.10"},
"(*archive/zip.FileHeader).ModTime": {"go1.10", "go1.10"},
"(*archive/zip.FileHeader).SetModTime": {"go1.10", "go1.10"},
"(go/doc.Package).Bugs": {"go1.1", "go1.1"},
"os.SEEK_SET": {"go1.7", "go1.7"},
"os.SEEK_CUR": {"go1.7", "go1.7"},
"os.SEEK_END": {"go1.7", "go1.7"},
"(net.Dialer).Cancel": {"go1.7", "go1.7"},
"runtime.CPUProfile": {"go1.9", "go1.0"},
"compress/flate.ReadError": {"go1.6", DeprecatedUseNoLonger},
"compress/flate.WriteError": {"go1.6", DeprecatedUseNoLonger},
"path/filepath.HasPrefix": {"go1.0", DeprecatedNeverUse},
"(net/http.Transport).Dial": {"go1.7", "go1.7"},
"(net/http.Transport).DialTLS": {"go1.14", "go1.14"},
"(*net/http.Transport).CancelRequest": {"go1.6", "go1.5"},
"net/http.ErrWriteAfterFlush": {"go1.7", DeprecatedUseNoLonger},
"net/http.ErrHeaderTooLong": {"go1.8", DeprecatedUseNoLonger},
"net/http.ErrShortBody": {"go1.8", DeprecatedUseNoLonger},
"net/http.ErrMissingContentLength": {"go1.8", DeprecatedUseNoLonger},
"net/http/httputil.ErrPersistEOF": {"go1.0", DeprecatedUseNoLonger},
"net/http/httputil.ErrClosed": {"go1.0", DeprecatedUseNoLonger},
"net/http/httputil.ErrPipeline": {"go1.0", DeprecatedUseNoLonger},
"net/http/httputil.ServerConn": {"go1.0", "go1.0"},
"net/http/httputil.NewServerConn": {"go1.0", "go1.0"},
"net/http/httputil.ClientConn": {"go1.0", "go1.0"},
"net/http/httputil.NewClientConn": {"go1.0", "go1.0"},
"net/http/httputil.NewProxyClientConn": {"go1.0", "go1.0"},
"(net/http.Request).Cancel": {"go1.7", "go1.7"},
"(text/template/parse.PipeNode).Line": {"go1.1", DeprecatedUseNoLonger},
"(text/template/parse.ActionNode).Line": {"go1.1", DeprecatedUseNoLonger},
"(text/template/parse.BranchNode).Line": {"go1.1", DeprecatedUseNoLonger},
"(text/template/parse.TemplateNode).Line": {"go1.1", DeprecatedUseNoLonger},
"database/sql/driver.ColumnConverter": {"go1.9", "go1.9"},
"database/sql/driver.Execer": {"go1.8", "go1.8"},
"database/sql/driver.Queryer": {"go1.8", "go1.8"},
"(database/sql/driver.Conn).Begin": {"go1.8", "go1.8"},
"(database/sql/driver.Stmt).Exec": {"go1.8", "go1.8"},
"(database/sql/driver.Stmt).Query": {"go1.8", "go1.8"},
"syscall.StringByteSlice": {"go1.1", "go1.1"},
"syscall.StringBytePtr": {"go1.1", "go1.1"},
"syscall.StringSlicePtr": {"go1.1", "go1.1"},
"syscall.StringToUTF16": {"go1.1", "go1.1"},
"syscall.StringToUTF16Ptr": {"go1.1", "go1.1"},
"(*regexp.Regexp).Copy": {"go1.12", DeprecatedUseNoLonger},
"(archive/tar.Header).Xattrs": {"go1.10", "go1.10"},
"archive/tar.TypeRegA": {"go1.11", "go1.1"},
"go/types.NewInterface": {"go1.11", "go1.11"},
"(*go/types.Interface).Embedded": {"go1.11", "go1.11"},
"go/importer.For": {"go1.12", "go1.12"},
"encoding/json.InvalidUTF8Error": {"go1.2", DeprecatedUseNoLonger},
"encoding/json.UnmarshalFieldError": {"go1.2", DeprecatedUseNoLonger},
"encoding/csv.ErrTrailingComma": {"go1.2", DeprecatedUseNoLonger},
"(encoding/csv.Reader).TrailingComma": {"go1.2", DeprecatedUseNoLonger},
"(net.Dialer).DualStack": {"go1.12", "go1.12"},
"net/http.ErrUnexpectedTrailer": {"go1.12", DeprecatedUseNoLonger},
"net/http.CloseNotifier": {"go1.11", "go1.7"},
// This is hairy. The notice says "Not all errors in the http package related to protocol errors are of type ProtocolError", but doesn't that imply that some errors do?
"net/http.ProtocolError": {"go1.8", DeprecatedUseNoLonger},
"(crypto/x509.CertificateRequest).Attributes": {"go1.5", "go1.3"},
"(*crypto/x509.Certificate).CheckCRLSignature": {"go1.19", "go1.19"},
"crypto/x509.ParseCRL": {"go1.19", "go1.19"},
"crypto/x509.ParseDERCRL": {"go1.19", "go1.19"},
"(*crypto/x509.Certificate).CreateCRL": {"go1.19", "go1.19"},
"crypto/x509/pkix.TBSCertificateList": {"go1.19", "go1.19"},
"crypto/x509/pkix.RevokedCertificate": {"go1.19", "go1.19"},
"go/doc.ToHTML": {"go1.20", "go1.20"},
"go/doc.ToText": {"go1.20", "go1.20"},
"go/doc.Synopsis": {"go1.20", "go1.20"},
"math/rand.Seed": {"go1.20", "go1.0"},
"math/rand.Read": {"go1.20", DeprecatedNeverUse},
// These functions have no direct alternative, but they are insecure and should no longer be used.
"crypto/x509.IsEncryptedPEMBlock": {"go1.16", DeprecatedNeverUse},
"crypto/x509.DecryptPEMBlock": {"go1.16", DeprecatedNeverUse},
"crypto/x509.EncryptPEMBlock": {"go1.16", DeprecatedNeverUse},
"crypto/dsa": {"go1.16", DeprecatedNeverUse},
// This function has no alternative, but also no purpose.
"(*crypto/rc4.Cipher).Reset": {"go1.12", DeprecatedNeverUse},
"(net/http/httptest.ResponseRecorder).HeaderMap": {"go1.11", "go1.7"},
"image.ZP": {"go1.13", "go1.0"},
"image.ZR": {"go1.13", "go1.0"},
"(*debug/gosym.LineTable).LineToPC": {"go1.2", "go1.2"},
"(*debug/gosym.LineTable).PCToLine": {"go1.2", "go1.2"},
"crypto/tls.VersionSSL30": {"go1.13", DeprecatedNeverUse},
"(crypto/tls.Config).NameToCertificate": {"go1.14", DeprecatedUseNoLonger},
"(*crypto/tls.Config).BuildNameToCertificate": {"go1.14", DeprecatedUseNoLonger},
"(crypto/tls.Config).SessionTicketKey": {"go1.16", "go1.5"},
// No alternative, no use
"(crypto/tls.ConnectionState).NegotiatedProtocolIsMutual": {"go1.16", DeprecatedNeverUse},
// No alternative, but insecure
"(crypto/tls.ConnectionState).TLSUnique": {"go1.16", DeprecatedNeverUse},
"image/jpeg.Reader": {"go1.4", DeprecatedNeverUse},
// All of these have been deprecated in favour of external libraries
"syscall.AttachLsf": {"go1.7", "go1.0"},
"syscall.DetachLsf": {"go1.7", "go1.0"},
"syscall.LsfSocket": {"go1.7", "go1.0"},
"syscall.SetLsfPromisc": {"go1.7", "go1.0"},
"syscall.LsfJump": {"go1.7", "go1.0"},
"syscall.LsfStmt": {"go1.7", "go1.0"},
"syscall.BpfStmt": {"go1.7", "go1.0"},
"syscall.BpfJump": {"go1.7", "go1.0"},
"syscall.BpfBuflen": {"go1.7", "go1.0"},
"syscall.SetBpfBuflen": {"go1.7", "go1.0"},
"syscall.BpfDatalink": {"go1.7", "go1.0"},
"syscall.SetBpfDatalink": {"go1.7", "go1.0"},
"syscall.SetBpfPromisc": {"go1.7", "go1.0"},
"syscall.FlushBpf": {"go1.7", "go1.0"},
"syscall.BpfInterface": {"go1.7", "go1.0"},
"syscall.SetBpfInterface": {"go1.7", "go1.0"},
"syscall.BpfTimeout": {"go1.7", "go1.0"},
"syscall.SetBpfTimeout": {"go1.7", "go1.0"},
"syscall.BpfStats": {"go1.7", "go1.0"},
"syscall.SetBpfImmediate": {"go1.7", "go1.0"},
"syscall.SetBpf": {"go1.7", "go1.0"},
"syscall.CheckBpfVersion": {"go1.7", "go1.0"},
"syscall.BpfHeadercmpl": {"go1.7", "go1.0"},
"syscall.SetBpfHeadercmpl": {"go1.7", "go1.0"},
"syscall.RouteRIB": {"go1.8", "go1.0"},
"syscall.RoutingMessage": {"go1.8", "go1.0"},
"syscall.RouteMessage": {"go1.8", "go1.0"},
"syscall.InterfaceMessage": {"go1.8", "go1.0"},
"syscall.InterfaceAddrMessage": {"go1.8", "go1.0"},
"syscall.ParseRoutingMessage": {"go1.8", "go1.0"},
"syscall.ParseRoutingSockaddr": {"go1.8", "go1.0"},
"syscall.InterfaceAnnounceMessage": {"go1.7", "go1.0"},
"syscall.InterfaceMulticastAddrMessage": {"go1.7", "go1.0"},
"syscall.FormatMessage": {"go1.5", "go1.0"},
"syscall.PostQueuedCompletionStatus": {"go1.17", "go1.0"},
"syscall.GetQueuedCompletionStatus": {"go1.17", "go1.0"},
"syscall.CreateIoCompletionPort": {"go1.17", "go1.0"},
// We choose to only track the package itself, even though all functions are deprecated individually, too. Anyone
// using ioutil directly will have to import it, and this keeps the noise down.
"io/ioutil": {"go1.19", "go1.19"},
"bytes.Title": {"go1.18", "go1.0"},
"strings.Title": {"go1.18", "go1.0"},
"(crypto/tls.Config).PreferServerCipherSuites": {"go1.18", DeprecatedUseNoLonger},
// It's not clear if Subjects was okay to use in the past, so we err on the less noisy side of assuming that it was.
"(*crypto/x509.CertPool).Subjects": {"go1.18", DeprecatedUseNoLonger},
"go/types.NewSignature": {"go1.18", "go1.18"},
"(net.Error).Temporary": {"go1.18", DeprecatedNeverUse},
// InterfaceData is another tricky case. It was deprecated in Go 1.18, but has been useless since Go 1.4, and an
// "alternative" (using your own unsafe hacks) has existed forever. We don't want to get into hairsplitting with
// users who somehow successfully used this between 1.4 and 1.18, so we'll just tag it as deprecated since 1.18.
"(reflect.Value).InterfaceData": {"go1.18", "go1.18"},
// The following objects are only deprecated on Windows.
"syscall.Syscall": {"go1.18", "go1.18"},
"syscall.Syscall12": {"go1.18", "go1.18"},
"syscall.Syscall15": {"go1.18", "go1.18"},
"syscall.Syscall18": {"go1.18", "go1.18"},
"syscall.Syscall6": {"go1.18", "go1.18"},
"syscall.Syscall9": {"go1.18", "go1.18"},
"reflect.SliceHeader": {"go1.21", "go1.17"},
"reflect.StringHeader": {"go1.21", "go1.20"},
"crypto/elliptic.GenerateKey": {"go1.21", "go1.21"},
"crypto/elliptic.Marshal": {"go1.21", "go1.21"},
"crypto/elliptic.Unmarshal": {"go1.21", "go1.21"},
"(*crypto/elliptic.CurveParams).Add": {"go1.21", "go1.21"},
"(*crypto/elliptic.CurveParams).Double": {"go1.21", "go1.21"},
"(*crypto/elliptic.CurveParams).IsOnCurve": {"go1.21", "go1.21"},
"(*crypto/elliptic.CurveParams).ScalarBaseMult": {"go1.21", "go1.21"},
"(*crypto/elliptic.CurveParams).ScalarMult": {"go1.21", "go1.21"},
"(crypto/elliptic.Curve).Add": {"go1.21", "go1.21"},
"(crypto/elliptic.Curve).Double": {"go1.21", "go1.21"},
"(crypto/elliptic.Curve).IsOnCurve": {"go1.21", "go1.21"},
"(crypto/elliptic.Curve).ScalarBaseMult": {"go1.21", "go1.21"},
"(crypto/elliptic.Curve).ScalarMult": {"go1.21", "go1.21"},
"crypto/rsa.GenerateMultiPrimeKey": {"go1.21", DeprecatedNeverUse},
"(crypto/rsa.PrecomputedValues).CRTValues": {"go1.21", DeprecatedNeverUse},
"(crypto/x509.RevocationList).RevokedCertificates": {"go1.21", "go1.21"},
"go/ast.NewPackage": {"go1.22", "go1.0"},
"go/ast.Importer": {"go1.22", "go1.0"},
"go/ast.Object": {"go1.22", "go1.0"},
"go/ast.Package": {"go1.22", "go1.0"},
"go/ast.Scope": {"go1.22", "go1.0"},
"html/template.ErrJSTemplate": {"go1.22", DeprecatedUseNoLonger},
"reflect.PtrTo": {"go1.22", "go1.18"},
// Technically, runtime.GOROOT could be considered DeprecatedNeverUse, but
// using it used to be a lot more common and accepted.
"runtime.GOROOT": {"go1.24", DeprecatedUseNoLonger},
// These are never safe to use; a concrete alternative was added in Go 1.2 (crypto/cipher.AEAD).
"crypto/cipher.NewCFBDecrypter": {"go1.24", "go1.2"},
"crypto/cipher.NewCFBEncrypter": {"go1.24", "go1.2"},
"crypto/cipher.NewOFB": {"go1.24", "go1.2"},
"go/ast.FilterFuncDuplicates": {"go1.25", "go1.0"},
"go/ast.FilterImportDuplicates": {"go1.25", "go1.0"},
"go/ast.FilterUnassociatedComments": {"go1.25", "go1.0"},
"go/ast.FilterPackage": {"go1.25", "go1.0"},
"go/ast.MergePackageFiles": {"go1.25", "go1.0"},
"go/ast.PackageExports": {"go1.25", "go1.0"},
"go/ast.MergeMode": {"go1.25", "go1.0"},
// Go 1.11 because that's around the time x/tools/go/packages was released.
"go/parser.ParseDir": {"go1.25", "go1.11"},
// Go 1.25 is the first version to provide all of the alternatives mentioned
// by the deprecation note.
"(crypto/ecdsa.PublicKey).X": {"go1.26", "go1.25"},
"(crypto/ecdsa.PublicKey).Y": {"go1.26", "go1.25"},
"(crypto/ecdsa.PrivateKey).D": {"go1.26", "go1.25"},
"crypto/rsa.DecryptPKCS1v15": {"go1.26", DeprecatedNeverUse},
"crypto/rsa.DecryptPKCS1v15SessionKey": {"go1.26", DeprecatedNeverUse},
"crypto/rsa.PKCS1v15DecryptOptions": {"go1.26", DeprecatedNeverUse},
"crypto/rsa.EncryptPKCS1v15": {"go1.26", DeprecatedNeverUse},
"(net/http/httputil.ReverseProxy).Director": {"go1.26", "go1.20"},
}
// Last imported from GOROOT/api/go1.26.txt at d3ddc4854429185e6e06ca1f7628bb790404abb5.
================================================
FILE: knowledge/doc.go
================================================
// Package knowledge contains manually collected information about Go APIs.
package knowledge
================================================
FILE: knowledge/signatures.go
================================================
package knowledge
import (
"go/token"
"go/types"
)
var Signatures = map[string]*types.Signature{
"(io.Seeker).Seek": types.NewSignatureType(nil, nil, nil,
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.Int64]),
types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]),
),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.Int64]),
types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
),
false,
),
"(io.Writer).Write": types.NewSignatureType(nil, nil, nil,
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])),
),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]),
types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
),
false,
),
"(io.StringWriter).WriteString": types.NewSignatureType(nil, nil, nil,
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.String]),
),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]),
types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
),
false,
),
"(encoding.TextMarshaler).MarshalText": types.NewSignatureType(nil, nil, nil,
types.NewTuple(),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])),
types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
),
false,
),
"(encoding/json.Marshaler).MarshalJSON": types.NewSignatureType(nil, nil, nil,
types.NewTuple(),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])),
types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
),
false,
),
"(fmt.Stringer).String": types.NewSignatureType(nil, nil, nil,
types.NewTuple(),
types.NewTuple(
types.NewParam(token.NoPos, nil, "", types.Typ[types.String]),
),
false,
),
}
var Interfaces = map[string]*types.Interface{
"fmt.Stringer": types.NewInterfaceType(
[]*types.Func{
types.NewFunc(token.NoPos, nil, "String", Signatures["(fmt.Stringer).String"]),
},
nil,
).Complete(),
"error": types.Universe.Lookup("error").Type().Underlying().(*types.Interface),
"io.Writer": types.NewInterfaceType(
[]*types.Func{
types.NewFunc(token.NoPos, nil, "Write", Signatures["(io.Writer).Write"]),
},
nil,
).Complete(),
"io.StringWriter": types.NewInterfaceType(
[]*types.Func{
types.NewFunc(token.NoPos, nil, "WriteString", Signatures["(io.StringWriter).WriteString"]),
},
nil,
).Complete(),
"encoding.TextMarshaler": types.NewInterfaceType(
[]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalText", Signatures["(encoding.TextMarshaler).MarshalText"]),
},
nil,
).Complete(),
"encoding/json.Marshaler": types.NewInterfaceType(
[]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalJSON", Signatures["(encoding/json.Marshaler).MarshalJSON"]),
},
nil,
).Complete(),
}
================================================
FILE: knowledge/targets.go
================================================
package knowledge
var KnownGOOS = map[string]struct{}{
"aix": {},
"android": {},
"darwin": {},
"dragonfly": {},
"freebsd": {},
"hurd": {},
"illumos": {},
"ios": {},
"js": {},
"linux": {},
"netbsd": {},
"openbsd": {},
"plan9": {},
"solaris": {},
"wasip1": {},
"windows": {},
}
var KnownGOARCH = map[string]struct{}{
"386": {},
"amd64": {},
"arm": {},
"arm64": {},
"loong64": {},
"mips": {},
"mipsle": {},
"mips64": {},
"mips64le": {},
"ppc64": {},
"ppc64le": {},
"riscv64": {},
"s390x": {},
"sparc64": {},
"wasm": {},
}
================================================
FILE: lintcmd/cache/UPSTREAM
================================================
This package is a copy of cmd/go/internal/cache.
Differences from upstream:
- we continue to use renameio instead of lockedfile for writing trim.txt
- we still use I/O helpers that work with earlier versions of Go.
- we use a cache directory specific to Staticcheck
- we use a Staticcheck-specific salt
The last upstream commit we've looked at was:
06ac303f6a14b133254f757e54599c48e3c2a4ad
================================================
FILE: lintcmd/cache/cache.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cache implements a build artifact cache.
//
// This package is a slightly modified fork of Go's
// cmd/go/internal/cache package.
package cache
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"honnef.co/go/tools/internal/renameio"
)
// An ActionID is a cache action key, the hash of a complete description of a
// repeatable computation (command line, environment variables,
// input file contents, executable contents).
type ActionID [HashSize]byte
// An OutputID is a cache output key, the hash of an output of a computation.
type OutputID [HashSize]byte
// A Cache is a package cache, backed by a file system directory tree.
type Cache struct {
dir string
now func() time.Time
salt []byte
}
// Open opens and returns the cache in the given directory.
//
// It is safe for multiple processes on a single machine to use the
// same cache directory in a local file system simultaneously.
// They will coordinate using operating system file locks and may
// duplicate effort but will not corrupt the cache.
//
// However, it is NOT safe for multiple processes on different machines
// to share a cache directory (for example, if the directory were stored
// in a network file system). File locking is notoriously unreliable in
// network file systems and may not suffice to protect the cache.
func Open(dir string) (*Cache, error) {
info, err := os.Stat(dir)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, &os.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
}
for i := range 256 {
name := filepath.Join(dir, fmt.Sprintf("%02x", i))
if err := os.MkdirAll(name, 0777); err != nil {
return nil, err
}
}
c := &Cache{
dir: dir,
now: time.Now,
}
return c, nil
}
func (c *Cache) SetSalt(b []byte) {
c.salt = b
}
// fileName returns the name of the file corresponding to the given id.
func (c *Cache) fileName(id [HashSize]byte, key string) string {
return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
}
// An entryNotFoundError indicates that a cache entry was not found, with an
// optional underlying reason.
type entryNotFoundError struct {
Err error
}
func (e *entryNotFoundError) Error() string {
if e.Err == nil {
return "cache entry not found"
}
return fmt.Sprintf("cache entry not found: %v", e.Err)
}
func (e *entryNotFoundError) Unwrap() error {
return e.Err
}
const (
// action entry file is "v1 \n"
hexSize = HashSize * 2
entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1 + 20 + 1
)
// verify controls whether to run the cache in verify mode.
// In verify mode, the cache always returns errMissing from Get
// but then double-checks in Put that the data being written
// exactly matches any existing entry. This provides an easy
// way to detect program behavior that would have been different
// had the cache entry been returned from Get.
//
// verify is enabled by setting the environment variable
// GODEBUG=gocacheverify=1.
var verify = false
var errVerifyMode = errors.New("gocacheverify=1")
// DebugTest is set when GODEBUG=gocachetest=1 is in the environment.
var DebugTest = false
func init() { initEnv() }
func initEnv() {
verify = false
debugHash = false
debug := strings.SplitSeq(os.Getenv("GODEBUG"), ",")
for f := range debug {
if f == "gocacheverify=1" {
verify = true
}
if f == "gocachehash=1" {
debugHash = true
}
if f == "gocachetest=1" {
DebugTest = true
}
}
}
// Get looks up the action ID in the cache,
// returning the corresponding output ID and file size, if any.
// Note that finding an output ID does not guarantee that the
// saved file for that output ID is still available.
func (c *Cache) Get(id ActionID) (Entry, error) {
if verify {
return Entry{}, &entryNotFoundError{Err: errVerifyMode}
}
return c.get(id)
}
type Entry struct {
OutputID OutputID
Size int64
Time time.Time
}
// get is Get but does not respect verify mode, so that Put can use it.
func (c *Cache) get(id ActionID) (Entry, error) {
missing := func(reason error) (Entry, error) {
return Entry{}, &entryNotFoundError{Err: reason}
}
f, err := os.Open(c.fileName(id, "a"))
if err != nil {
return missing(err)
}
defer f.Close()
entry := make([]byte, entrySize+1) // +1 to detect whether f is too long
if n, err := io.ReadFull(f, entry); n > entrySize {
return missing(errors.New("too long"))
} else if err != io.ErrUnexpectedEOF {
if err == io.EOF {
return missing(errors.New("file is empty"))
}
return missing(err)
} else if n < entrySize {
return missing(errors.New("entry file incomplete"))
}
if entry[0] != 'v' || entry[1] != '1' || entry[2] != ' ' || entry[3+hexSize] != ' ' || entry[3+hexSize+1+hexSize] != ' ' || entry[3+hexSize+1+hexSize+1+20] != ' ' || entry[entrySize-1] != '\n' {
return missing(errors.New("invalid header"))
}
eid, entry := entry[3:3+hexSize], entry[3+hexSize:]
eout, entry := entry[1:1+hexSize], entry[1+hexSize:]
esize, entry := entry[1:1+20], entry[1+20:]
//lint:ignore SA4006 See https://github.com/dominikh/go-tools/issues/465
etime, entry := entry[1:1+20], entry[1+20:]
var buf [HashSize]byte
if _, err := hex.Decode(buf[:], eid); err != nil {
return missing(fmt.Errorf("decoding ID: %v", err))
} else if buf != id {
return missing(errors.New("mismatched ID"))
}
if _, err := hex.Decode(buf[:], eout); err != nil {
return missing(fmt.Errorf("decoding output ID: %v", err))
}
i := 0
for i < len(esize) && esize[i] == ' ' {
i++
}
size, err := strconv.ParseInt(string(esize[i:]), 10, 64)
if err != nil {
return missing(fmt.Errorf("parsing size: %v", err))
} else if size < 0 {
return missing(errors.New("negative size"))
}
i = 0
for i < len(etime) && etime[i] == ' ' {
i++
}
tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
if err != nil {
return missing(fmt.Errorf("parsing timestamp: %v", err))
} else if tm < 0 {
return missing(errors.New("negative timestamp"))
}
c.used(c.fileName(id, "a"))
return Entry{buf, size, time.Unix(0, tm)}, nil
}
// GetFile looks up the action ID in the cache and returns
// the name of the corresponding data file.
func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
entry, err = c.Get(id)
if err != nil {
return "", Entry{}, err
}
file = c.OutputFile(entry.OutputID)
info, err := os.Stat(file)
if err != nil {
return "", Entry{}, &entryNotFoundError{Err: err}
}
if info.Size() != entry.Size {
return "", Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
}
return file, entry, nil
}
// GetBytes looks up the action ID in the cache and returns
// the corresponding output bytes.
// GetBytes should only be used for data that can be expected to fit in memory.
func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
entry, err := c.Get(id)
if err != nil {
return nil, entry, err
}
data, _ := os.ReadFile(c.OutputFile(entry.OutputID))
if sha256.Sum256(data) != entry.OutputID {
return nil, entry, &entryNotFoundError{Err: errors.New("bad checksum")}
}
return data, entry, nil
}
// OutputFile returns the name of the cache file storing output with the given OutputID.
func (c *Cache) OutputFile(out OutputID) string {
file := c.fileName(out, "d")
c.used(file)
return file
}
// Time constants for cache expiration.
//
// We set the mtime on a cache file on each use, but at most one per mtimeInterval (1 hour),
// to avoid causing many unnecessary inode updates. The mtimes therefore
// roughly reflect "time of last use" but may in fact be older by at most an hour.
//
// We scan the cache for entries to delete at most once per trimInterval (1 day).
//
// When we do scan the cache, we delete entries that have not been used for
// at least trimLimit (5 days). Statistics gathered from a month of usage by
// Go developers found that essentially all reuse of cached entries happened
// within 5 days of the previous reuse. See golang.org/issue/22990.
const (
mtimeInterval = 1 * time.Hour
trimInterval = 24 * time.Hour
trimLimit = 5 * 24 * time.Hour
)
// used makes a best-effort attempt to update mtime on file,
// so that mtime reflects cache access time.
//
// Because the reflection only needs to be approximate,
// and to reduce the amount of disk activity caused by using
// cache entries, used only updates the mtime if the current
// mtime is more than an hour old. This heuristic eliminates
// nearly all of the mtime updates that would otherwise happen,
// while still keeping the mtimes useful for cache trimming.
func (c *Cache) used(file string) {
info, err := os.Stat(file)
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
return
}
os.Chtimes(file, c.now(), c.now())
}
// Trim removes old cache entries that are likely not to be reused.
func (c *Cache) Trim() {
now := c.now()
// We maintain in dir/trim.txt the time of the last completed cache trim.
// If the cache has been trimmed recently enough, do nothing.
// This is the common case.
data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt"))
t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval {
return
}
// Trim each of the 256 subdirectories.
// We subtract an additional mtimeInterval
// to account for the imprecision of our "last used" mtimes.
cutoff := now.Add(-trimLimit - mtimeInterval)
for i := range 256 {
subdir := filepath.Join(c.dir, fmt.Sprintf("%02x", i))
c.trimSubdir(subdir, cutoff)
}
// Ignore errors from here: if we don't write the complete timestamp, the
// cache will appear older than it is, and we'll trim it again next time.
renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), fmt.Appendf(nil, "%d", now.Unix()), 0666)
}
// trimSubdir trims a single cache subdirectory.
func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
// Read all directory entries from subdir before removing
// any files, in case removing files invalidates the file offset
// in the directory scan. Also, ignore error from f.Readdirnames,
// because we don't care about reporting the error and we still
// want to process any entries found before the error.
f, err := os.Open(subdir)
if err != nil {
return
}
names, _ := f.Readdirnames(-1)
f.Close()
for _, name := range names {
// Remove only cache entries (xxxx-a and xxxx-d).
if !strings.HasSuffix(name, "-a") && !strings.HasSuffix(name, "-d") {
continue
}
entry := filepath.Join(subdir, name)
info, err := os.Stat(entry)
if err == nil && info.ModTime().Before(cutoff) {
os.Remove(entry)
}
}
}
// putIndexEntry adds an entry to the cache recording that executing the action
// with the given id produces an output with the given output id (hash) and size.
func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
// Note: We expect that for one reason or another it may happen
// that repeating an action produces a different output hash
// (for example, if the output contains a time stamp or temp dir name).
// While not ideal, this is also not a correctness problem, so we
// don't make a big deal about it. In particular, we leave the action
// cache entries writable specifically so that they can be overwritten.
//
// Setting GODEBUG=gocacheverify=1 does make a big deal:
// in verify mode we are double-checking that the cache entries
// are entirely reproducible. As just noted, this may be unrealistic
// in some cases but the check is also useful for shaking out real bugs.
entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
if verify && allowVerify {
old, err := c.get(id)
if err == nil && (old.OutputID != out || old.Size != size) {
// panic to show stack trace, so we can see what code is generating this cache entry.
msg := fmt.Sprintf("go: internal cache error: cache verify failed: id=%x changed:<<<\n%s\n>>>\nold: %x %d\nnew: %x %d", id, reverseHash(id), out, size, old.OutputID, old.Size)
panic(msg)
}
}
file := c.fileName(id, "a")
// Copy file to cache directory.
mode := os.O_WRONLY | os.O_CREATE
f, err := os.OpenFile(file, mode, 0666)
if err != nil {
return err
}
_, err = f.WriteString(entry)
if err == nil {
// Truncate the file only *after* writing it.
// (This should be a no-op, but truncate just in case of previous corruption.)
//
// This differs from ioutil.WriteFile, which truncates to 0 *before* writing
// via os.O_TRUNC. Truncating only after writing ensures that a second write
// of the same content to the same file is idempotent, and does not — even
// temporarily! — undo the effect of the first write.
err = f.Truncate(int64(len(entry)))
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
// TODO(bcmills): This Remove potentially races with another go command writing to file.
// Can we eliminate it?
os.Remove(file)
return err
}
os.Chtimes(file, c.now(), c.now()) // mainly for tests
return nil
}
// Put stores the given output in the cache as the output for the action ID.
// It may read file twice. The content of file must not change between the two passes.
func (c *Cache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
return c.put(id, file, true)
}
// PutNoVerify is like Put but disables the verify check
// when GODEBUG=goverifycache=1 is set.
// It is meant for data that is OK to cache but that we expect to vary slightly from run to run,
// like test output containing times and the like.
func (c *Cache) PutNoVerify(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
return c.put(id, file, false)
}
func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
// Compute output ID.
h := sha256.New()
if _, err := file.Seek(0, 0); err != nil {
return OutputID{}, 0, err
}
size, err := io.Copy(h, file)
if err != nil {
return OutputID{}, 0, err
}
var out OutputID
h.Sum(out[:0])
// Copy to cached output file (if not already present).
if err := c.copyFile(file, out, size); err != nil {
return out, size, err
}
// Add to cache index.
return out, size, c.putIndexEntry(id, out, size, allowVerify)
}
// PutBytes stores the given bytes in the cache as the output for the action ID.
func (c *Cache) PutBytes(id ActionID, data []byte) error {
_, _, err := c.Put(id, bytes.NewReader(data))
return err
}
// copyFile copies file into the cache, expecting it to have the given
// output ID and size, if that file is not present already.
func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
name := c.fileName(out, "d")
info, err := os.Stat(name)
if err == nil && info.Size() == size {
// Check hash.
if f, err := os.Open(name); err == nil {
h := sha256.New()
io.Copy(h, f)
f.Close()
var out2 OutputID
h.Sum(out2[:0])
if out == out2 {
return nil
}
}
// Hash did not match. Fall through and rewrite file.
}
// Copy file to cache directory.
mode := os.O_RDWR | os.O_CREATE
if err == nil && info.Size() > size { // shouldn't happen but fix in case
mode |= os.O_TRUNC
}
f, err := os.OpenFile(name, mode, 0666)
if err != nil {
return err
}
defer f.Close()
if size == 0 {
// File now exists with correct size.
// Only one possible zero-length file, so contents are OK too.
// Early return here makes sure there's a "last byte" for code below.
return nil
}
// From here on, if any of the I/O writing the file fails,
// we make a best-effort attempt to truncate the file f
// before returning, to avoid leaving bad bytes in the file.
// Copy file to f, but also into h to double-check hash.
if _, err := file.Seek(0, 0); err != nil {
f.Truncate(0)
return err
}
h := sha256.New()
w := io.MultiWriter(f, h)
if _, err := io.CopyN(w, file, size-1); err != nil {
f.Truncate(0)
return err
}
// Check last byte before writing it; writing it will make the size match
// what other processes expect to find and might cause them to start
// using the file.
buf := make([]byte, 1)
if _, err := file.Read(buf); err != nil {
f.Truncate(0)
return err
}
h.Write(buf)
sum := h.Sum(nil)
if !bytes.Equal(sum, out[:]) {
f.Truncate(0)
return fmt.Errorf("file content changed underfoot")
}
// Commit cache file entry.
if _, err := f.Write(buf); err != nil {
f.Truncate(0)
return err
}
if err := f.Close(); err != nil {
// Data might not have been written,
// but file may look like it is the right size.
// To be extra careful, remove cached file.
os.Remove(name)
return err
}
os.Chtimes(name, c.now(), c.now()) // mainly for tests
return nil
}
================================================
FILE: lintcmd/cache/cache_test.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"path/filepath"
"testing"
"time"
)
func init() {
verify = false // even if GODEBUG is set
}
func TestBasic(t *testing.T) {
dir := t.TempDir()
_, err := Open(filepath.Join(dir, "notexist"))
if err == nil {
t.Fatal(`Open("tmp/notexist") succeeded, want failure`)
}
cdir := filepath.Join(dir, "c1")
if err := os.Mkdir(cdir, 0777); err != nil {
t.Fatal(err)
}
c1, err := Open(cdir)
if err != nil {
t.Fatalf("Open(c1) (create): %v", err)
}
if err := c1.putIndexEntry(dummyID(1), dummyID(12), 13, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
if err := c1.putIndexEntry(dummyID(1), dummyID(2), 3, true); err != nil { // overwrite entry
t.Fatalf("addIndexEntry: %v", err)
}
if entry, err := c1.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
t.Fatalf("c1.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
}
c2, err := Open(cdir)
if err != nil {
t.Fatalf("Open(c2) (reuse): %v", err)
}
if entry, err := c2.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
t.Fatalf("c2.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
}
if err := c2.putIndexEntry(dummyID(2), dummyID(3), 4, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
if entry, err := c1.Get(dummyID(2)); err != nil || entry.OutputID != dummyID(3) || entry.Size != 4 {
t.Fatalf("c1.Get(2) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(3), 4)
}
}
func TestGrowth(t *testing.T) {
dir := t.TempDir()
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
n := 10000
if testing.Short() {
n = 10
}
for i := 0; i < n; i++ {
if err := c.putIndexEntry(dummyID(i), dummyID(i*99), int64(i)*101, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
id := ActionID(dummyID(i))
entry, err := c.Get(id)
if err != nil {
t.Fatalf("Get(%x): %v", id, err)
}
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
t.Errorf("Get(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
}
}
for i := 0; i < n; i++ {
id := ActionID(dummyID(i))
entry, err := c.Get(id)
if err != nil {
t.Fatalf("Get2(%x): %v", id, err)
}
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
t.Errorf("Get2(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
}
}
}
func TestVerifyPanic(t *testing.T) {
os.Setenv("GODEBUG", "gocacheverify=1")
initEnv()
defer func() {
os.Unsetenv("GODEBUG")
verify = false
}()
if !verify {
t.Fatal("initEnv did not set verify")
}
dir := t.TempDir()
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
id := ActionID(dummyID(1))
if err := c.PutBytes(id, []byte("abc")); err != nil {
t.Fatal(err)
}
defer func() {
if err := recover(); err != nil {
t.Log(err)
return
}
}()
c.PutBytes(id, []byte("def"))
t.Fatal("mismatched Put did not panic in verify mode")
}
func dummyID(x int) [HashSize]byte {
var out [HashSize]byte
binary.LittleEndian.PutUint64(out[:], uint64(x))
return out
}
func TestCacheTrim(t *testing.T) {
dir := t.TempDir()
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
const start = 1000000000
now := int64(start)
c.now = func() time.Time { return time.Unix(now, 0) }
checkTime := func(name string, mtime int64) {
t.Helper()
file := filepath.Join(c.dir, name[:2], name)
info, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if info.ModTime().Unix() != mtime {
t.Fatalf("%s mtime = %d, want %d", name, info.ModTime().Unix(), mtime)
}
}
id := ActionID(dummyID(1))
c.PutBytes(id, []byte("abc"))
entry, _ := c.Get(id)
c.PutBytes(ActionID(dummyID(2)), []byte("def"))
mtime := now
checkTime(fmt.Sprintf("%x-a", id), mtime)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
// Get should not change recent mtimes.
now = start + 10
c.Get(id)
checkTime(fmt.Sprintf("%x-a", id), mtime)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
// Get should change distant mtimes.
now = start + 5000
mtime2 := now
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
checkTime(fmt.Sprintf("%x-a", id), mtime2)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2)
// Trim should leave everything alone: it's all too new.
c.Trim()
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
// Trim less than a day later should not do any work at all.
now = start + 80000
c.Trim()
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data2, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, data2) {
t.Fatalf("second trim did work: %q -> %q", data, data2)
}
// Fast forward and do another trim just before the 5 day cutoff.
// Note that because of usedQuantum the cutoff is actually 5 days + 1 hour.
// We used c.Get(id) just now, so 5 days later it should still be kept.
// On the other hand almost a full day has gone by since we wrote dummyID(2)
// and we haven't looked at it since, so 5 days later it should be gone.
now += 5 * 86400
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
c.Trim()
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
mtime3 := now
if _, err := c.Get(dummyID(2)); err == nil { // haven't done a Get for this since original write above
t.Fatalf("Trim did not remove dummyID(2)")
}
// The c.Get(id) refreshed id's mtime again.
// Check that another 5 days later it is still not gone,
// but check by using checkTime, which doesn't bring mtime forward.
now += 5 * 86400
c.Trim()
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
// Half a day later Trim should still be a no-op, because there was a Trim recently.
// Even though the entry for id is now old enough to be trimmed,
// it gets a reprieve until the time comes for a new Trim scan.
now += 86400 / 2
c.Trim()
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
// Another half a day later, Trim should actually run, and it should remove id.
now += 86400/2 + 1
c.Trim()
if _, err := c.Get(dummyID(1)); err == nil {
t.Fatal("Trim did not remove dummyID(1)")
}
}
================================================
FILE: lintcmd/cache/default.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"fmt"
"log"
"os"
"path/filepath"
"sync"
)
// Default returns the default cache to use.
func Default() (*Cache, error) {
defaultOnce.Do(initDefaultCache)
return defaultCache, defaultDirErr
}
var (
defaultOnce sync.Once
defaultCache *Cache
)
// cacheREADME is a message stored in a README in the cache directory.
// Because the cache lives outside the normal Go trees, we leave the
// README as a courtesy to explain where it came from.
const cacheREADME = `This directory holds cached build artifacts from staticcheck.
`
// initDefaultCache does the work of finding the default cache
// the first time Default is called.
func initDefaultCache() {
dir := DefaultDir()
if err := os.MkdirAll(dir, 0777); err != nil {
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
if _, err := os.Stat(filepath.Join(dir, "README")); err != nil {
// Best effort.
os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
}
c, err := Open(dir)
if err != nil {
log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
defaultCache = c
}
var (
defaultDirOnce sync.Once
defaultDir string
defaultDirErr error
)
// DefaultDir returns the effective STATICCHECK_CACHE setting.
func DefaultDir() string {
// Save the result of the first call to DefaultDir for later use in
// initDefaultCache. cmd/go/main.go explicitly sets GOCACHE so that
// subprocesses will inherit it, but that means initDefaultCache can't
// otherwise distinguish between an explicit "off" and a UserCacheDir error.
defaultDirOnce.Do(func() {
defaultDir = os.Getenv("STATICCHECK_CACHE")
if filepath.IsAbs(defaultDir) {
return
}
if defaultDir != "" {
defaultDirErr = fmt.Errorf("STATICCHECK_CACHE is not an absolute path")
return
}
// Compute default location.
dir, err := os.UserCacheDir()
if err != nil {
defaultDirErr = fmt.Errorf("STATICCHECK_CACHE is not defined and %v", err)
return
}
defaultDir = filepath.Join(dir, "staticcheck")
})
return defaultDir
}
================================================
FILE: lintcmd/cache/hash.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"bytes"
"crypto/sha256"
"fmt"
"hash"
"io"
"os"
"sync"
)
var debugHash = false // set when GODEBUG=gocachehash=1
// HashSize is the number of bytes in a hash.
const HashSize = 32
// A Hash provides access to the canonical hash function used to index the cache.
// The current implementation uses salted SHA256, but clients must not assume this.
type Hash struct {
h hash.Hash
name string // for debugging
buf *bytes.Buffer // for verify
}
// Subkey returns an action ID corresponding to mixing a parent
// action ID with a string description of the subkey.
func Subkey(parent ActionID, desc string) ActionID {
h := sha256.New()
h.Write([]byte("subkey:"))
h.Write(parent[:])
h.Write([]byte(desc))
var out ActionID
h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH subkey %x %q = %x\n", parent, desc, out)
}
if verify {
hashDebug.Lock()
hashDebug.m[out] = fmt.Sprintf("subkey %x %q", parent, desc)
hashDebug.Unlock()
}
return out
}
// NewHash returns a new Hash.
// The caller is expected to Write data to it and then call Sum.
func (c *Cache) NewHash(name string) *Hash {
h := &Hash{h: sha256.New(), name: name}
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name)
}
h.Write(c.salt)
if verify {
h.buf = new(bytes.Buffer)
}
return h
}
// Write writes data to the running hash.
func (h *Hash) Write(b []byte) (int, error) {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b)
}
if h.buf != nil {
h.buf.Write(b)
}
return h.h.Write(b)
}
// Sum returns the hash of the data written previously.
func (h *Hash) Sum() [HashSize]byte {
var out [HashSize]byte
h.h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out)
}
if h.buf != nil {
hashDebug.Lock()
if hashDebug.m == nil {
hashDebug.m = make(map[[HashSize]byte]string)
}
hashDebug.m[out] = h.buf.String()
hashDebug.Unlock()
}
return out
}
// In GODEBUG=gocacheverify=1 mode,
// hashDebug holds the input to every computed hash ID,
// so that we can work backward from the ID involved in a
// cache entry mismatch to a description of what should be there.
var hashDebug struct {
sync.Mutex
m map[[HashSize]byte]string
}
// reverseHash returns the input used to compute the hash id.
func reverseHash(id [HashSize]byte) string {
hashDebug.Lock()
s := hashDebug.m[id]
hashDebug.Unlock()
return s
}
var hashFileCache struct {
sync.Mutex
m map[string][HashSize]byte
}
// FileHash returns the hash of the named file.
// It caches repeated lookups for a given file,
// and the cache entry for a file can be initialized
// using SetFileHash.
// The hash used by FileHash is not the same as
// the hash used by NewHash.
func FileHash(file string) ([HashSize]byte, error) {
hashFileCache.Lock()
out, ok := hashFileCache.m[file]
hashFileCache.Unlock()
if ok {
return out, nil
}
h := sha256.New()
f, err := os.Open(file)
if err != nil {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err)
}
return [HashSize]byte{}, err
}
_, err = io.Copy(h, f)
f.Close()
if err != nil {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err)
}
return [HashSize]byte{}, err
}
h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out)
}
SetFileHash(file, out)
return out, nil
}
// SetFileHash sets the hash returned by FileHash for file.
func SetFileHash(file string, sum [HashSize]byte) {
hashFileCache.Lock()
if hashFileCache.m == nil {
hashFileCache.m = make(map[string][HashSize]byte)
}
hashFileCache.m[file] = sum
hashFileCache.Unlock()
}
================================================
FILE: lintcmd/cache/hash_test.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"fmt"
"os"
"testing"
)
func TestHash(t *testing.T) {
c := &Cache{}
h := c.NewHash("alice")
h.Write([]byte("hello world"))
sum := fmt.Sprintf("%x", h.Sum())
want := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
if sum != want {
t.Errorf("hash(hello world) = %v, want %v", sum, want)
}
}
func TestHashFile(t *testing.T) {
f, err := os.CreateTemp("", "cmd-go-test-")
if err != nil {
t.Fatal(err)
}
name := f.Name()
fmt.Fprintf(f, "hello world")
defer os.Remove(name)
if err := f.Close(); err != nil {
t.Fatal(err)
}
var h ActionID // make sure hash result is assignable to ActionID
h, err = FileHash(name)
if err != nil {
t.Fatal(err)
}
sum := fmt.Sprintf("%x", h)
want := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
if sum != want {
t.Errorf("hash(hello world) = %v, want %v", sum, want)
}
}
================================================
FILE: lintcmd/cmd.go
================================================
// Package lintcmd implements the frontend of an analysis runner.
// It serves as the entry-point for the staticcheck command, and can also be used to implement custom linters that behave like staticcheck.
package lintcmd
import (
"bufio"
"encoding/gob"
"flag"
"fmt"
"go/token"
stdversion "go/version"
"io"
"log"
"maps"
"os"
"path/filepath"
"reflect"
"runtime"
"runtime/pprof"
"runtime/trace"
"slices"
"sort"
"strings"
"sync"
"time"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/config"
"honnef.co/go/tools/go/loader"
"honnef.co/go/tools/lintcmd/version"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/buildutil"
)
type buildConfig struct {
Name string
Envs []string
Flags []string
}
// Command represents a linter command line tool.
type Command struct {
name string
analyzers map[string]*lint.Analyzer
version string
machineVersion string
flags struct {
fs *flag.FlagSet
tags string
tests bool
showIgnored bool
formatter string
// mutually exclusive mode flags
explain string
printVersion bool
listChecks bool
merge bool
matrix bool
debugCpuprofile string
debugMemprofile string
debugVersion bool
debugNoCompileErrors bool
debugMeasureAnalyzers string
debugTrace string
checks list
fail list
goVersion versionFlag
}
}
// NewCommand returns a new Command.
func NewCommand(name string) *Command {
cmd := &Command{
name: name,
analyzers: map[string]*lint.Analyzer{},
version: "devel",
machineVersion: "devel",
}
cmd.initFlagSet(name)
return cmd
}
// SetVersion sets the command's version.
// It is divided into a human part and a machine part.
// For example, Staticcheck 2020.2.1 had the human version "2020.2.1" and the machine version "v0.1.1".
// If you only use Semver, you can set both parts to the same value.
//
// Calling this method is optional. Both versions default to "devel", and we'll attempt to deduce more version information from the Go module.
func (cmd *Command) SetVersion(human, machine string) {
cmd.version = human
cmd.machineVersion = machine
}
// FlagSet returns the command's flag set.
// This can be used to add additional command line arguments.
func (cmd *Command) FlagSet() *flag.FlagSet {
return cmd.flags.fs
}
// AddAnalyzers adds analyzers to the command.
// These are lint.Analyzer analyzers, which wrap analysis.Analyzer analyzers, bundling them with structured documentation.
//
// To add analysis.Analyzer analyzers without providing structured documentation, use AddBareAnalyzers.
func (cmd *Command) AddAnalyzers(as ...*lint.Analyzer) {
for _, a := range as {
cmd.analyzers[a.Analyzer.Name] = a
}
}
// AddBareAnalyzers adds bare analyzers to the command.
func (cmd *Command) AddBareAnalyzers(as ...*analysis.Analyzer) {
for _, a := range as {
var title, text string
if idx := strings.Index(a.Doc, "\n\n"); idx > -1 {
title = a.Doc[:idx]
text = a.Doc[idx+2:]
}
doc := &lint.RawDocumentation{
Title: title,
Text: text,
Severity: lint.SeverityWarning,
}
cmd.analyzers[a.Name] = &lint.Analyzer{
Doc: doc,
Analyzer: a,
}
}
}
func (cmd *Command) initFlagSet(name string) {
flags := flag.NewFlagSet("", flag.ExitOnError)
cmd.flags.fs = flags
flags.Usage = usage(name, flags)
flags.StringVar(&cmd.flags.tags, "tags", "", "List of `build tags`")
flags.BoolVar(&cmd.flags.tests, "tests", true, "Include tests")
flags.BoolVar(&cmd.flags.printVersion, "version", false, "Print version and exit")
flags.BoolVar(&cmd.flags.showIgnored, "show-ignored", false, "Don't filter ignored diagnostics")
flags.StringVar(&cmd.flags.formatter, "f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
flags.StringVar(&cmd.flags.explain, "explain", "", "Print description of `check`")
flags.BoolVar(&cmd.flags.listChecks, "list-checks", false, "List all available checks")
flags.BoolVar(&cmd.flags.merge, "merge", false, "Merge results of multiple Staticcheck runs")
flags.BoolVar(&cmd.flags.matrix, "matrix", false, "Read a build config matrix from stdin")
flags.StringVar(&cmd.flags.debugCpuprofile, "debug.cpuprofile", "", "Write CPU profile to `file`")
flags.StringVar(&cmd.flags.debugMemprofile, "debug.memprofile", "", "Write memory profile to `file`")
flags.BoolVar(&cmd.flags.debugVersion, "debug.version", false, "Print detailed version information about this program")
flags.BoolVar(&cmd.flags.debugNoCompileErrors, "debug.no-compile-errors", false, "Don't print compile errors")
flags.StringVar(&cmd.flags.debugMeasureAnalyzers, "debug.measure-analyzers", "", "Write analysis measurements to `file`. `file` will be opened for appending if it already exists.")
flags.StringVar(&cmd.flags.debugTrace, "debug.trace", "", "Write trace to `file`")
cmd.flags.checks = list{"inherit"}
cmd.flags.fail = list{"all"}
cmd.flags.goVersion = versionFlag("module")
flags.Var(&cmd.flags.checks, "checks", "Comma-separated list of `checks` to enable.")
flags.Var(&cmd.flags.fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
flags.Var(&cmd.flags.goVersion, "go", "Target Go `version` in the format '1.x', or the literal 'module' to use the module's Go version")
}
type list []string
func (list *list) String() string {
return `"` + strings.Join(*list, ",") + `"`
}
func (list *list) Set(s string) error {
if s == "" {
*list = nil
return nil
}
elems := strings.Split(s, ",")
for i, elem := range elems {
elems[i] = strings.TrimSpace(elem)
}
*list = elems
return nil
}
type versionFlag string
func (v *versionFlag) String() string {
return fmt.Sprintf("%q", string(*v))
}
func (v *versionFlag) Set(s string) error {
if s == "module" {
*v = "module"
} else {
orig := s
if !strings.HasPrefix(s, "go") {
s = "go" + s
}
if stdversion.IsValid(s) {
*v = versionFlag(s)
} else {
return fmt.Errorf("%q is not a valid Go version", orig)
}
}
return nil
}
// ParseFlags parses command line flags.
// It must be called before calling Run.
// After calling ParseFlags, the values of flags can be accessed.
//
// Example:
//
// cmd.ParseFlags(os.Args[1:])
func (cmd *Command) ParseFlags(args []string) {
cmd.flags.fs.Parse(args)
}
// diagnosticDescriptor represents the uniquely identifying information of diagnostics.
type diagnosticDescriptor struct {
Position token.Position
End token.Position
Category string
Message string
}
func (diag diagnostic) descriptor() diagnosticDescriptor {
return diagnosticDescriptor{
Position: diag.Position,
End: diag.End,
Category: diag.Category,
Message: diag.Message,
}
}
type run struct {
checkedFiles map[string]struct{}
diagnostics map[diagnosticDescriptor]diagnostic
}
func runFromLintResult(res lintResult) run {
out := run{
checkedFiles: map[string]struct{}{},
diagnostics: map[diagnosticDescriptor]diagnostic{},
}
for _, cf := range res.CheckedFiles {
out.checkedFiles[cf] = struct{}{}
}
for _, diag := range res.Diagnostics {
out.diagnostics[diag.descriptor()] = diag
}
return out
}
func decodeGob(br io.ByteReader) ([]run, error) {
var runs []run
for {
var res lintResult
if err := gob.NewDecoder(br.(io.Reader)).Decode(&res); err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
runs = append(runs, runFromLintResult(res))
}
return runs, nil
}
// Execute runs all registered analyzers and reports their findings.
// The status code returned can be used for os.Exit(cmd.Execute()).
func (cmd *Command) Execute() int {
// Set up profiling and tracing
if path := cmd.flags.debugCpuprofile; path != "" {
f, err := os.Create(path)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
}
if path := cmd.flags.debugTrace; path != "" {
f, err := os.Create(path)
if err != nil {
log.Fatal(err)
}
trace.Start(f)
}
// Update the default config's list of enabled checks
defaultChecks := []string{"all"}
for _, a := range cmd.analyzers {
if a.Doc.NonDefault {
defaultChecks = append(defaultChecks, "-"+a.Analyzer.Name)
}
}
config.DefaultConfig.Checks = defaultChecks
// Run the appropriate mode
var exit int
switch {
case cmd.flags.debugVersion:
exit = cmd.printDebugVersion()
case cmd.flags.listChecks:
exit = cmd.listChecks()
case cmd.flags.printVersion:
exit = cmd.printVersion()
case cmd.flags.explain != "":
exit = cmd.explain()
case cmd.flags.merge:
exit = cmd.merge()
default:
exit = cmd.lint()
}
// Stop profiling
if cmd.flags.debugCpuprofile != "" {
pprof.StopCPUProfile()
}
if path := cmd.flags.debugMemprofile; path != "" {
f, err := os.Create(path)
if err != nil {
panic(err)
}
runtime.GC()
pprof.WriteHeapProfile(f)
}
if cmd.flags.debugTrace != "" {
trace.Stop()
}
return exit
}
// Run runs all registered analyzers and reports their findings.
// It always calls os.Exit and does not return.
func (cmd *Command) Run() {
os.Exit(cmd.Execute())
}
func (cmd *Command) printDebugVersion() int {
version.Verbose(cmd.version, cmd.machineVersion)
return 0
}
func (cmd *Command) listChecks() int {
cs := slices.Collect(maps.Values(cmd.analyzers))
sort.Slice(cs, func(i, j int) bool {
return cs[i].Analyzer.Name < cs[j].Analyzer.Name
})
for _, c := range cs {
var title string
if c.Doc != nil {
title = c.Doc.Compile().Title
}
fmt.Printf("%s %s\n", c.Analyzer.Name, title)
}
return 0
}
func (cmd *Command) printVersion() int {
version.Print(cmd.version, cmd.machineVersion)
return 0
}
func (cmd *Command) explain() int {
explain := cmd.flags.explain
check, ok := cmd.analyzers[explain]
if !ok {
fmt.Fprintln(os.Stderr, "Couldn't find check", explain)
return 1
}
if check.Analyzer.Doc == "" {
fmt.Fprintln(os.Stderr, explain, "has no documentation")
return 1
}
fmt.Println(check.Doc.Compile())
fmt.Println("Online documentation\n https://staticcheck.dev/docs/checks#" + check.Analyzer.Name)
return 0
}
func (cmd *Command) merge() int {
var runs []run
if len(cmd.flags.fs.Args()) == 0 {
var err error
runs, err = decodeGob(bufio.NewReader(os.Stdin))
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("couldn't parse stdin: %s", err))
return 1
}
} else {
for _, path := range cmd.flags.fs.Args() {
someRuns, err := func(path string) ([]run, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
br := bufio.NewReader(f)
return decodeGob(br)
}(path)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("couldn't parse file %s: %s", path, err))
return 1
}
runs = append(runs, someRuns...)
}
}
relevantDiagnostics := mergeRuns(runs)
cs := slices.Collect(maps.Values(cmd.analyzers))
return cmd.printDiagnostics(cs, relevantDiagnostics)
}
func (cmd *Command) lint() int {
switch cmd.flags.formatter {
case "text", "stylish", "json", "sarif", "binary", "null":
default:
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter)
return 2
}
var bconfs []buildConfig
if cmd.flags.matrix {
if cmd.flags.tags != "" {
fmt.Fprintln(os.Stderr, "cannot use -matrix and -tags together")
return 2
}
var err error
bconfs, err = parseBuildConfigs(os.Stdin)
if err != nil {
if perr, ok := err.(parseBuildConfigError); ok {
fmt.Fprintf(os.Stderr, ":%d couldn't parse build matrix: %s\n", perr.line, perr.err)
} else {
fmt.Fprintln(os.Stderr, err)
}
return 2
}
} else {
bc := buildConfig{}
if cmd.flags.tags != "" {
// Validate that the tags argument is well-formed. go/packages
// doesn't detect malformed build flags and returns unhelpful
// errors.
tf := buildutil.TagsFlag{}
if err := tf.Set(cmd.flags.tags); err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", cmd.flags.tags, err))
return 1
}
bc.Flags = []string{"-tags", cmd.flags.tags}
}
bconfs = append(bconfs, bc)
}
var measureAnalyzers func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration)
if path := cmd.flags.debugMeasureAnalyzers; path != "" {
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
mu := &sync.Mutex{}
measureAnalyzers = func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) {
mu.Lock()
defer mu.Unlock()
// FIXME(dh): print pkg.ID
if _, err := fmt.Fprintf(f, "%s\t%s\t%d\n", analysis.Name, pkg, d.Nanoseconds()); err != nil {
log.Println("error writing analysis measurements:", err)
}
}
}
var runs []run
cs := slices.Collect(maps.Values(cmd.analyzers))
opts := options{
analyzers: cs,
patterns: cmd.flags.fs.Args(),
lintTests: cmd.flags.tests,
goVersion: string(cmd.flags.goVersion),
config: config.Config{
Checks: cmd.flags.checks,
},
printAnalyzerMeasurement: measureAnalyzers,
}
l, err := newLinter(opts)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
for _, bconf := range bconfs {
res, err := l.run(bconf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
for _, w := range res.Warnings {
fmt.Fprintln(os.Stderr, "warning:", w)
}
cwd, err := os.Getwd()
if err != nil {
cwd = ""
}
relPath := func(s string) string {
if cwd == "" {
return filepath.ToSlash(s)
}
out, err := filepath.Rel(cwd, s)
if err != nil {
return filepath.ToSlash(s)
}
return filepath.ToSlash(out)
}
if cmd.flags.formatter == "binary" {
for i, s := range res.CheckedFiles {
res.CheckedFiles[i] = relPath(s)
}
for i := range res.Diagnostics {
// We turn all paths into relative, /-separated paths. This is to make -merge work correctly when
// merging runs from different OSs, with different absolute paths.
//
// We zero out Offset, because checkouts of code on different OSs may have different kinds of
// newlines and thus different offsets. We don't ever make use of the Offset, anyway. Line and
// column numbers are precomputed.
d := &res.Diagnostics[i]
d.Position.Filename = relPath(d.Position.Filename)
d.Position.Offset = 0
d.End.Filename = relPath(d.End.Filename)
d.End.Offset = 0
for j := range d.Related {
r := &d.Related[j]
r.Position.Filename = relPath(r.Position.Filename)
r.Position.Offset = 0
r.End.Filename = relPath(r.End.Filename)
r.End.Offset = 0
}
}
err := gob.NewEncoder(os.Stdout).Encode(res)
if err != nil {
fmt.Fprintf(os.Stderr, "failed writing output: %s\n", err)
return 2
}
} else {
runs = append(runs, runFromLintResult(res))
}
}
l.cache.Trim()
if cmd.flags.formatter != "binary" {
diags := mergeRuns(runs)
return cmd.printDiagnostics(cs, diags)
}
return 0
}
func mergeRuns(runs []run) []diagnostic {
var relevantDiagnostics []diagnostic
for _, r := range runs {
for _, diag := range r.diagnostics {
switch diag.MergeIf {
case lint.MergeIfAny:
relevantDiagnostics = append(relevantDiagnostics, diag)
case lint.MergeIfAll:
doPrint := true
for _, r := range runs {
if _, ok := r.checkedFiles[diag.Position.Filename]; ok {
if _, ok := r.diagnostics[diag.descriptor()]; !ok {
doPrint = false
}
}
}
if doPrint {
relevantDiagnostics = append(relevantDiagnostics, diag)
}
}
}
}
return relevantDiagnostics
}
// printDiagnostics prints the diagnostics and exits the process.
func (cmd *Command) printDiagnostics(cs []*lint.Analyzer, diagnostics []diagnostic) int {
if len(diagnostics) > 1 {
sort.Slice(diagnostics, func(i, j int) bool {
di := diagnostics[i]
dj := diagnostics[j]
pi := di.Position
pj := dj.Position
if pi.Filename != pj.Filename {
return pi.Filename < pj.Filename
}
if pi.Line != pj.Line {
return pi.Line < pj.Line
}
if pi.Column != pj.Column {
return pi.Column < pj.Column
}
if di.Message != dj.Message {
return di.Message < dj.Message
}
if di.BuildName != dj.BuildName {
return di.BuildName < dj.BuildName
}
return di.Category < dj.Category
})
filtered := []diagnostic{
diagnostics[0],
}
builds := []map[string]struct{}{
{diagnostics[0].BuildName: {}},
}
for _, diag := range diagnostics[1:] {
// We may encounter duplicate diagnostics because one file
// can be part of many packages, and because multiple
// build configurations may check the same files.
if !filtered[len(filtered)-1].equal(diag) {
if filtered[len(filtered)-1].descriptor() == diag.descriptor() {
// Diagnostics only differ in build name, track new name
builds[len(filtered)-1][diag.BuildName] = struct{}{}
} else {
filtered = append(filtered, diag)
builds = append(builds, map[string]struct{}{})
builds[len(filtered)-1][diag.BuildName] = struct{}{}
}
}
}
var names []string
for i := range filtered {
names = names[:0]
for k := range builds[i] {
names = append(names, k)
}
sort.Strings(names)
filtered[i].BuildName = strings.Join(names, ",")
}
diagnostics = filtered
}
var f formatter
switch cmd.flags.formatter {
case "text":
f = textFormatter{W: os.Stdout}
case "stylish":
f = &stylishFormatter{W: os.Stdout}
case "json":
f = jsonFormatter{W: os.Stdout}
case "sarif":
f = &sarifFormatter{
driverName: cmd.name,
driverVersion: cmd.version,
}
if cmd.name == "staticcheck" {
f.(*sarifFormatter).driverName = "Staticcheck"
f.(*sarifFormatter).driverWebsite = "https://staticcheck.dev"
}
case "binary":
fmt.Fprintln(os.Stderr, "'-f binary' not supported in this context")
return 2
case "null":
f = nullFormatter{}
default:
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter)
return 2
}
fail := cmd.flags.fail
analyzerNames := make([]string, len(cs))
for i, a := range cs {
analyzerNames[i] = a.Analyzer.Name
}
shouldExit := filterAnalyzerNames(analyzerNames, fail)
shouldExit["staticcheck"] = true
shouldExit["compile"] = true
shouldExit["config"] = true
var (
numErrors int
numWarnings int
numIgnored int
)
notIgnored := make([]diagnostic, 0, len(diagnostics))
for _, diag := range diagnostics {
if diag.Category == "compile" && cmd.flags.debugNoCompileErrors {
continue
}
if diag.Severity == severityIgnored && !cmd.flags.showIgnored {
numIgnored++
continue
}
if shouldExit[diag.Category] {
numErrors++
} else {
diag.Severity = severityWarning
numWarnings++
}
notIgnored = append(notIgnored, diag)
}
f.Format(cs, notIgnored)
if f, ok := f.(statter); ok {
f.Stats(len(diagnostics), numErrors, numWarnings, numIgnored)
}
if numErrors > 0 {
if _, ok := f.(*sarifFormatter); ok {
// When emitting SARIF, finding errors is considered success.
return 0
} else {
return 1
}
}
return 0
}
func usage(name string, fs *flag.FlagSet) func() {
return func() {
fmt.Fprintf(os.Stderr, "Usage: %s [flags] [packages]\n", name)
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "Flags:")
printDefaults(fs)
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "For help about specifying packages, see 'go help packages'")
}
}
// isZeroValue determines whether the string represents the zero
// value for a flag.
//
// this function has been copied from the Go standard library's 'flag' package.
func isZeroValue(f *flag.Flag, value string) bool {
// Build a zero value of the flag's Value type, and see if the
// result of calling its String method equals the value passed in.
// This works unless the Value type is itself an interface type.
typ := reflect.TypeOf(f.Value)
var z reflect.Value
if typ.Kind() == reflect.Pointer {
z = reflect.New(typ.Elem())
} else {
z = reflect.Zero(typ)
}
return value == z.Interface().(flag.Value).String()
}
// this function has been copied from the Go standard library's 'flag' package and modified to skip debug flags.
func printDefaults(fs *flag.FlagSet) {
fs.VisitAll(func(f *flag.Flag) {
// Don't print debug flags
if strings.HasPrefix(f.Name, "debug.") {
return
}
var b strings.Builder
fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments.
name, usage := flag.UnquoteUsage(f)
if len(name) > 0 {
b.WriteString(" ")
b.WriteString(name)
}
// Boolean flags of one ASCII letter are so common we
// treat them specially, putting their usage on the same line.
if b.Len() <= 4 { // space, space, '-', 'x'.
b.WriteString("\t")
} else {
// Four spaces before the tab triggers good alignment
// for both 4- and 8-space tab stops.
b.WriteString("\n \t")
}
b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t"))
if !isZeroValue(f, f.DefValue) {
if T := reflect.TypeOf(f.Value); T.Name() == "*stringValue" && T.PkgPath() == "flag" {
// put quotes on the value
fmt.Fprintf(&b, " (default %q)", f.DefValue)
} else {
fmt.Fprintf(&b, " (default %v)", f.DefValue)
}
}
fmt.Fprint(fs.Output(), b.String(), "\n")
})
}
================================================
FILE: lintcmd/cmd_test.go
================================================
package lintcmd
import (
"go/token"
"testing"
)
func TestParsePos(t *testing.T) {
var tests = []struct {
in string
out token.Position
}{
{
"/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go:1",
token.Position{
Filename: "/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go",
Line: 1,
Column: 0,
},
},
{
"/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go:1:",
token.Position{
Filename: "/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go",
Line: 1,
Column: 0,
},
},
{
"/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go:23:43",
token.Position{
Filename: "/tmp/gopackages280076669/go-build/net/cgo_linux.cgo1.go",
Line: 23,
Column: 43,
},
},
}
for _, tt := range tests {
res, _, _ := parsePos(tt.in)
if res != tt.out {
t.Errorf("failed to parse %q correctly", tt.in)
}
}
}
================================================
FILE: lintcmd/config.go
================================================
package lintcmd
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"unicode"
)
type parseBuildConfigError struct {
line int
err error
}
func (err parseBuildConfigError) Error() string { return err.err.Error() }
func parseBuildConfigs(r io.Reader) ([]buildConfig, error) {
var builds []buildConfig
br := bufio.NewReader(r)
i := 0
for {
line, err := br.ReadString('\n')
if err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
name, envs, flags, err := parseBuildConfig(line)
if err != nil {
return nil, parseBuildConfigError{line: i + 1, err: err}
}
bc := buildConfig{
Name: name,
Envs: envs,
Flags: flags,
}
builds = append(builds, bc)
i++
}
return builds, nil
}
func parseBuildConfig(line string) (name string, envs []string, flags []string, err error) {
if line == "" {
return "", nil, nil, errors.New("couldn't parse empty build config")
}
if strings.Index(line, ":") == len(line)-1 {
name = line[:len(line)-1]
} else {
before, after, ok := strings.Cut(line, ": ")
if !ok {
return name, envs, flags, errors.New("missing build name")
}
name = before
var buf []rune
var inQuote bool
args := &envs
for _, r := range strings.TrimSpace(after) {
switch r {
case ' ':
if inQuote {
buf = append(buf, r)
} else if len(buf) != 0 {
if buf[0] == '-' {
args = &flags
}
*args = append(*args, string(buf))
buf = buf[:0]
}
case '"':
inQuote = !inQuote
default:
buf = append(buf, r)
}
}
if len(buf) > 0 {
if inQuote {
return "", nil, nil, errors.New("unterminated quoted string")
}
if buf[0] == '-' {
args = &flags
}
*args = append(*args, string(buf))
}
}
for _, r := range name {
if !(r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)) {
return "", nil, nil, fmt.Errorf("invalid build name %q", name)
}
}
return name, envs, flags, nil
}
================================================
FILE: lintcmd/config_test.go
================================================
//go:build go1.18
package lintcmd
import (
"testing"
)
var buildConfigTests = []struct {
in string
name string
envs []string
flags []string
invalid bool
}{
{
`some_name: ENV1=foo ENV_2=bar ENV3="foo bar baz" ENV4=foo"bar" -flag1 -flag2= -flag3=value -flag4="some value" -flag5=some" value "test "-flag6=1"`,
"some_name",
[]string{"ENV1=foo", "ENV_2=bar", "ENV3=foo bar baz", "ENV4=foobar"},
[]string{"-flag1", "-flag2=", "-flag3=value", "-flag4=some value", "-flag5=some value test", "-flag6=1"},
false,
},
{
`some_name: ENV1=foo -tags bar baz=meow`,
"some_name",
[]string{"ENV1=foo"},
[]string{"-tags", "bar", "baz=meow"},
false,
},
{
"some_name:",
"some_name",
nil,
nil,
false,
},
{
"some name:",
"",
nil,
nil,
true,
},
{
"",
"",
nil,
nil,
true,
},
}
func FuzzParseBuildConfig(f *testing.F) {
equal := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
for _, tt := range buildConfigTests {
f.Add(tt.in)
name, envs, flags, err := parseBuildConfig(tt.in)
if err != nil {
if tt.invalid {
continue
}
f.Fatalf("input %q failed to parse: %s", tt.in, err)
}
if tt.invalid {
f.Fatalf("expected input %q to fail but it didn't", tt.in)
}
if name != tt.name {
f.Fatalf("got name %q, want %q", name, tt.name)
}
if !equal(envs, tt.envs) {
f.Fatalf("got environment %#v, want %#v", envs, tt.envs)
}
if !equal(flags, tt.flags) {
f.Fatalf("got flags %#v, want %#v", flags, tt.flags)
}
}
f.Fuzz(func(t *testing.T, in string) {
parseBuildConfig(in)
})
}
================================================
FILE: lintcmd/directives.go
================================================
package lintcmd
import (
"strings"
"honnef.co/go/tools/lintcmd/runner"
)
func parseDirectives(dirs []runner.SerializedDirective) ([]ignore, []diagnostic) {
var ignores []ignore
var diagnostics []diagnostic
for _, dir := range dirs {
cmd := dir.Command
args := dir.Arguments
switch cmd {
case "ignore", "file-ignore":
if len(args) < 2 {
p := diagnostic{
Diagnostic: runner.Diagnostic{
Position: dir.NodePosition,
Message: "malformed linter directive; missing the required reason field?",
Category: "compile",
},
Severity: severityError,
}
diagnostics = append(diagnostics, p)
continue
}
default:
// unknown directive, ignore
continue
}
checks := strings.Split(args[0], ",")
pos := dir.NodePosition
var ig ignore
switch cmd {
case "ignore":
ig = &lineIgnore{
File: pos.Filename,
Line: pos.Line,
Checks: checks,
Pos: dir.DirectivePosition,
}
case "file-ignore":
ig = &fileIgnore{
File: pos.Filename,
Checks: checks,
}
}
ignores = append(ignores, ig)
}
return ignores, diagnostics
}
================================================
FILE: lintcmd/format.go
================================================
package lintcmd
import (
"encoding/json"
"fmt"
"go/token"
"io"
"os"
"path/filepath"
"text/tabwriter"
"honnef.co/go/tools/analysis/lint"
)
func shortPath(path string) string {
cwd, err := os.Getwd()
if err != nil {
return path
}
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
return rel
}
return path
}
func relativePositionString(pos token.Position) string {
s := shortPath(pos.Filename)
if pos.IsValid() {
if s != "" {
s += ":"
}
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
}
if s == "" {
s = "-"
}
return s
}
type statter interface {
Stats(total, errors, warnings, ignored int)
}
type formatter interface {
Format(checks []*lint.Analyzer, diagnostics []diagnostic)
}
type textFormatter struct {
W io.Writer
}
func (o textFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
for _, p := range ps {
fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Position), p.String())
for _, r := range p.Related {
fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Position), r.Message)
}
}
}
type nullFormatter struct{}
func (nullFormatter) Format([]*lint.Analyzer, []diagnostic) {}
type jsonFormatter struct {
W io.Writer
}
func (o jsonFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
type location struct {
File string `json:"file"`
Line int `json:"line"`
Column int `json:"column"`
}
type related struct {
Location location `json:"location"`
End location `json:"end"`
Message string `json:"message"`
}
enc := json.NewEncoder(o.W)
for _, p := range ps {
jp := struct {
Code string `json:"code"`
Severity string `json:"severity,omitempty"`
Location location `json:"location"`
End location `json:"end"`
Message string `json:"message"`
Related []related `json:"related,omitempty"`
}{
Code: p.Category,
Severity: p.Severity.String(),
Location: location{
File: p.Position.Filename,
Line: p.Position.Line,
Column: p.Position.Column,
},
End: location{
File: p.End.Filename,
Line: p.End.Line,
Column: p.End.Column,
},
Message: p.Message,
}
for _, r := range p.Related {
jp.Related = append(jp.Related, related{
Location: location{
File: r.Position.Filename,
Line: r.Position.Line,
Column: r.Position.Column,
},
End: location{
File: r.End.Filename,
Line: r.End.Line,
Column: r.End.Column,
},
Message: r.Message,
})
}
_ = enc.Encode(jp)
}
}
type stylishFormatter struct {
W io.Writer
prevFile string
tw *tabwriter.Writer
}
func (o *stylishFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
for _, p := range ps {
pos := p.Position
if pos.Filename == "" {
pos.Filename = "-"
}
if pos.Filename != o.prevFile {
if o.prevFile != "" {
o.tw.Flush()
fmt.Fprintln(o.W)
}
fmt.Fprintln(o.W, pos.Filename)
o.prevFile = pos.Filename
o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
}
fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Category, p.Message)
for _, r := range p.Related {
fmt.Fprintf(o.tw, " (%d, %d)\t\t %s\n", r.Position.Line, r.Position.Column, r.Message)
}
}
}
func (o *stylishFormatter) Stats(total, errors, warnings, ignored int) {
if o.tw != nil {
o.tw.Flush()
fmt.Fprintln(o.W)
}
fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n",
total, errors, warnings, ignored)
}
================================================
FILE: lintcmd/lint.go
================================================
package lintcmd
import (
"crypto/sha256"
"fmt"
"go/token"
"io"
"os"
"os/signal"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/config"
"honnef.co/go/tools/go/buildid"
"honnef.co/go/tools/go/loader"
"honnef.co/go/tools/lintcmd/cache"
"honnef.co/go/tools/lintcmd/runner"
"honnef.co/go/tools/unused"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
)
// A linter lints Go source code.
type linter struct {
analyzers map[string]*lint.Analyzer
cache *cache.Cache
opts options
}
func computeSalt() ([]byte, error) {
p, err := os.Executable()
if err != nil {
return nil, err
}
if id, err := buildid.ReadFile(p); err == nil {
return []byte(id), nil
} else {
// For some reason we couldn't read the build id from the executable.
// Fall back to hashing the entire executable.
f, err := os.Open(p)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
}
func newLinter(opts options) (*linter, error) {
c, err := cache.Default()
if err != nil {
return nil, err
}
salt, err := computeSalt()
if err != nil {
return nil, fmt.Errorf("could not compute salt for cache: %s", err)
}
c.SetSalt(salt)
analyzers := make(map[string]*lint.Analyzer, len(opts.analyzers))
for _, a := range opts.analyzers {
analyzers[a.Analyzer.Name] = a
}
return &linter{
cache: c,
analyzers: analyzers,
opts: opts,
}, nil
}
type lintResult struct {
// These fields are exported so that we can gob encode them.
CheckedFiles []string
Diagnostics []diagnostic
Warnings []string
}
type options struct {
config config.Config
analyzers []*lint.Analyzer
patterns []string
lintTests bool
goVersion string
printAnalyzerMeasurement func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration)
}
func (l *linter) run(bconf buildConfig) (lintResult, error) {
cfg := &packages.Config{}
if l.opts.lintTests {
cfg.Tests = true
}
cfg.BuildFlags = bconf.Flags
cfg.Env = append(os.Environ(), bconf.Envs...)
r, err := runner.New(l.opts.config, l.cache)
if err != nil {
return lintResult{}, err
}
r.GoVersion = l.opts.goVersion
r.Stats.PrintAnalyzerMeasurement = l.opts.printAnalyzerMeasurement
printStats := func() {
// Individual stats are read atomically, but overall there
// is no synchronisation. For printing rough progress
// information, this doesn't matter.
switch r.Stats.State() {
case runner.StateInitializing:
fmt.Fprintln(os.Stderr, "Status: initializing")
case runner.StateLoadPackageGraph:
fmt.Fprintln(os.Stderr, "Status: loading package graph")
case runner.StateBuildActionGraph:
fmt.Fprintln(os.Stderr, "Status: building action graph")
case runner.StateProcessing:
fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d\n",
r.Stats.ProcessedInitialPackages(),
r.Stats.InitialPackages(),
r.Stats.ProcessedPackages(),
r.Stats.TotalPackages(),
r.ActiveWorkers(),
r.TotalWorkers(),
)
case runner.StateFinalizing:
fmt.Fprintln(os.Stderr, "Status: finalizing")
}
}
if len(infoSignals) > 0 {
ch := make(chan os.Signal, 1)
signal.Notify(ch, infoSignals...)
defer signal.Stop(ch)
go func() {
for range ch {
printStats()
}
}()
}
res, err := l.lint(r, cfg, l.opts.patterns)
for i := range res.Diagnostics {
res.Diagnostics[i].BuildName = bconf.Name
}
return res, err
}
func (l *linter) lint(r *runner.Runner, cfg *packages.Config, patterns []string) (lintResult, error) {
var out lintResult
as := make([]*analysis.Analyzer, 0, len(l.analyzers))
for _, a := range l.analyzers {
as = append(as, a.Analyzer)
}
results, err := r.Run(cfg, as, patterns)
if err != nil {
return out, err
}
if len(results) == 0 {
// TODO(dh): emulate Go's behavior more closely once we have
// access to go list's Match field.
for _, pattern := range patterns {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
}
analyzerNames := make([]string, 0, len(l.analyzers))
for name := range l.analyzers {
analyzerNames = append(analyzerNames, name)
}
used := map[unusedKey]bool{}
var unuseds []unusedPair
for _, res := range results {
if len(res.Errors) > 0 && !res.Failed {
panic("package has errors but isn't marked as failed")
}
if res.Failed {
out.Diagnostics = append(out.Diagnostics, failed(res)...)
} else {
if res.Skipped {
out.Warnings = append(out.Warnings, fmt.Sprintf("skipped package %s because it is too large", res.Package))
continue
}
if !res.Initial {
continue
}
out.CheckedFiles = append(out.CheckedFiles, res.Package.GoFiles...)
allowedAnalyzers := filterAnalyzerNames(analyzerNames, res.Config.Checks)
resd, err := res.Load()
if err != nil {
return out, err
}
ps := success(allowedAnalyzers, resd)
filtered, err := filterIgnored(ps, resd, allowedAnalyzers)
if err != nil {
return out, err
}
// OPT move this code into the 'success' function.
for i, diag := range filtered {
a := l.analyzers[diag.Category]
// Some diag.Category don't map to analyzers, such as "staticcheck"
if a != nil {
filtered[i].MergeIf = a.Doc.MergeIf
}
}
out.Diagnostics = append(out.Diagnostics, filtered...)
for _, obj := range resd.Unused.Used {
// Note: a side-effect of this code is that fields in instantiated structs are handled correctly. Even
// if only an instantiated field is marked as used, we will not flag the generic field, because it has
// the same position as the instance. At some point this won't be necessary anymore because we'll be
// able to make use of the Go 1.19+ Origin methods.
// FIXME(dh): pick the object whose filename does not include $GOROOT
key := unusedKey{
pkgPath: res.Package.PkgPath,
base: filepath.Base(obj.Position.Filename),
line: obj.Position.Line,
name: obj.Name,
}
used[key] = true
}
if allowedAnalyzers["U1000"] {
for _, obj := range resd.Unused.Unused {
key := unusedKey{
pkgPath: res.Package.PkgPath,
base: filepath.Base(obj.Position.Filename),
line: obj.Position.Line,
name: obj.Name,
}
unuseds = append(unuseds, unusedPair{key, obj})
if _, ok := used[key]; !ok {
used[key] = false
}
}
}
}
}
for _, uo := range unuseds {
if used[uo.key] {
continue
}
out.Diagnostics = append(out.Diagnostics, diagnostic{
Diagnostic: runner.Diagnostic{
Position: uo.obj.DisplayPosition,
Message: fmt.Sprintf("%s %s is unused", uo.obj.Kind, uo.obj.Name),
Category: "U1000",
},
MergeIf: lint.MergeIfAll,
})
}
return out, nil
}
func filterIgnored(diagnostics []diagnostic, res runner.ResultData, allowedAnalyzers map[string]bool) ([]diagnostic, error) {
couldHaveMatched := func(ig *lineIgnore) bool {
for _, c := range ig.Checks {
if c == "U1000" {
// We never want to flag ignores for U1000,
// because U1000 isn't local to a single
// package. For example, an identifier may
// only be used by tests, in which case an
// ignore would only fire when not analyzing
// tests. To avoid spurious "useless ignore"
// warnings, just never flag U1000.
return false
}
// Even though the runner always runs all analyzers, we
// still only flag unmatched ignores for the set of
// analyzers the user has expressed interest in. That way,
// `staticcheck -checks=SA1000` won't complain about an
// unmatched ignore for an unrelated check.
if allowedAnalyzers[c] {
return true
}
}
return false
}
ignores, moreDiagnostics := parseDirectives(res.Directives)
for _, ig := range ignores {
for i := range diagnostics {
diag := &diagnostics[i]
if ig.match(*diag) {
diag.Severity = severityIgnored
}
}
if ig, ok := ig.(*lineIgnore); ok && !ig.Matched && couldHaveMatched(ig) {
diag := diagnostic{
Diagnostic: runner.Diagnostic{
Position: ig.Pos,
Message: "this linter directive didn't match anything; should it be removed?",
Category: "staticcheck",
},
}
moreDiagnostics = append(moreDiagnostics, diag)
}
}
return append(diagnostics, moreDiagnostics...), nil
}
type ignore interface {
match(diag diagnostic) bool
}
type lineIgnore struct {
File string
Line int
Checks []string
Matched bool
Pos token.Position
}
func (li *lineIgnore) match(p diagnostic) bool {
pos := p.Position
if pos.Filename != li.File || pos.Line != li.Line {
return false
}
for _, c := range li.Checks {
if m, _ := filepath.Match(c, p.Category); m {
li.Matched = true
return true
}
}
return false
}
func (li *lineIgnore) String() string {
matched := "not matched"
if li.Matched {
matched = "matched"
}
return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
}
type fileIgnore struct {
File string
Checks []string
}
func (fi *fileIgnore) match(p diagnostic) bool {
if p.Position.Filename != fi.File {
return false
}
for _, c := range fi.Checks {
if m, _ := filepath.Match(c, p.Category); m {
return true
}
}
return false
}
type severity uint8
const (
severityError severity = iota
severityWarning
severityIgnored
)
func (s severity) String() string {
switch s {
case severityError:
return "error"
case severityWarning:
return "warning"
case severityIgnored:
return "ignored"
default:
return fmt.Sprintf("Severity(%d)", s)
}
}
// diagnostic represents a diagnostic in some source code.
type diagnostic struct {
runner.Diagnostic
// These fields are exported so that we can gob encode them.
Severity severity
MergeIf lint.MergeStrategy
BuildName string
}
func (p diagnostic) equal(o diagnostic) bool {
return p.Position == o.Position &&
p.End == o.End &&
p.Message == o.Message &&
p.Category == o.Category &&
p.Severity == o.Severity &&
p.MergeIf == o.MergeIf &&
p.BuildName == o.BuildName
}
func (p *diagnostic) String() string {
if p.BuildName != "" {
return fmt.Sprintf("%s [%s] (%s)", p.Message, p.BuildName, p.Category)
} else {
return fmt.Sprintf("%s (%s)", p.Message, p.Category)
}
}
func failed(res runner.Result) []diagnostic {
var diagnostics []diagnostic
for _, e := range res.Errors {
switch e := e.(type) {
case packages.Error:
msg := e.Msg
if len(msg) != 0 && msg[0] == '\n' {
// TODO(dh): See https://github.com/golang/go/issues/32363
msg = msg[1:]
}
cat := "compile"
if e.Kind == packages.ParseError {
cat = "config"
}
var posn token.Position
if e.Pos == "" {
// Under certain conditions (malformed package
// declarations, multiple packages in the same
// directory), go list emits an error on stderr
// instead of JSON. Those errors do not have
// associated position information in
// go/packages.Error, even though the output on
// stderr may contain it.
if p, n, err := parsePos(msg); err == nil {
if abs, err := filepath.Abs(p.Filename); err == nil {
p.Filename = abs
}
posn = p
msg = msg[n+2:]
}
} else {
var err error
posn, _, err = parsePos(e.Pos)
if err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
}
diag := diagnostic{
Diagnostic: runner.Diagnostic{
Position: posn,
Message: msg,
Category: cat,
},
Severity: severityError,
}
diagnostics = append(diagnostics, diag)
case error:
diag := diagnostic{
Diagnostic: runner.Diagnostic{
Position: token.Position{},
Message: e.Error(),
Category: "compile",
},
Severity: severityError,
}
diagnostics = append(diagnostics, diag)
}
}
return diagnostics
}
type unusedKey struct {
pkgPath string
base string
line int
name string
}
type unusedPair struct {
key unusedKey
obj unused.Object
}
func success(allowedAnalyzers map[string]bool, res runner.ResultData) []diagnostic {
diags := res.Diagnostics
var diagnostics []diagnostic
for _, diag := range diags {
if !allowedAnalyzers[diag.Category] {
continue
}
diagnostics = append(diagnostics, diagnostic{Diagnostic: diag})
}
return diagnostics
}
func filterAnalyzerNames(analyzers []string, checks []string) map[string]bool {
allowedChecks := map[string]bool{}
for _, check := range checks {
b := true
if len(check) > 1 && check[0] == '-' {
b = false
check = check[1:]
}
if check == "*" || check == "all" {
// Match all
for _, c := range analyzers {
allowedChecks[c] = b
}
} else if strings.HasSuffix(check, "*") {
// Glob
prefix := check[:len(check)-1]
isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
for _, a := range analyzers {
idx := strings.IndexFunc(a, func(r rune) bool { return unicode.IsNumber(r) })
if isCat {
// Glob is S*, which should match S1000 but not SA1000
cat := a[:idx]
if prefix == cat {
allowedChecks[a] = b
}
} else {
// Glob is S1*
if strings.HasPrefix(a, prefix) {
allowedChecks[a] = b
}
}
}
} else {
// Literal check name
allowedChecks[check] = b
}
}
return allowedChecks
}
// Note that the file name is optional and can be empty because of //line
// directives of the form "//line :1" (but not "//line :1:1"). See
// https://go.dev/issue/24183 and https://staticcheck.dev/issues/1582.
var posRe = regexp.MustCompile(`^(?:(.+?):)?(\d+)(?::(\d+)?)?`)
func parsePos(pos string) (token.Position, int, error) {
if pos == "-" || pos == "" {
return token.Position{}, 0, nil
}
parts := posRe.FindStringSubmatch(pos)
if parts == nil {
return token.Position{}, 0, fmt.Errorf("malformed position %q", pos)
}
file := parts[1]
line, _ := strconv.Atoi(parts[2])
col, _ := strconv.Atoi(parts[3])
return token.Position{
Filename: file,
Line: line,
Column: col,
}, len(parts[0]), nil
}
================================================
FILE: lintcmd/runner/runner.go
================================================
// Package runner implements a go/analysis runner. It makes heavy use
// of on-disk caching to reduce overall memory usage and to speed up
// repeat runs.
//
// # Public API
//
// A Runner maps a list of analyzers and package patterns to a list of
// results. Results provide access to diagnostics, directives, errors
// encountered, and information about packages. Results explicitly do
// not contain ASTs or type information. All position information is
// returned in the form of token.Position, not token.Pos. All work
// that requires access to the loaded representation of a package has
// to occur inside analyzers.
//
// # Planning and execution
//
// Analyzing packages is split into two phases: planning and
// execution.
//
// During planning, a directed acyclic graph of package dependencies
// is computed. We materialize the full graph so that we can execute
// the graph from the bottom up, without keeping unnecessary data in
// memory during a DFS and with simplified parallel execution.
//
// During execution, leaf nodes (nodes with no outstanding
// dependencies) get executed in parallel, bounded by a semaphore
// sized according to the number of CPUs. Conceptually, this happens
// in a loop, processing new leaf nodes as they appear, until no more
// nodes are left. In the actual implementation, nodes know their
// dependents, and the last dependency of a node to be processed is
// responsible for scheduling its dependent.
//
// The graph is rooted at a synthetic root node. Upon execution of the
// root node, the algorithm terminates.
//
// Analyzing a package repeats the same planning + execution steps,
// but this time on a graph of analyzers for the package. Parallel
// execution of individual analyzers is bounded by the same semaphore
// as executing packages.
//
// # Parallelism
//
// Actions are executed in parallel where the dependency graph allows.
// Overall parallelism is bounded by a semaphore, sized according to
// GOMAXPROCS. Each concurrently processed package takes up a
// token, as does each analyzer – but a package can always execute at
// least one analyzer, using the package's token.
//
// Depending on the overall shape of the graph, there may be GOMAXPROCS
// packages running a single analyzer each, a single package running
// GOMAXPROCS analyzers, or anything in between.
//
// Total memory consumption grows roughly linearly with the number of
// CPUs, while total execution time is inversely proportional to the
// number of CPUs. Overall, parallelism is affected by the shape of
// the dependency graph. A lot of inter-connected packages will see
// less parallelism than a lot of independent packages.
//
// # Caching
//
// The runner caches facts, directives and diagnostics in a
// content-addressable cache that is designed after Go's own cache.
// Additionally, it makes use of Go's export data.
//
// This cache not only speeds up repeat runs, it also reduces peak
// memory usage. When we've analyzed a package, we cache the results
// and drop them from memory. When a dependent needs any of this
// information, or when analysis is complete and we wish to render the
// results, the data gets loaded from disk again.
//
// Data only exists in memory when it is immediately needed, not
// retained for possible future uses. This trades increased CPU usage
// for reduced memory usage. A single dependency may be loaded many
// times over, but it greatly reduces peak memory usage, as an
// arbitrary amount of time may pass between analyzing a dependency
// and its dependent, during which other packages will be processed.
package runner
// OPT(dh): we could reduce disk storage usage of cached data by
// compressing it, either directly at the cache layer, or by feeding
// compressed data to the cache. Of course doing so may negatively
// affect CPU usage, and there are lower hanging fruit, such as
// needing to cache less data in the first place.
// OPT(dh): right now, each package is analyzed completely
// independently. Each package loads all of its dependencies from
// export data and cached facts. If we have two packages A and B,
// which both depend on C, and which both get analyzed in parallel,
// then C will be loaded twice. This wastes CPU time and memory. It
// would be nice if we could reuse a single C for the analysis of both
// A and B.
//
// We can't reuse the actual types.Package or facts, because each
// package gets its own token.FileSet. Sharing a global FileSet has
// several drawbacks, including increased memory usage and running the
// risk of running out of FileSet address space.
//
// We could however avoid loading the same raw export data from disk
// twice, as well as deserializing gob data twice. One possible
// solution would be a duplicate-suppressing in-memory cache that
// caches data for a limited amount of time. When the same package
// needs to be loaded twice in close succession, we can reuse work,
// without holding unnecessary data in memory for an extended period
// of time.
//
// We would likely need to do extensive benchmarking to figure out how
// long to keep data around to find a sweet spot where we reduce CPU
// load without increasing memory usage.
//
// We can probably populate the cache after we've analyzed a package,
// on the assumption that it will have to be loaded again in the near
// future.
import (
"bytes"
"encoding/gob"
"fmt"
"go/token"
"go/types"
"io"
"maps"
"os"
"reflect"
"runtime"
"sort"
"strings"
"sync/atomic"
"time"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/config"
"honnef.co/go/tools/go/loader"
tsync "honnef.co/go/tools/internal/sync"
"honnef.co/go/tools/lintcmd/cache"
"honnef.co/go/tools/unused"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/types/objectpath"
)
const sanityCheck = false
// Diagnostic is like go/analysis.Diagnostic, but with all token.Pos resolved to token.Position.
type Diagnostic struct {
Position token.Position
End token.Position
Category string
Message string
SuggestedFixes []SuggestedFix
Related []RelatedInformation
}
// RelatedInformation provides additional context for a diagnostic.
type RelatedInformation struct {
Position token.Position
End token.Position
Message string
}
type SuggestedFix struct {
Message string
TextEdits []TextEdit
}
type TextEdit struct {
Position token.Position
End token.Position
NewText []byte
}
// A Result describes the result of analyzing a single package.
//
// It holds references to cached diagnostics and directives. They can
// be loaded on demand with the Load method.
type Result struct {
Package *loader.PackageSpec
Config config.Config
Initial bool
Skipped bool
Failed bool
Errors []error
// Action results, path to file
results string
// Results relevant to testing, only set when test mode is enabled, path to file
testData string
}
type SerializedDirective struct {
Command string
Arguments []string
// The position of the comment
DirectivePosition token.Position
// The position of the node that the comment is attached to
NodePosition token.Position
}
func serializeDirective(dir lint.Directive, fset *token.FileSet) SerializedDirective {
return SerializedDirective{
Command: dir.Command,
Arguments: dir.Arguments,
DirectivePosition: report.DisplayPosition(fset, dir.Directive.Pos()),
NodePosition: report.DisplayPosition(fset, dir.Node.Pos()),
}
}
type ResultData struct {
Directives []SerializedDirective
Diagnostics []Diagnostic
Unused unused.Result
}
func (r Result) Load() (ResultData, error) {
if r.Failed {
panic("Load called on failed Result")
}
if r.results == "" {
// this package was only a dependency
return ResultData{}, nil
}
f, err := os.Open(r.results)
if err != nil {
return ResultData{}, fmt.Errorf("failed loading result: %w", err)
}
defer f.Close()
var out ResultData
err = gob.NewDecoder(f).Decode(&out)
return out, err
}
// TestData contains extra information about analysis runs that is only available in test mode.
type TestData struct {
// Facts contains facts produced by analyzers for a package.
// Unlike vetx, this list only contains facts specific to this package,
// not all facts for the transitive closure of dependencies.
Facts []TestFact
// List of files that were part of the package.
Files []string
}
// LoadTest returns data relevant to testing.
// It should only be called if Runner.TestMode was set to true.
func (r Result) LoadTest() (TestData, error) {
if r.Failed {
panic("Load called on failed Result")
}
if r.results == "" {
// this package was only a dependency
return TestData{}, nil
}
f, err := os.Open(r.testData)
if err != nil {
return TestData{}, fmt.Errorf("failed loading test data: %w", err)
}
defer f.Close()
var out TestData
err = gob.NewDecoder(f).Decode(&out)
return out, err
}
type action interface {
Deps() []action
Triggers() []action
DecrementPending() bool
MarkFailed()
IsFailed() bool
AddError(error)
}
type baseAction struct {
// Action description
deps []action
triggers []action
pending uint32
// Action results
// failed is set to true if the action couldn't be processed. This
// may either be due to an error specific to this action, in
// which case the errors field will be populated, or due to a
// dependency being marked as failed, in which case errors will be
// empty.
failed bool
errors []error
}
func (act *baseAction) Deps() []action { return act.deps }
func (act *baseAction) Triggers() []action { return act.triggers }
func (act *baseAction) DecrementPending() bool {
return atomic.AddUint32(&act.pending, ^uint32(0)) == 0
}
func (act *baseAction) MarkFailed() { act.failed = true }
func (act *baseAction) IsFailed() bool { return act.failed }
func (act *baseAction) AddError(err error) { act.errors = append(act.errors, err) }
// packageAction describes the act of loading a package, fully
// analyzing it, and storing the results.
type packageAction struct {
baseAction
// Action description
Package *loader.PackageSpec
factsOnly bool
hash cache.ActionID
// Action results
cfg config.Config
vetx string
results string
testData string
skipped bool
}
func (act *packageAction) String() string {
return fmt.Sprintf("packageAction(%s)", act.Package)
}
type objectFact struct {
fact analysis.Fact
// TODO(dh): why do we store the objectpath when producing the
// fact? Is it just for the sanity checking, which compares the
// stored path with a path recomputed from objectFactKey.Obj?
path objectpath.Path
}
type objectFactKey struct {
Obj types.Object
Type reflect.Type
}
type packageFactKey struct {
Pkg *types.Package
Type reflect.Type
}
type gobFact struct {
PkgPath string
ObjPath string
Fact analysis.Fact
}
// TestFact is a serialization of facts that is specific to the test mode.
type TestFact struct {
ObjectName string
Position token.Position
FactString string
Analyzer string
}
// analyzerAction describes the act of analyzing a package with a
// single analyzer.
type analyzerAction struct {
baseAction
// Action description
Analyzer *analysis.Analyzer
// Action results
// We can store actual results here without worrying about memory
// consumption because analyzer actions get garbage collected once
// a package has been fully analyzed.
Result any
Diagnostics []Diagnostic
ObjectFacts map[objectFactKey]objectFact
PackageFacts map[packageFactKey]analysis.Fact
Pass *analysis.Pass
}
func (act *analyzerAction) String() string {
return fmt.Sprintf("analyzerAction(%s)", act.Analyzer)
}
// A Runner executes analyzers on packages.
type Runner struct {
Stats Stats
GoVersion string
// If set to true, Runner will populate results with data relevant to testing analyzers
TestMode bool
// Config that gets merged with per-package configs
cfg config.Config
cache *cache.Cache
semaphore tsync.Semaphore
}
type subrunner struct {
*Runner
analyzers []*analysis.Analyzer
factAnalyzers []*analysis.Analyzer
analyzerNames string
cache *cache.Cache
}
// New returns a new Runner.
func New(cfg config.Config, c *cache.Cache) (*Runner, error) {
return &Runner{
cfg: cfg,
cache: c,
semaphore: tsync.NewSemaphore(runtime.GOMAXPROCS(0)),
}, nil
}
func newSubrunner(r *Runner, analyzers []*analysis.Analyzer) *subrunner {
analyzerNames := make([]string, len(analyzers))
for i, a := range analyzers {
analyzerNames[i] = a.Name
}
sort.Strings(analyzerNames)
var factAnalyzers []*analysis.Analyzer
for _, a := range analyzers {
if len(a.FactTypes) > 0 {
factAnalyzers = append(factAnalyzers, a)
}
}
return &subrunner{
Runner: r,
analyzers: analyzers,
factAnalyzers: factAnalyzers,
analyzerNames: strings.Join(analyzerNames, ","),
cache: r.cache,
}
}
func newPackageActionRoot(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction {
a := newPackageAction(pkg, cache)
a.factsOnly = false
return a
}
func newPackageAction(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction {
if a, ok := cache[pkg]; ok {
return a
}
a := &packageAction{
Package: pkg,
factsOnly: true, // will be overwritten by any call to Action
}
cache[pkg] = a
if len(pkg.Errors) > 0 {
a.errors = make([]error, len(pkg.Errors))
for i, err := range pkg.Errors {
a.errors[i] = err
}
a.failed = true
// We don't need to process our imports if this package is
// already broken.
return a
}
a.deps = make([]action, 0, len(pkg.Imports))
for _, dep := range pkg.Imports {
depa := newPackageAction(dep, cache)
depa.triggers = append(depa.triggers, a)
a.deps = append(a.deps, depa)
if depa.failed {
a.failed = true
}
}
// sort dependencies because the list of dependencies is part of
// the cache key
sort.Slice(a.deps, func(i, j int) bool {
return a.deps[i].(*packageAction).Package.ID < a.deps[j].(*packageAction).Package.ID
})
a.pending = uint32(len(a.deps))
return a
}
func newAnalyzerAction(an *analysis.Analyzer, cache map[*analysis.Analyzer]*analyzerAction) *analyzerAction {
if a, ok := cache[an]; ok {
return a
}
a := &analyzerAction{
Analyzer: an,
ObjectFacts: map[objectFactKey]objectFact{},
PackageFacts: map[packageFactKey]analysis.Fact{},
}
cache[an] = a
for _, dep := range an.Requires {
depa := newAnalyzerAction(dep, cache)
depa.triggers = append(depa.triggers, a)
a.deps = append(a.deps, depa)
}
a.pending = uint32(len(a.deps))
return a
}
func getCachedFiles(cache *cache.Cache, ids []cache.ActionID, out []*string) error {
for i, id := range ids {
var err error
*out[i], _, err = cache.GetFile(id)
if err != nil {
return err
}
}
return nil
}
func (r *subrunner) do(act action) error {
a := act.(*packageAction)
defer func() {
r.Stats.finishPackage()
if !a.factsOnly {
r.Stats.finishInitialPackage()
}
}()
// compute hash of action
a.cfg = a.Package.Config.Merge(r.cfg)
h := r.cache.NewHash("staticcheck " + a.Package.PkgPath)
// Note that we do not filter the list of analyzers by the
// package's configuration. We don't allow configuration to
// accidentally break dependencies between analyzers, and it's
// easier to always run all checks and filter the output. This
// also makes cached data more reusable.
// OPT(dh): not all changes in configuration invalidate cached
// data. specifically, when a.factsOnly == true, we only care
// about checks that produce facts, and settings that affect those
// checks.
// Config used for constructing the hash; this config doesn't have
// Checks populated, because we always run all checks.
//
// This even works for users who add custom checks, because we include the binary's hash.
hashCfg := a.cfg
hashCfg.Checks = nil
// note that we don't hash staticcheck's version; it is set as the
// salt by a package main.
fmt.Fprintf(h, "cfg %#v\n", hashCfg)
fmt.Fprintf(h, "pkg %x\n", a.Package.Hash)
fmt.Fprintf(h, "analyzers %s\n", r.analyzerNames)
fmt.Fprintf(h, "go %s\n", r.GoVersion)
fmt.Fprintf(h, "env godebug %q\n", os.Getenv("GODEBUG"))
// OPT(dh): do we actually need to hash vetx? can we not assume
// that for identical inputs, staticcheck will produce identical
// vetx?
for _, dep := range a.deps {
dep := dep.(*packageAction)
vetxHash, err := cache.FileHash(dep.vetx)
if err != nil {
return fmt.Errorf("failed computing hash: %w", err)
}
fmt.Fprintf(h, "vetout %q %x\n", dep.Package.PkgPath, vetxHash)
}
a.hash = cache.ActionID(h.Sum())
// try to fetch hashed data
ids := make([]cache.ActionID, 0, 2)
ids = append(ids, cache.Subkey(a.hash, "vetx"))
if !a.factsOnly {
ids = append(ids, cache.Subkey(a.hash, "results"))
if r.TestMode {
ids = append(ids, cache.Subkey(a.hash, "testdata"))
}
}
if err := getCachedFiles(r.cache, ids, []*string{&a.vetx, &a.results, &a.testData}); err != nil {
result, err := r.doUncached(a)
if err != nil {
return err
}
if a.failed {
return nil
}
a.skipped = result.skipped
// OPT(dh) instead of collecting all object facts and encoding
// them after analysis finishes, we could encode them as we
// go. however, that would require some locking.
//
// OPT(dh): We could sort gobFacts for more consistent output,
// but it doesn't matter. The hash of a package includes all
// of its files, so whether the vetx hash changes or not, a
// change to a package requires re-analyzing all dependents,
// even if the vetx data stayed the same. See also the note at
// the top of loader/hash.go.
tf := &bytes.Buffer{}
enc := gob.NewEncoder(tf)
for _, gf := range result.facts {
if err := enc.Encode(gf); err != nil {
return fmt.Errorf("failed gob encoding data: %w", err)
}
}
a.vetx, err = r.writeCacheReader(a, "vetx", bytes.NewReader(tf.Bytes()))
if err != nil {
return err
}
if a.factsOnly {
return nil
}
var out ResultData
out.Directives = make([]SerializedDirective, len(result.dirs))
for i, dir := range result.dirs {
out.Directives[i] = serializeDirective(dir, result.lpkg.Fset)
}
out.Diagnostics = result.diags
out.Unused = result.unused
a.results, err = r.writeCacheGob(a, "results", out)
if err != nil {
return err
}
if r.TestMode {
out := TestData{
Facts: result.testFacts,
Files: result.lpkg.GoFiles,
}
a.testData, err = r.writeCacheGob(a, "testdata", out)
if err != nil {
return err
}
}
}
return nil
}
// ActiveWorkers returns the number of currently running workers.
func (r *Runner) ActiveWorkers() int {
return r.semaphore.Len()
}
// TotalWorkers returns the maximum number of possible workers.
func (r *Runner) TotalWorkers() int {
return r.semaphore.Cap()
}
func (r *Runner) writeCacheReader(a *packageAction, kind string, rs io.ReadSeeker) (string, error) {
h := cache.Subkey(a.hash, kind)
out, _, err := r.cache.Put(h, rs)
if err != nil {
return "", fmt.Errorf("failed caching data: %w", err)
}
return r.cache.OutputFile(out), nil
}
func (r *Runner) writeCacheGob(a *packageAction, kind string, data any) (string, error) {
f, err := os.CreateTemp("", "staticcheck")
if err != nil {
return "", err
}
defer f.Close()
os.Remove(f.Name())
if err := gob.NewEncoder(f).Encode(data); err != nil {
return "", fmt.Errorf("failed gob encoding data: %w", err)
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return "", err
}
return r.writeCacheReader(a, kind, f)
}
type packageActionResult struct {
facts []gobFact
diags []Diagnostic
unused unused.Result
dirs []lint.Directive
lpkg *loader.Package
skipped bool
// Only set when using test mode
testFacts []TestFact
}
func (r *subrunner) doUncached(a *packageAction) (packageActionResult, error) {
// OPT(dh): for a -> b; c -> b; if both a and b are being
// processed concurrently, we shouldn't load b's export data
// twice.
pkg, _, err := loader.Load(a.Package, &loader.Options{GoVersion: r.GoVersion})
if err != nil {
return packageActionResult{}, err
}
if len(pkg.Errors) > 0 {
// this handles errors that occurred during type-checking the
// package in loader.Load
for _, err := range pkg.Errors {
a.errors = append(a.errors, err)
}
a.failed = true
return packageActionResult{}, nil
}
if len(pkg.Syntax) == 0 && pkg.PkgPath != "unsafe" {
return packageActionResult{lpkg: pkg, skipped: true}, nil
}
// OPT(dh): instead of parsing directives twice (twice because
// U1000 depends on the facts.Directives analyzer), reuse the
// existing result
var dirs []lint.Directive
if !a.factsOnly {
dirs = lint.ParseDirectives(pkg.Syntax, pkg.Fset)
}
res, err := r.runAnalyzers(a, pkg)
return packageActionResult{
facts: res.facts,
testFacts: res.testFacts,
diags: res.diagnostics,
unused: res.unused,
dirs: dirs,
lpkg: pkg,
}, err
}
func pkgPaths(root *types.Package) map[string]*types.Package {
out := map[string]*types.Package{}
var dfs func(*types.Package)
dfs = func(pkg *types.Package) {
if _, ok := out[pkg.Path()]; ok {
return
}
out[pkg.Path()] = pkg
for _, imp := range pkg.Imports() {
dfs(imp)
}
}
dfs(root)
return out
}
func (r *Runner) loadFacts(root *types.Package, dep *packageAction, objFacts map[objectFactKey]objectFact, pkgFacts map[packageFactKey]analysis.Fact) error {
// Load facts of all imported packages
vetx, err := os.Open(dep.vetx)
if err != nil {
return fmt.Errorf("failed loading cached facts: %w", err)
}
defer vetx.Close()
pathToPkg := pkgPaths(root)
dec := gob.NewDecoder(vetx)
for {
var gf gobFact
err := dec.Decode(&gf)
if err != nil {
if err == io.EOF {
break
}
return fmt.Errorf("failed loading cached facts: %w", err)
}
pkg, ok := pathToPkg[gf.PkgPath]
if !ok {
continue
}
if gf.ObjPath == "" {
pkgFacts[packageFactKey{
Pkg: pkg,
Type: reflect.TypeOf(gf.Fact),
}] = gf.Fact
} else {
obj, err := objectpath.Object(pkg, objectpath.Path(gf.ObjPath))
if err != nil {
continue
}
objFacts[objectFactKey{
Obj: obj,
Type: reflect.TypeOf(gf.Fact),
}] = objectFact{gf.Fact, objectpath.Path(gf.ObjPath)}
}
}
return nil
}
func genericHandle(a action, root action, queue chan action, sem *tsync.Semaphore, exec func(a action) error) {
if a == root {
close(queue)
if sem != nil {
sem.Release()
}
return
}
if !a.IsFailed() {
// the action may have already been marked as failed during
// construction of the action graph, for example because of
// unresolved imports.
for _, dep := range a.Deps() {
if dep.IsFailed() {
// One of our dependencies failed, so mark this package as
// failed and bail. We don't need to record an error for
// this package, the relevant error will have been
// reported by the first package in the chain that failed.
a.MarkFailed()
break
}
}
}
if !a.IsFailed() {
if err := exec(a); err != nil {
a.MarkFailed()
a.AddError(err)
}
}
if sem != nil {
sem.Release()
}
for _, t := range a.Triggers() {
if t.DecrementPending() {
queue <- t
}
}
}
type analyzerRunner struct {
pkg *loader.Package
// object facts of our dependencies; may contain facts of
// analyzers other than the current one
depObjFacts map[objectFactKey]objectFact
// package facts of our dependencies; may contain facts of
// analyzers other than the current one
depPkgFacts map[packageFactKey]analysis.Fact
factsOnly bool
stats *Stats
}
func (ar *analyzerRunner) do(act action) error {
a := act.(*analyzerAction)
results := map[*analysis.Analyzer]any{}
// TODO(dh): does this have to be recursive?
for _, dep := range a.deps {
dep := dep.(*analyzerAction)
results[dep.Analyzer] = dep.Result
}
// OPT(dh): cache factTypes, it is the same for all packages for a given analyzer
//
// OPT(dh): do we need the factTypes map? most analyzers have 0-1
// fact types. iterating over the slice is probably faster than
// indexing a map.
factTypes := map[reflect.Type]struct{}{}
for _, typ := range a.Analyzer.FactTypes {
factTypes[reflect.TypeOf(typ)] = struct{}{}
}
filterFactType := func(typ reflect.Type) bool {
_, ok := factTypes[typ]
return ok
}
a.Pass = &analysis.Pass{
Analyzer: a.Analyzer,
Fset: ar.pkg.Fset,
Files: ar.pkg.Syntax,
OtherFiles: ar.pkg.OtherFiles,
Pkg: ar.pkg.Types,
TypesInfo: ar.pkg.TypesInfo,
TypesSizes: ar.pkg.TypesSizes,
Report: func(diag analysis.Diagnostic) {
if !ar.factsOnly {
if diag.Category == "" {
diag.Category = a.Analyzer.Name
}
d := Diagnostic{
Position: report.DisplayPosition(ar.pkg.Fset, diag.Pos),
End: report.DisplayPosition(ar.pkg.Fset, diag.End),
Category: diag.Category,
Message: diag.Message,
}
for _, sugg := range diag.SuggestedFixes {
s := SuggestedFix{
Message: sugg.Message,
}
for _, edit := range sugg.TextEdits {
s.TextEdits = append(s.TextEdits, TextEdit{
Position: report.DisplayPosition(ar.pkg.Fset, edit.Pos),
End: report.DisplayPosition(ar.pkg.Fset, edit.End),
NewText: edit.NewText,
})
}
d.SuggestedFixes = append(d.SuggestedFixes, s)
}
for _, rel := range diag.Related {
d.Related = append(d.Related, RelatedInformation{
Position: report.DisplayPosition(ar.pkg.Fset, rel.Pos),
End: report.DisplayPosition(ar.pkg.Fset, rel.End),
Message: rel.Message,
})
}
a.Diagnostics = append(a.Diagnostics, d)
}
},
ResultOf: results,
ImportObjectFact: func(obj types.Object, fact analysis.Fact) bool {
key := objectFactKey{
Obj: obj,
Type: reflect.TypeOf(fact),
}
if f, ok := ar.depObjFacts[key]; ok {
reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem())
return true
} else if f, ok := a.ObjectFacts[key]; ok {
reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem())
return true
}
return false
},
ImportPackageFact: func(pkg *types.Package, fact analysis.Fact) bool {
key := packageFactKey{
Pkg: pkg,
Type: reflect.TypeOf(fact),
}
if f, ok := ar.depPkgFacts[key]; ok {
reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
return true
} else if f, ok := a.PackageFacts[key]; ok {
reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem())
return true
}
return false
},
ExportObjectFact: func(obj types.Object, fact analysis.Fact) {
key := objectFactKey{
Obj: obj,
Type: reflect.TypeOf(fact),
}
path, _ := objectpath.For(obj)
a.ObjectFacts[key] = objectFact{fact, path}
},
ExportPackageFact: func(fact analysis.Fact) {
key := packageFactKey{
Pkg: ar.pkg.Types,
Type: reflect.TypeOf(fact),
}
a.PackageFacts[key] = fact
},
AllPackageFacts: func() []analysis.PackageFact {
out := make([]analysis.PackageFact, 0, len(ar.depPkgFacts)+len(a.PackageFacts))
for key, fact := range ar.depPkgFacts {
out = append(out, analysis.PackageFact{
Package: key.Pkg,
Fact: fact,
})
}
for key, fact := range a.PackageFacts {
out = append(out, analysis.PackageFact{
Package: key.Pkg,
Fact: fact,
})
}
return out
},
AllObjectFacts: func() []analysis.ObjectFact {
out := make([]analysis.ObjectFact, 0, len(ar.depObjFacts)+len(a.ObjectFacts))
for key, fact := range ar.depObjFacts {
if filterFactType(key.Type) {
out = append(out, analysis.ObjectFact{
Object: key.Obj,
Fact: fact.fact,
})
}
}
for key, fact := range a.ObjectFacts {
if filterFactType(key.Type) {
out = append(out, analysis.ObjectFact{
Object: key.Obj,
Fact: fact.fact,
})
}
}
return out
},
}
t := time.Now()
res, err := a.Analyzer.Run(a.Pass)
ar.stats.measureAnalyzer(a.Analyzer, ar.pkg.PackageSpec, time.Since(t))
if err != nil {
return err
}
a.Result = res
return nil
}
type analysisResult struct {
facts []gobFact
diagnostics []Diagnostic
unused unused.Result
// Only set when using test mode
testFacts []TestFact
}
func (r *subrunner) runAnalyzers(pkgAct *packageAction, pkg *loader.Package) (analysisResult, error) {
depObjFacts := map[objectFactKey]objectFact{}
depPkgFacts := map[packageFactKey]analysis.Fact{}
for _, dep := range pkgAct.deps {
if err := r.loadFacts(pkg.Types, dep.(*packageAction), depObjFacts, depPkgFacts); err != nil {
return analysisResult{}, err
}
}
root := &analyzerAction{}
var analyzers []*analysis.Analyzer
if pkgAct.factsOnly {
// When analyzing non-initial packages, we only care about
// analyzers that produce facts.
analyzers = r.factAnalyzers
} else {
analyzers = r.analyzers
}
all := map[*analysis.Analyzer]*analyzerAction{}
for _, a := range analyzers {
a := newAnalyzerAction(a, all)
root.deps = append(root.deps, a)
a.triggers = append(a.triggers, root)
}
root.pending = uint32(len(root.deps))
ar := &analyzerRunner{
pkg: pkg,
factsOnly: pkgAct.factsOnly,
depObjFacts: depObjFacts,
depPkgFacts: depPkgFacts,
stats: &r.Stats,
}
queue := make(chan action, len(all))
for _, a := range all {
if len(a.Deps()) == 0 {
queue <- a
}
}
// Don't hang if there are no analyzers to run; for example
// because we are analyzing a dependency but have no analyzers
// that produce facts.
if len(all) == 0 {
close(queue)
}
for item := range queue {
b := r.semaphore.AcquireMaybe()
if b {
go genericHandle(item, root, queue, &r.semaphore, ar.do)
} else {
// the semaphore is exhausted; run the analysis under the
// token we've acquired for analyzing the package.
genericHandle(item, root, queue, nil, ar.do)
}
}
var unusedResult unused.Result
for _, a := range all {
if a != root && a.Analyzer.Name == "U1000" && !a.failed {
// TODO(dh): figure out a clean abstraction, instead of
// special-casing U1000.
unusedResult = a.Result.(unused.Result)
}
maps.Copy(depObjFacts, a.ObjectFacts)
maps.Copy(depPkgFacts, a.PackageFacts)
}
// OPT(dh): cull objects not reachable via the exported closure
var testFacts []TestFact
gobFacts := make([]gobFact, 0, len(depObjFacts)+len(depPkgFacts))
for key, fact := range depObjFacts {
if fact.path == "" {
continue
}
if sanityCheck {
p, _ := objectpath.For(key.Obj)
if p != fact.path {
panic(fmt.Sprintf("got different object paths for %v. old: %q new: %q", key.Obj, fact.path, p))
}
}
gf := gobFact{
PkgPath: key.Obj.Pkg().Path(),
ObjPath: string(fact.path),
Fact: fact.fact,
}
gobFacts = append(gobFacts, gf)
}
for key, fact := range depPkgFacts {
gf := gobFact{
PkgPath: key.Pkg.Path(),
Fact: fact,
}
gobFacts = append(gobFacts, gf)
}
if r.TestMode {
for _, a := range all {
for key, fact := range a.ObjectFacts {
tgf := TestFact{
ObjectName: key.Obj.Name(),
Position: pkg.Fset.Position(key.Obj.Pos()),
FactString: fmt.Sprint(fact.fact),
Analyzer: a.Analyzer.Name,
}
testFacts = append(testFacts, tgf)
}
for _, fact := range a.PackageFacts {
tgf := TestFact{
ObjectName: "",
Position: pkg.Fset.Position(pkg.Syntax[0].Pos()),
FactString: fmt.Sprint(fact),
Analyzer: a.Analyzer.Name,
}
testFacts = append(testFacts, tgf)
}
}
}
var diags []Diagnostic
for _, a := range root.deps {
a := a.(*analyzerAction)
diags = append(diags, a.Diagnostics...)
}
return analysisResult{
facts: gobFacts,
testFacts: testFacts,
diagnostics: diags,
unused: unusedResult,
}, nil
}
func registerGobTypes(analyzers []*analysis.Analyzer) {
for _, a := range analyzers {
for _, typ := range a.FactTypes {
// FIXME(dh): use RegisterName so we can work around collisions
// in names. For pointer-types, gob incorrectly qualifies
// type names with the package name, not the import path.
gob.Register(typ)
}
}
}
func allAnalyzers(analyzers []*analysis.Analyzer) []*analysis.Analyzer {
seen := map[*analysis.Analyzer]struct{}{}
out := make([]*analysis.Analyzer, 0, len(analyzers))
var dfs func(*analysis.Analyzer)
dfs = func(a *analysis.Analyzer) {
if _, ok := seen[a]; ok {
return
}
seen[a] = struct{}{}
out = append(out, a)
for _, dep := range a.Requires {
dfs(dep)
}
}
for _, a := range analyzers {
dfs(a)
}
return out
}
// Run loads the packages specified by patterns, runs analyzers on
// them and returns the results. Each result corresponds to a single
// package. Results will be returned for all packages, including
// dependencies. Errors specific to packages will be reported in the
// respective results.
//
// If cfg is nil, a default config will be used. Otherwise, cfg will
// be used, with the exception of the Mode field.
func (r *Runner) Run(cfg *packages.Config, analyzers []*analysis.Analyzer, patterns []string) ([]Result, error) {
analyzers = allAnalyzers(analyzers)
registerGobTypes(analyzers)
r.Stats.setState(StateLoadPackageGraph)
lpkgs, err := loader.Graph(r.cache, cfg, patterns...)
if err != nil {
return nil, err
}
r.Stats.setInitialPackages(len(lpkgs))
if len(lpkgs) == 0 {
return nil, nil
}
r.Stats.setState(StateBuildActionGraph)
all := map[*loader.PackageSpec]*packageAction{}
root := &packageAction{}
for _, lpkg := range lpkgs {
a := newPackageActionRoot(lpkg, all)
root.deps = append(root.deps, a)
a.triggers = append(a.triggers, root)
}
root.pending = uint32(len(root.deps))
queue := make(chan action)
r.Stats.setTotalPackages(len(all) - 1)
r.Stats.setState(StateProcessing)
go func() {
for _, a := range all {
if len(a.Deps()) == 0 {
queue <- a
}
}
}()
sr := newSubrunner(r, analyzers)
for item := range queue {
r.semaphore.Acquire()
go genericHandle(item, root, queue, &r.semaphore, func(act action) error {
return sr.do(act)
})
}
r.Stats.setState(StateFinalizing)
out := make([]Result, 0, len(all))
for _, item := range all {
if item.Package == nil {
continue
}
out = append(out, Result{
Package: item.Package,
Config: item.cfg,
Initial: !item.factsOnly,
Skipped: item.skipped,
Failed: item.failed,
Errors: item.errors,
results: item.results,
testData: item.testData,
})
}
return out, nil
}
================================================
FILE: lintcmd/runner/stats.go
================================================
package runner
import (
"sync/atomic"
"time"
"honnef.co/go/tools/go/loader"
"golang.org/x/tools/go/analysis"
)
const (
StateInitializing = iota
StateLoadPackageGraph
StateBuildActionGraph
StateProcessing
StateFinalizing
)
type Stats struct {
state uint32
initialPackages uint32
totalPackages uint32
processedPackages uint32
processedInitialPackages uint32
// optional function to call every time an analyzer has finished analyzing a package.
PrintAnalyzerMeasurement func(*analysis.Analyzer, *loader.PackageSpec, time.Duration)
}
func (s *Stats) setState(state uint32) { atomic.StoreUint32(&s.state, state) }
func (s *Stats) State() int { return int(atomic.LoadUint32(&s.state)) }
func (s *Stats) setInitialPackages(n int) { atomic.StoreUint32(&s.initialPackages, uint32(n)) }
func (s *Stats) InitialPackages() int { return int(atomic.LoadUint32(&s.initialPackages)) }
func (s *Stats) setTotalPackages(n int) { atomic.StoreUint32(&s.totalPackages, uint32(n)) }
func (s *Stats) TotalPackages() int { return int(atomic.LoadUint32(&s.totalPackages)) }
func (s *Stats) finishPackage() { atomic.AddUint32(&s.processedPackages, 1) }
func (s *Stats) finishInitialPackage() { atomic.AddUint32(&s.processedInitialPackages, 1) }
func (s *Stats) ProcessedPackages() int { return int(atomic.LoadUint32(&s.processedPackages)) }
func (s *Stats) ProcessedInitialPackages() int {
return int(atomic.LoadUint32(&s.processedInitialPackages))
}
func (s *Stats) measureAnalyzer(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) {
if s.PrintAnalyzerMeasurement != nil {
s.PrintAnalyzerMeasurement(analysis, pkg, d)
}
}
================================================
FILE: lintcmd/sarif.go
================================================
package lintcmd
// Notes on GitHub-specific restrictions:
//
// Result.Message needs to either have ID or Text set. Markdown
// gets ignored. Text isn't treated verbatim however: Markdown
// formatting gets stripped, except for links.
//
// GitHub does not display RelatedLocations. The only way to make
// use of them is to link to them (via their ID) in the
// Result.Message. And even then, it will only show the referred
// line of code, not the message. We can duplicate the messages in
// the Result.Message, but we can't even indent them, because
// leading whitespace gets stripped.
//
// GitHub does use the Markdown version of rule help, but it
// renders it the way it renders comments on issues – that is, it
// turns line breaks into hard line breaks, even though it
// shouldn't.
//
// GitHub doesn't make use of the tool's URI or version, nor of
// the help URIs of rules.
//
// There does not seem to be a way of using SARIF for "normal" CI,
// without results showing up as code scanning alerts. Also, a
// SARIF file containing only warnings, no errors, will not fail
// CI by default, but this is configurable.
// GitHub does display some parts of SARIF results in PRs, but
// most of the useful parts of SARIF, such as help text of rules,
// is only accessible via the code scanning alerts, which are only
// accessible by users with write permissions.
//
// Result.Suppressions is being ignored.
//
//
// Notes on other tools
//
// VS Code Sarif viewer
//
// The Sarif viewer in VS Code displays the full message in the
// tabular view, removing newlines. That makes our multi-line
// messages (which we use as a workaround for missing related
// information) very ugly.
//
// Much like GitHub, the Sarif viewer does not make related
// information visible unless we explicitly refer to it in the
// message.
//
// Suggested fixes are not exposed in any way.
//
// It only shows the shortDescription or fullDescription of a
// rule, not its help. We can't put the help in fullDescription,
// because the fullDescription isn't meant to be that long. For
// example, GitHub displays it in a single line, under the
// shortDescription.
//
// VS Code can filter based on Result.Suppressions, but it doesn't
// display our suppression message. Also, by default, suppressed
// results get shown, and the column indicating that a result is
// suppressed is hidden, which makes for a confusing experience.
//
// When a rule has only an ID, no name, VS Code displays a
// prominent dash in place of the name. When the name and ID are
// identical, it prints both. However, we can't make them
// identical, as SARIF requires that either the ID and name are
// different, or that the name is omitted.
// FIXME(dh): we're currently reporting column information using UTF-8
// byte offsets, not using Unicode code points or UTF-16, which are
// the only two ways allowed by SARIF.
// TODO(dh) set properties.tags – we can use different tags for the
// staticcheck, simple, stylecheck and unused checks, so users can
// filter their results
import (
"encoding/json"
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/sarif"
)
type sarifFormatter struct {
driverName string
driverVersion string
driverWebsite string
}
func sarifLevel(severity lint.Severity) string {
switch severity {
case lint.SeverityNone:
// no configured severity, default to warning
return "warning"
case lint.SeverityError:
return "error"
case lint.SeverityDeprecated:
return "warning"
case lint.SeverityWarning:
return "warning"
case lint.SeverityInfo:
return "note"
case lint.SeverityHint:
return "note"
default:
// unreachable
return "none"
}
}
func encodePath(path string) string {
return (&url.URL{Path: path}).EscapedPath()
}
func sarifURI(path string) string {
u := url.URL{
Scheme: "file",
Path: path,
}
return u.String()
}
func sarifArtifactLocation(name string) sarif.ArtifactLocation {
// Ideally we use relative paths so that GitHub can resolve them
name = shortPath(name)
if filepath.IsAbs(name) {
return sarif.ArtifactLocation{
URI: sarifURI(name),
}
} else {
return sarif.ArtifactLocation{
URI: encodePath(name),
URIBaseID: "%SRCROOT%", // This is specific to GitHub,
}
}
}
func sarifFormatText(s string) string {
// GitHub doesn't ignore line breaks, even though it should, so we remove them.
var out strings.Builder
lines := strings.Split(s, "\n")
for i, line := range lines[:len(lines)-1] {
out.WriteString(line)
if line == "" {
out.WriteString("\n")
} else {
nextLine := lines[i+1]
if nextLine == "" || strings.HasPrefix(line, "> ") || strings.HasPrefix(line, " ") {
out.WriteString("\n")
} else {
out.WriteString(" ")
}
}
}
out.WriteString(lines[len(lines)-1])
return convertCodeBlocks(out.String())
}
func moreCodeFollows(lines []string) bool {
for _, line := range lines {
if line == "" {
continue
}
if strings.HasPrefix(line, " ") {
return true
} else {
return false
}
}
return false
}
var alpha = regexp.MustCompile(`^[a-zA-Z ]+$`)
func convertCodeBlocks(text string) string {
var buf strings.Builder
lines := strings.Split(text, "\n")
inCode := false
empties := 0
for i, line := range lines {
if inCode {
if !moreCodeFollows(lines[i:]) {
if inCode {
fmt.Fprintln(&buf, "```")
inCode = false
}
}
}
prevEmpties := empties
if line == "" && !inCode {
empties++
} else {
empties = 0
}
if line == "" {
fmt.Fprintln(&buf)
continue
}
if strings.HasPrefix(line, " ") {
line = line[4:]
if !inCode {
fmt.Fprintln(&buf, "```go")
inCode = true
}
}
onlyAlpha := alpha.MatchString(line)
out := line
if !inCode && prevEmpties >= 2 && onlyAlpha {
fmt.Fprintf(&buf, "## %s\n", out)
} else {
fmt.Fprint(&buf, out)
fmt.Fprintln(&buf)
}
}
if inCode {
fmt.Fprintln(&buf, "```")
}
return buf.String()
}
func (o *sarifFormatter) Format(checks []*lint.Analyzer, diagnostics []diagnostic) {
// TODO(dh): some diagnostics shouldn't be reported as results. For example, when the user specifies a package on the command line that doesn't exist.
cwd, _ := os.Getwd()
run := sarif.Run{
Tool: sarif.Tool{
Driver: sarif.ToolComponent{
Name: o.driverName,
Version: o.driverVersion,
InformationURI: o.driverWebsite,
},
},
Invocations: []sarif.Invocation{{
Arguments: os.Args[1:],
WorkingDirectory: sarif.ArtifactLocation{
URI: sarifURI(cwd),
},
ExecutionSuccessful: true,
}},
}
for _, c := range checks {
doc := c.Doc.Compile()
run.Tool.Driver.Rules = append(run.Tool.Driver.Rules,
sarif.ReportingDescriptor{
// We don't set Name, as Name and ID mustn't be identical.
ID: c.Analyzer.Name,
ShortDescription: sarif.Message{
Text: doc.Title,
Markdown: doc.TitleMarkdown,
},
HelpURI: "https://staticcheck.dev/docs/checks#" + c.Analyzer.Name,
// We use our markdown as the plain text version, too. We
// use very little markdown, primarily quotations,
// indented code blocks and backticks. All of these are
// fine as plain text, too.
Help: sarif.Message{
Text: sarifFormatText(doc.Format(false)),
Markdown: sarifFormatText(doc.FormatMarkdown(false)),
},
DefaultConfiguration: sarif.ReportingConfiguration{
// TODO(dh): we could figure out which checks were disabled globally
Enabled: true,
Level: sarifLevel(doc.Severity),
},
})
}
for _, p := range diagnostics {
r := sarif.Result{
RuleID: p.Category,
Kind: sarif.Fail,
Message: sarif.Message{
Text: p.Message,
},
}
r.Locations = []sarif.Location{{
PhysicalLocation: sarif.PhysicalLocation{
ArtifactLocation: sarifArtifactLocation(p.Position.Filename),
Region: sarif.Region{
StartLine: p.Position.Line,
StartColumn: p.Position.Column,
EndLine: p.End.Line,
EndColumn: p.End.Column,
},
},
}}
for _, fix := range p.SuggestedFixes {
sfix := sarif.Fix{
Description: sarif.Message{
Text: fix.Message,
},
}
// file name -> replacements
changes := map[string][]sarif.Replacement{}
for _, edit := range fix.TextEdits {
changes[edit.Position.Filename] = append(changes[edit.Position.Filename], sarif.Replacement{
DeletedRegion: sarif.Region{
StartLine: edit.Position.Line,
StartColumn: edit.Position.Column,
EndLine: edit.End.Line,
EndColumn: edit.End.Column,
},
InsertedContent: sarif.ArtifactContent{
Text: string(edit.NewText),
},
})
}
for path, replacements := range changes {
sfix.ArtifactChanges = append(sfix.ArtifactChanges, sarif.ArtifactChange{
ArtifactLocation: sarifArtifactLocation(path),
Replacements: replacements,
})
}
r.Fixes = append(r.Fixes, sfix)
}
for i, related := range p.Related {
r.Message.Text += fmt.Sprintf("\n\t[%s](%d)", related.Message, i+1)
r.RelatedLocations = append(r.RelatedLocations,
sarif.Location{
ID: i + 1,
Message: &sarif.Message{
Text: related.Message,
},
PhysicalLocation: sarif.PhysicalLocation{
ArtifactLocation: sarifArtifactLocation(related.Position.Filename),
Region: sarif.Region{
StartLine: related.Position.Line,
StartColumn: related.Position.Column,
EndLine: related.End.Line,
EndColumn: related.End.Column,
},
},
})
}
if p.Severity == severityIgnored {
// Note that GitHub does not support suppressions, which is why Staticcheck still requires the -show-ignored flag to be set for us to emit ignored diagnostics.
r.Suppressions = []sarif.Suppression{{
Kind: "inSource",
// TODO(dh): populate the Justification field
}}
} else {
// We want an empty slice, not nil. SARIF differentiates
// between the two. An empty slice means that the diagnostic
// wasn't suppressed, while nil means that we don't have the
// information available.
r.Suppressions = []sarif.Suppression{}
}
run.Results = append(run.Results, r)
}
json.NewEncoder(os.Stdout).Encode(sarif.Log{
Version: sarif.Version,
Schema: sarif.Schema,
Runs: []sarif.Run{run},
})
}
================================================
FILE: lintcmd/stats.go
================================================
//go:build !aix && !android && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris
package lintcmd
import "os"
var infoSignals = []os.Signal{}
================================================
FILE: lintcmd/stats_bsd.go
================================================
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
package lintcmd
import (
"os"
"syscall"
)
var infoSignals = []os.Signal{syscall.SIGINFO}
================================================
FILE: lintcmd/stats_posix.go
================================================
//go:build aix || android || linux || solaris
package lintcmd
import (
"os"
"syscall"
)
var infoSignals = []os.Signal{syscall.SIGUSR1}
================================================
FILE: lintcmd/version/buildinfo.go
================================================
package version
import (
"fmt"
"runtime/debug"
)
func printBuildInfo() {
if info, ok := debug.ReadBuildInfo(); ok {
fmt.Println("Main module:")
printModule(&info.Main)
fmt.Println("Dependencies:")
for _, dep := range info.Deps {
printModule(dep)
}
} else {
fmt.Println("Built without Go modules")
}
}
func buildInfoVersion() (string, bool) {
info, ok := debug.ReadBuildInfo()
if !ok {
return "", false
}
if info.Main.Version == "(devel)" {
return "", false
}
return info.Main.Version, true
}
func printModule(m *debug.Module) {
fmt.Printf("\t%s", m.Path)
if m.Version != "(devel)" {
fmt.Printf("@%s", m.Version)
}
if m.Sum != "" {
fmt.Printf(" (sum: %s)", m.Sum)
}
if m.Replace != nil {
fmt.Printf(" (replace: %s)", m.Replace.Path)
}
fmt.Println()
}
================================================
FILE: lintcmd/version/version.go
================================================
package version
import (
"fmt"
"os"
"path/filepath"
"runtime"
)
const Version = "devel"
const MachineVersion = "devel"
// version returns a version descriptor and reports whether the
// version is a known release.
func version(human, machine string) (human_, machine_ string, known bool) {
if human != "devel" {
return human, machine, true
}
v, ok := buildInfoVersion()
if ok {
return v, "", false
}
return "devel", "", false
}
func Print(human, machine string) {
human, machine, release := version(human, machine)
if release {
fmt.Printf("%s %s (%s)\n", filepath.Base(os.Args[0]), human, machine)
} else if human == "devel" {
fmt.Printf("%s (no version)\n", filepath.Base(os.Args[0]))
} else {
fmt.Printf("%s (devel, %s)\n", filepath.Base(os.Args[0]), human)
}
}
func Verbose(human, machine string) {
Print(human, machine)
fmt.Println()
fmt.Println("Compiled with Go version:", runtime.Version())
printBuildInfo()
}
================================================
FILE: pattern/convert.go
================================================
package pattern
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
)
var astTypes = map[string]reflect.Type{
"Ellipsis": reflect.TypeFor[ast.Ellipsis](),
"RangeStmt": reflect.TypeFor[ast.RangeStmt](),
"AssignStmt": reflect.TypeFor[ast.AssignStmt](),
"IndexExpr": reflect.TypeFor[ast.IndexExpr](),
"IndexListExpr": reflect.TypeFor[ast.IndexListExpr](),
"Ident": reflect.TypeFor[ast.Ident](),
"ValueSpec": reflect.TypeFor[ast.ValueSpec](),
"GenDecl": reflect.TypeFor[ast.GenDecl](),
"BinaryExpr": reflect.TypeFor[ast.BinaryExpr](),
"ForStmt": reflect.TypeFor[ast.ForStmt](),
"ArrayType": reflect.TypeFor[ast.ArrayType](),
"DeferStmt": reflect.TypeFor[ast.DeferStmt](),
"MapType": reflect.TypeFor[ast.MapType](),
"ReturnStmt": reflect.TypeFor[ast.ReturnStmt](),
"SliceExpr": reflect.TypeFor[ast.SliceExpr](),
"StarExpr": reflect.TypeFor[ast.StarExpr](),
"UnaryExpr": reflect.TypeFor[ast.UnaryExpr](),
"SendStmt": reflect.TypeFor[ast.SendStmt](),
"SelectStmt": reflect.TypeFor[ast.SelectStmt](),
"ImportSpec": reflect.TypeFor[ast.ImportSpec](),
"IfStmt": reflect.TypeFor[ast.IfStmt](),
"GoStmt": reflect.TypeFor[ast.GoStmt](),
"Field": reflect.TypeFor[ast.Field](),
"SelectorExpr": reflect.TypeFor[ast.SelectorExpr](),
"StructType": reflect.TypeFor[ast.StructType](),
"KeyValueExpr": reflect.TypeFor[ast.KeyValueExpr](),
"FuncType": reflect.TypeFor[ast.FuncType](),
"FuncLit": reflect.TypeFor[ast.FuncLit](),
"FuncDecl": reflect.TypeFor[ast.FuncDecl](),
"ChanType": reflect.TypeFor[ast.ChanType](),
"CallExpr": reflect.TypeFor[ast.CallExpr](),
"CaseClause": reflect.TypeFor[ast.CaseClause](),
"CommClause": reflect.TypeFor[ast.CommClause](),
"CompositeLit": reflect.TypeFor[ast.CompositeLit](),
"EmptyStmt": reflect.TypeFor[ast.EmptyStmt](),
"SwitchStmt": reflect.TypeFor[ast.SwitchStmt](),
"TypeSwitchStmt": reflect.TypeFor[ast.TypeSwitchStmt](),
"TypeAssertExpr": reflect.TypeFor[ast.TypeAssertExpr](),
"TypeSpec": reflect.TypeFor[ast.TypeSpec](),
"InterfaceType": reflect.TypeFor[ast.InterfaceType](),
"BranchStmt": reflect.TypeFor[ast.BranchStmt](),
"IncDecStmt": reflect.TypeFor[ast.IncDecStmt](),
"BasicLit": reflect.TypeFor[ast.BasicLit](),
}
func ASTToNode(node any) Node {
switch node := node.(type) {
case *ast.File:
panic("cannot convert *ast.File to Node")
case nil:
return Nil{}
case string:
return String(node)
case token.Token:
return Token(node)
case *ast.ExprStmt:
return ASTToNode(node.X)
case *ast.BlockStmt:
if node == nil {
return Nil{}
}
return ASTToNode(node.List)
case *ast.FieldList:
if node == nil {
return Nil{}
}
return ASTToNode(node.List)
case *ast.BasicLit:
if node == nil {
return Nil{}
}
case *ast.ParenExpr:
return ASTToNode(node.X)
}
if node, ok := node.(ast.Node); ok {
name := reflect.TypeOf(node).Elem().Name()
T, ok := structNodes[name]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
if reflect.ValueOf(node).IsNil() {
return Nil{}
}
v := reflect.ValueOf(node).Elem()
objs := make([]Node, T.NumField())
for i := 0; i < T.NumField(); i++ {
f := v.FieldByName(T.Field(i).Name)
objs[i] = ASTToNode(f.Interface())
}
n, err := populateNode(name, objs, false)
if err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
return n
}
s := reflect.ValueOf(node)
if s.Kind() == reflect.Slice {
if s.Len() == 0 {
return List{}
}
if s.Len() == 1 {
return ASTToNode(s.Index(0).Interface())
}
tail := List{}
for i := s.Len() - 1; i >= 0; i-- {
head := ASTToNode(s.Index(i).Interface())
l := List{
Head: head,
Tail: tail,
}
tail = l
}
return tail
}
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
func NodeToAST(node Node, state State) any {
switch node := node.(type) {
case Binding:
v, ok := state[node.Name]
if !ok {
// really we want to return an error here
panic("XXX")
}
switch v := v.(type) {
case types.Object:
return &ast.Ident{Name: v.Name()}
default:
return v
}
case Builtin, Any, Object, Symbol, Not, Or:
panic("XXX")
case List:
if (node == List{}) {
return []ast.Node{}
}
x := []ast.Node{NodeToAST(node.Head, state).(ast.Node)}
x = append(x, NodeToAST(node.Tail, state).([]ast.Node)...)
return x
case Token:
return token.Token(node)
case String:
return string(node)
case Nil:
return nil
}
name := reflect.TypeOf(node).Name()
T, ok := astTypes[name]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
v := reflect.ValueOf(node)
out := reflect.New(T)
for i := 0; i < T.NumField(); i++ {
fNode := v.FieldByName(T.Field(i).Name)
if (fNode == reflect.Value{}) {
continue
}
fAST := out.Elem().FieldByName(T.Field(i).Name)
switch fAST.Type().Kind() {
case reflect.Slice:
c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state))
if c.Kind() != reflect.Slice {
// it's a single node in the pattern, we have to wrap
// it in a slice
slice := reflect.MakeSlice(fAST.Type(), 1, 1)
slice.Index(0).Set(c)
c = slice
}
switch fAST.Interface().(type) {
case []ast.Node:
switch cc := c.Interface().(type) {
case []ast.Node:
fAST.Set(c)
case []ast.Expr:
var slice []ast.Node
for _, el := range cc {
slice = append(slice, el)
}
fAST.Set(reflect.ValueOf(slice))
default:
panic("XXX")
}
case []ast.Expr:
switch cc := c.Interface().(type) {
case []ast.Node:
var slice []ast.Expr
for _, el := range cc {
slice = append(slice, el.(ast.Expr))
}
fAST.Set(reflect.ValueOf(slice))
case []ast.Expr:
fAST.Set(c)
default:
panic("XXX")
}
default:
panic("XXX")
}
case reflect.Int:
c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state))
switch c.Kind() {
case reflect.String:
tok, ok := tokensByString[c.Interface().(string)]
if !ok {
// really we want to return an error here
panic("XXX")
}
fAST.SetInt(int64(tok))
case reflect.Int:
fAST.Set(c)
default:
panic(fmt.Sprintf("internal error: unexpected kind %s", c.Kind()))
}
default:
r := NodeToAST(fNode.Interface().(Node), state)
if r != nil {
fAST.Set(reflect.ValueOf(r))
}
}
}
return out.Interface().(ast.Node)
}
================================================
FILE: pattern/doc.go
================================================
/*
Package pattern implements a simple language for pattern matching Go ASTs.
# Design decisions and trade-offs
The language is designed specifically for the task of filtering ASTs
to simplify the implementation of analyses in staticcheck.
It is also intended to be trivial to parse and execute.
To that end, we make certain decisions that make the language more
suited to its task, while making certain queries infeasible.
Furthermore, it is fully expected that the majority of analyses will still require ordinary Go code
to further process the filtered AST, to make use of type information and to enforce complex invariants.
It is not our goal to design a scripting language for writing entire checks in.
# The language
At its core, patterns are a representation of Go ASTs, allowing for the use of placeholders to enable pattern matching.
Their syntax is inspired by LISP and Haskell, but unlike LISP, the core unit of patterns isn't the list, but the node.
There is a fixed set of nodes, identified by name, and with the exception of the Or node, all nodes have a fixed number of arguments.
In addition to nodes, there are atoms, which represent basic units such as strings or the nil value.
Pattern matching is implemented via bindings, represented by the Binding node.
A Binding can match nodes and associate them with names, to later recall the nodes.
This allows for expressing "this node must be equal to that node" constraints.
To simplify writing and reading patterns, a small amount of additional syntax exists on top of nodes and atoms.
This additional syntax doesn't add any new features of its own, it simply provides shortcuts to creating nodes and atoms.
To show an example of a pattern, first consider this snippet of Go code:
if x := fn(); x != nil {
for _, v := range x {
println(v, x)
}
}
The corresponding AST expressed as an idiomatic pattern would look as follows:
(IfStmt
(AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") []))
(BinaryExpr (Ident "x") "!=" (Ident "nil"))
(RangeStmt
(Ident "_") (Ident "v") ":=" (Ident "x")
(CallExpr (Ident "println") [(Ident "v") (Ident "x")]))
nil)
Two things are worth noting about this representation.
First, the [el1 el2 ...] syntax is a short-hand for creating lists.
It is a short-hand for el1:el2:[], which itself is a short-hand for (List el1 (List el2 (List nil nil)).
Second, note the absence of a lot of lists in places that normally accept lists.
For example, assignment assigns a number of right-hands to a number of left-hands, yet our AssignStmt is lacking any form of list.
This is due to the fact that a single node can match a list of exactly one element.
Thus, the two following forms have identical matching behavior:
(AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") []))
(AssignStmt [(Ident "x")] ":=" [(CallExpr (Ident "fn") [])])
This section serves as an overview of the language's syntax.
More in-depth explanations of the matching behavior as well as an exhaustive list of node types follows in the coming sections.
# Pattern matching
# TODO write about pattern matching
- inspired by haskell syntax, but much, much simpler and naive
# Node types
The language contains two kinds of nodes: those that map to nodes in the AST, and those that implement additional logic.
Nodes that map directly to AST nodes are named identically to the types in the go/ast package.
What follows is an exhaustive list of these nodes:
(ArrayType len elt)
(AssignStmt lhs tok rhs)
(BasicLit kind value)
(BinaryExpr x op y)
(BranchStmt tok label)
(CallExpr fun args)
(CaseClause list body)
(ChanType dir value)
(CommClause comm body)
(CompositeLit type elts)
(DeferStmt call)
(Ellipsis elt)
(EmptyStmt)
(Field names type tag)
(ForStmt init cond post body)
(FuncDecl recv name type body)
(FuncLit type body)
(FuncType params results)
(GenDecl specs)
(GoStmt call)
(Ident name)
(IfStmt init cond body else)
(ImportSpec name path)
(IncDecStmt x tok)
(IndexExpr x index)
(InterfaceType methods)
(KeyValueExpr key value)
(MapType key value)
(RangeStmt key value tok x body)
(ReturnStmt results)
(SelectStmt body)
(SelectorExpr x sel)
(SendStmt chan value)
(SliceExpr x low high max)
(StarExpr x)
(StructType fields)
(SwitchStmt init tag body)
(TypeAssertExpr)
(TypeSpec name type)
(TypeSwitchStmt init assign body)
(UnaryExpr op x)
(ValueSpec names type values)
Additionally, there are the String, Token and nil atoms.
Strings are double-quoted string literals, as in (Ident "someName").
Tokens are also represented as double-quoted string literals, but are converted to token.Token values in contexts that require tokens,
such as in (BinaryExpr x "<" y), where "<" is transparently converted to token.LSS during matching.
The keyword 'nil' denotes the nil value, which represents the absence of any value.
We also define the (List head tail) node, which is used to represent sequences of elements as a singly linked list.
The head is a single element, and the tail is the remainder of the list.
For example,
(List "foo" (List "bar" (List "baz" (List nil nil))))
represents a list of three elements, "foo", "bar" and "baz". There is dedicated syntax for writing lists, which looks as follows:
["foo" "bar" "baz"]
This syntax is itself syntactic sugar for the following form:
"foo":"bar":"baz":[]
This form is of particular interest for pattern matching, as it allows matching on the head and tail. For example,
"foo":"bar":_
would match any list with at least two elements, where the first two elements are "foo" and "bar". This is equivalent to writing
(List "foo" (List "bar" _))
Note that it is not possible to match from the end of the list.
That is, there is no way to express a query such as "a list of any length where the last element is foo".
Note that unlike in LISP, nil and empty lists are distinct from one another.
In patterns, with respect to lists, nil is akin to Go's untyped nil.
It will match a nil ast.Node, but it will not match a nil []ast.Expr. Nil will, however, match pointers to named types such as *ast.Ident.
Similarly, lists are akin to Go's
slices. An empty list will match both a nil and an empty []ast.Expr, but it will not match a nil ast.Node.
Due to the difference between nil and empty lists, an empty list is represented as (List nil nil), i.e. a list with no head or tail.
Similarly, a list of one element is represented as (List el (List nil nil)). Unlike in LISP, it cannot be represented by (List el nil).
Finally, there are nodes that implement special logic or matching behavior.
(Any) matches any value. The underscore (_) maps to this node, making the following two forms equivalent:
(Ident _)
(Ident (Any))
(Builtin name) matches a built-in identifier or function by name.
This is a type-aware variant of (Ident name).
Instead of only comparing the name, it resolves the object behind the name and makes sure it's a pre-declared identifier.
For example, in the following piece of code
func fn() {
println(true)
true := false
println(true)
}
the pattern
(Builtin "true")
will match exactly once, on the first use of 'true' in the function.
Subsequent occurrences of 'true' no longer refer to the pre-declared identifier.
(Object name) matches an identifier by name, but yields the
types.Object it refers to.
(Symbol name) matches ast.Idents and ast.SelectorExprs that refer to a symbol with a given fully qualified name.
For example, "net/url.PathEscape" matches the PathEscape function in the net/url package,
and "(net/url.EscapeError).Error" refers to the Error method on the net/url.EscapeError type,
either on an instance of the type, or on the type itself.
For example, the following patterns match the following lines of code:
(CallExpr (Symbol "fmt.Println") _) // pattern 1
(CallExpr (Symbol "(net/url.EscapeError).Error") _) // pattern 2
fmt.Println("hello, world") // matches pattern 1
var x url.EscapeError
x.Error() // matches pattern 2
(url.EscapeError).Error(x) // also matches pattern 2
(Binding name node) creates or uses a binding.
Bindings work like variable assignments, allowing referring to already matched nodes.
As an example, bindings are necessary to match self-assignment of the form "x = x",
since we need to express that the right-hand side is identical to the left-hand side.
If a binding's node is not nil, the matcher will attempt to match a node according to the pattern.
If a binding's node is nil, the binding will either recall an existing value, or match the Any node.
It is an error to provide a non-nil node to a binding that has already been bound.
Referring back to the earlier example, the following pattern will match self-assignment of idents:
(AssignStmt (Binding "lhs" (Ident _)) "=" (Binding "lhs" nil))
Because bindings are a crucial component of pattern matching, there is special syntax for creating and recalling bindings.
Lower-case names refer to bindings. If standing on its own, the name "foo" will be equivalent to (Binding "foo" nil).
If a name is followed by an at-sign (@) then it will create a binding for the node that follows.
Together, this allows us to rewrite the earlier example as follows:
(AssignStmt lhs@(Ident _) "=" lhs)
(Or nodes...) is a variadic node that tries matching each node until one succeeds. For example, the following pattern matches all idents of name "foo" or "bar":
(Ident (Or "foo" "bar"))
We could also have written
(Or (Ident "foo") (Ident "bar"))
and achieved the same result. We can also mix different kinds of nodes:
(Or (Ident "foo") (CallExpr (Ident "bar") _))
When using bindings inside of nodes used inside Or, all or none of the bindings will be bound.
That is, partially matched nodes that ultimately failed to match will not produce any bindings observable outside of the matching attempt.
We can thus write
(Or (Ident name) (CallExpr name))
and 'name' will either be a String if the first option matched, or an Ident or SelectorExpr if the second option matched.
(Not node)
The Not node negates a match. For example, (Not (Ident _)) will match all nodes that aren't identifiers.
ChanDir(0)
# Automatic unnesting of AST nodes
The Go AST has several types of nodes that wrap other nodes.
To simplify matching, we automatically unwrap some of these nodes.
These nodes are ExprStmt (for using expressions in a statement context),
ParenExpr (for parenthesized expressions),
DeclStmt (for declarations in a statement context),
and LabeledStmt (for labeled statements).
Thus, the query
(FuncLit _ [(CallExpr _ _)]
will match a function literal containing a single function call,
even though in the actual Go AST, the CallExpr is nested inside an ExprStmt,
as function bodies are made up of sequences of statements.
On the flip-side, there is no way to specifically match these wrapper nodes.
For example, there is no way of searching for unnecessary parentheses, like in the following piece of Go code:
((x)) += 2
*/
package pattern
================================================
FILE: pattern/lexer.go
================================================
package pattern
import (
"fmt"
"go/token"
"iter"
"unicode"
"unicode/utf8"
)
// lex returns the sequence of tokens in the input.
func lex(f *token.File, input string) iter.Seq[item] {
return func(yield func(item) bool) {
lex := &lexer{
f: f,
input: input,
yield: yield,
}
lex.run()
}
}
// lexer holds the state of a single [lex] iteration.
type lexer struct {
f *token.File
input string
start int
pos int
width int
yield func(item) bool
}
type itemType int
const eof = -1
const (
itemError itemType = iota
itemLeftParen
itemRightParen
itemLeftBracket
itemRightBracket
itemTypeName
itemVariable
itemAt
itemColon
itemBlank
itemString
itemEOF
)
func (typ itemType) String() string {
switch typ {
case itemError:
return "ERROR"
case itemLeftParen:
return "("
case itemRightParen:
return ")"
case itemLeftBracket:
return "["
case itemRightBracket:
return "]"
case itemTypeName:
return "TYPE"
case itemVariable:
return "VAR"
case itemAt:
return "@"
case itemColon:
return ":"
case itemBlank:
return "_"
case itemString:
return "STRING"
case itemEOF:
return "EOF"
default:
return fmt.Sprintf("itemType(%d)", typ)
}
}
type item struct {
typ itemType
val string
pos int
}
type stateFn func(*lexer) stateFn
func (l *lexer) run() {
for state := lexStart; state != nil; {
state = state(l)
}
}
func (l *lexer) emitValue(t itemType, value string) bool {
ok := l.yield(item{t, value, l.start})
l.start = l.pos
return ok
}
func (l *lexer) emit(t itemType) bool {
ok := l.yield(item{t, l.input[l.start:l.pos], l.start})
l.start = l.pos
return ok
}
func lexStart(l *lexer) stateFn {
switch r := l.next(); {
case r == eof:
_ = l.emit(itemEOF)
return nil
case unicode.IsSpace(r):
l.ignore()
case r == '(':
if !l.emit(itemLeftParen) {
return nil
}
case r == ')':
if !l.emit(itemRightParen) {
return nil
}
case r == '[':
if !l.emit(itemLeftBracket) {
return nil
}
case r == ']':
if !l.emit(itemRightBracket) {
return nil
}
case r == '@':
if !l.emit(itemAt) {
return nil
}
case r == ':':
if !l.emit(itemColon) {
return nil
}
case r == '_':
if !l.emit(itemBlank) {
return nil
}
case r == '"':
l.backup()
return lexString
case unicode.IsUpper(r):
l.backup()
return lexType
case unicode.IsLower(r):
l.backup()
return lexVariable
default:
return l.errorf("unexpected character %c", r)
}
return lexStart
}
func (l *lexer) next() (r rune) {
if l.pos >= len(l.input) {
l.width = 0
return eof
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
if r == '\n' {
l.f.AddLine(l.pos)
}
l.pos += l.width
return r
}
func (l *lexer) ignore() {
l.start = l.pos
}
func (l *lexer) backup() {
l.pos -= l.width
}
func (l *lexer) errorf(format string, args ...any) stateFn {
// TODO(dh): emit position information in errors
_ = l.yield(item{
itemError,
fmt.Sprintf(format, args...),
l.start,
})
return nil
}
func isAlphaNumeric(r rune) bool {
return r >= '0' && r <= '9' ||
r >= 'a' && r <= 'z' ||
r >= 'A' && r <= 'Z'
}
func lexString(l *lexer) stateFn {
l.next() // skip quote
escape := false
var runes []rune
for {
switch r := l.next(); r {
case eof:
return l.errorf("unterminated string")
case '"':
if !escape {
if !l.emitValue(itemString, string(runes)) {
return nil
}
return lexStart
} else {
runes = append(runes, '"')
escape = false
}
case '\\':
if escape {
runes = append(runes, '\\')
escape = false
} else {
escape = true
}
default:
runes = append(runes, r)
}
}
}
func lexType(l *lexer) stateFn {
l.next()
for {
if !isAlphaNumeric(l.next()) {
l.backup()
if !l.emit(itemTypeName) {
return nil
}
return lexStart
}
}
}
func lexVariable(l *lexer) stateFn {
l.next()
for {
if !isAlphaNumeric(l.next()) {
l.backup()
if !l.emit(itemVariable) {
return nil
}
return lexStart
}
}
}
================================================
FILE: pattern/match.go
================================================
package pattern
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
)
var tokensByString = map[string]Token{
"INT": Token(token.INT),
"FLOAT": Token(token.FLOAT),
"IMAG": Token(token.IMAG),
"CHAR": Token(token.CHAR),
"STRING": Token(token.STRING),
"+": Token(token.ADD),
"-": Token(token.SUB),
"*": Token(token.MUL),
"/": Token(token.QUO),
"%": Token(token.REM),
"&": Token(token.AND),
"|": Token(token.OR),
"^": Token(token.XOR),
"<<": Token(token.SHL),
">>": Token(token.SHR),
"&^": Token(token.AND_NOT),
"+=": Token(token.ADD_ASSIGN),
"-=": Token(token.SUB_ASSIGN),
"*=": Token(token.MUL_ASSIGN),
"/=": Token(token.QUO_ASSIGN),
"%=": Token(token.REM_ASSIGN),
"&=": Token(token.AND_ASSIGN),
"|=": Token(token.OR_ASSIGN),
"^=": Token(token.XOR_ASSIGN),
"<<=": Token(token.SHL_ASSIGN),
">>=": Token(token.SHR_ASSIGN),
"&^=": Token(token.AND_NOT_ASSIGN),
"&&": Token(token.LAND),
"||": Token(token.LOR),
"<-": Token(token.ARROW),
"++": Token(token.INC),
"--": Token(token.DEC),
"==": Token(token.EQL),
"<": Token(token.LSS),
">": Token(token.GTR),
"=": Token(token.ASSIGN),
"!": Token(token.NOT),
"!=": Token(token.NEQ),
"<=": Token(token.LEQ),
">=": Token(token.GEQ),
":=": Token(token.DEFINE),
"...": Token(token.ELLIPSIS),
"IMPORT": Token(token.IMPORT),
"VAR": Token(token.VAR),
"TYPE": Token(token.TYPE),
"CONST": Token(token.CONST),
"BREAK": Token(token.BREAK),
"CONTINUE": Token(token.CONTINUE),
"GOTO": Token(token.GOTO),
"FALLTHROUGH": Token(token.FALLTHROUGH),
}
func maybeToken(node Node) (Node, bool) {
if node, ok := node.(String); ok {
if tok, ok := tokensByString[string(node)]; ok {
return tok, true
}
return node, false
}
return node, false
}
func isNil(v any) bool {
if v == nil {
return true
}
if _, ok := v.(Nil); ok {
return true
}
return false
}
type matcher interface {
Match(*Matcher, any) (any, bool)
}
type State = map[string]any
type Matcher struct {
TypesInfo *types.Info
State State
bindingsMapping []string
setBindings []uint64
}
func (m *Matcher) set(b Binding, value any) {
m.State[b.Name] = value
m.setBindings[len(m.setBindings)-1] |= 1 << b.idx
}
func (m *Matcher) push() {
m.setBindings = append(m.setBindings, 0)
}
func (m *Matcher) pop() {
set := m.setBindings[len(m.setBindings)-1]
if set != 0 {
for i := 0; i < len(m.bindingsMapping); i++ {
if (set & (1 << i)) != 0 {
key := m.bindingsMapping[i]
delete(m.State, key)
}
}
}
m.setBindings = m.setBindings[:len(m.setBindings)-1]
}
func (m *Matcher) merge() {
m.setBindings = m.setBindings[:len(m.setBindings)-1]
}
func (m *Matcher) Match(a Pattern, b ast.Node) bool {
m.bindingsMapping = a.Bindings
m.State = State{}
m.push()
_, ok := match(m, a.Root, b)
m.merge()
if len(m.setBindings) != 0 {
panic(fmt.Sprintf("%d entries left on the stack, expected none", len(m.setBindings)))
}
return ok
}
func Match(a Pattern, b ast.Node) (*Matcher, bool) {
m := &Matcher{}
ret := m.Match(a, b)
return m, ret
}
// Match two items, which may be (Node, AST) or (AST, AST)
func match(m *Matcher, l, r any) (any, bool) {
if _, ok := r.(Node); ok {
panic("Node mustn't be on right side of match")
}
switch l := l.(type) {
case *ast.ParenExpr:
return match(m, l.X, r)
case *ast.ExprStmt:
return match(m, l.X, r)
case *ast.DeclStmt:
return match(m, l.Decl, r)
case *ast.LabeledStmt:
return match(m, l.Stmt, r)
case *ast.BlockStmt:
return match(m, l.List, r)
case *ast.FieldList:
if l == nil {
return match(m, nil, r)
} else {
return match(m, l.List, r)
}
}
switch r := r.(type) {
case *ast.ParenExpr:
return match(m, l, r.X)
case *ast.ExprStmt:
return match(m, l, r.X)
case *ast.DeclStmt:
return match(m, l, r.Decl)
case *ast.LabeledStmt:
return match(m, l, r.Stmt)
case *ast.BlockStmt:
if r == nil {
return match(m, l, nil)
}
return match(m, l, r.List)
case *ast.FieldList:
if r == nil {
return match(m, l, nil)
}
return match(m, l, r.List)
case *ast.BasicLit:
if r == nil {
return match(m, l, nil)
}
}
if l, ok := l.(matcher); ok {
return l.Match(m, r)
}
if l, ok := l.(Node); ok {
// Matching of pattern with concrete value
return matchNodeAST(m, l, r)
}
if l == nil || r == nil {
return nil, l == r
}
{
ln, ok1 := l.(ast.Node)
rn, ok2 := r.(ast.Node)
if ok1 && ok2 {
return matchAST(m, ln, rn)
}
}
{
obj, ok := l.(types.Object)
if ok {
switch r := r.(type) {
case *ast.Ident:
return obj, obj == m.TypesInfo.ObjectOf(r)
case *ast.SelectorExpr:
return obj, obj == m.TypesInfo.ObjectOf(r.Sel)
default:
return obj, false
}
}
}
// TODO(dh): the three blocks handling slices can be combined into a single block if we use reflection
{
ln, ok1 := l.([]ast.Expr)
rn, ok2 := r.([]ast.Expr)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(ast.Expr)
if !ok {
return nil, false
}
rn = []ast.Expr{cast}
} else if !ok1 && ok2 {
cast, ok := l.(ast.Expr)
if !ok {
return nil, false
}
ln = []ast.Expr{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
{
ln, ok1 := l.([]ast.Stmt)
rn, ok2 := r.([]ast.Stmt)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(ast.Stmt)
if !ok {
return nil, false
}
rn = []ast.Stmt{cast}
} else if !ok1 && ok2 {
cast, ok := l.(ast.Stmt)
if !ok {
return nil, false
}
ln = []ast.Stmt{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
{
ln, ok1 := l.([]*ast.Field)
rn, ok2 := r.([]*ast.Field)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(*ast.Field)
if !ok {
return nil, false
}
rn = []*ast.Field{cast}
} else if !ok1 && ok2 {
cast, ok := l.(*ast.Field)
if !ok {
return nil, false
}
ln = []*ast.Field{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
return nil, false
}
// Match a Node with an AST node
func matchNodeAST(m *Matcher, a Node, b any) (any, bool) {
switch b := b.(type) {
case []ast.Stmt:
// 'a' is not a List or we'd be using its Match
// implementation.
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case []ast.Expr:
// 'a' is not a List or we'd be using its Match
// implementation.
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case []*ast.Field:
// 'a' is not a List or we'd be using its Match
// implementation
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case ast.Node:
ra := reflect.ValueOf(a)
rb := reflect.ValueOf(b).Elem()
if ra.Type().Name() != rb.Type().Name() {
return nil, false
}
for i := 0; i < ra.NumField(); i++ {
af := ra.Field(i)
fieldName := ra.Type().Field(i).Name
bf := rb.FieldByName(fieldName)
if (bf == reflect.Value{}) {
panic(fmt.Sprintf("internal error: could not find field %s in type %t when comparing with %T", fieldName, b, a))
}
ai := af.Interface()
bi := bf.Interface()
if ai == nil {
return b, bi == nil
}
if _, ok := match(m, ai.(Node), bi); !ok {
return b, false
}
}
return b, true
case nil:
return nil, a == Nil{}
case string, token.Token:
// 'a' can't be a String, Token, or Binding or we'd be using their Match implementations.
return nil, false
default:
panic(fmt.Sprintf("unhandled type %T", b))
}
}
// Match two AST nodes
func matchAST(m *Matcher, a, b ast.Node) (any, bool) {
ra := reflect.ValueOf(a)
rb := reflect.ValueOf(b)
if ra.Type() != rb.Type() {
return nil, false
}
if ra.IsNil() || rb.IsNil() {
return rb, ra.IsNil() == rb.IsNil()
}
ra = ra.Elem()
rb = rb.Elem()
for i := 0; i < ra.NumField(); i++ {
af := ra.Field(i)
bf := rb.Field(i)
if af.Type() == rtTokPos || af.Type() == rtObject || af.Type() == rtCommentGroup {
continue
}
switch af.Kind() {
case reflect.Slice:
if af.Len() != bf.Len() {
return nil, false
}
for j := 0; j < af.Len(); j++ {
if _, ok := match(m, af.Index(j).Interface().(ast.Node), bf.Index(j).Interface().(ast.Node)); !ok {
return nil, false
}
}
case reflect.String:
if af.String() != bf.String() {
return nil, false
}
case reflect.Int:
if af.Int() != bf.Int() {
return nil, false
}
case reflect.Bool:
if af.Bool() != bf.Bool() {
return nil, false
}
case reflect.Pointer, reflect.Interface:
if _, ok := match(m, af.Interface(), bf.Interface()); !ok {
return nil, false
}
default:
panic(fmt.Sprintf("internal error: unhandled kind %s (%T)", af.Kind(), af.Interface()))
}
}
return b, true
}
func (b Binding) Match(m *Matcher, node any) (any, bool) {
if isNil(b.Node) {
v, ok := m.State[b.Name]
if ok {
// Recall value
return match(m, v, node)
}
// Matching anything
b.Node = Any{}
}
// Store value
if _, ok := m.State[b.Name]; ok {
panic(fmt.Sprintf("binding already created: %s", b.Name))
}
new, ret := match(m, b.Node, node)
if ret {
m.set(b, new)
}
return new, ret
}
func (Any) Match(m *Matcher, node any) (any, bool) {
return node, true
}
func (l List) Match(m *Matcher, node any) (any, bool) {
v := reflect.ValueOf(node)
if v.Kind() == reflect.Slice {
if isNil(l.Head) {
return node, v.Len() == 0
}
if v.Len() == 0 {
return nil, false
}
// OPT(dh): don't check the entire tail if head didn't match
_, ok1 := match(m, l.Head, v.Index(0).Interface())
_, ok2 := match(m, l.Tail, v.Slice(1, v.Len()).Interface())
return node, ok1 && ok2
}
// Our empty list does not equal an untyped Go nil. This way, we can
// tell apart an if with no else and an if with an empty else.
return nil, false
}
func (s String) Match(m *Matcher, node any) (any, bool) {
switch o := node.(type) {
case token.Token:
if tok, ok := maybeToken(s); ok {
return match(m, tok, node)
}
return nil, false
case string:
return o, string(s) == o
case types.TypeAndValue:
return o, o.Value != nil && o.Value.String() == string(s)
default:
return nil, false
}
}
func (tok Token) Match(m *Matcher, node any) (any, bool) {
o, ok := node.(token.Token)
if !ok {
return nil, false
}
return o, token.Token(tok) == o
}
func (Nil) Match(m *Matcher, node any) (any, bool) {
if isNil(node) {
return nil, true
}
v := reflect.ValueOf(node)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return nil, v.IsNil()
default:
return nil, false
}
}
func (builtin Builtin) Match(m *Matcher, node any) (any, bool) {
r, ok := match(m, Ident(builtin), node)
if !ok {
return nil, false
}
ident := r.(*ast.Ident)
obj := m.TypesInfo.ObjectOf(ident)
if obj != types.Universe.Lookup(ident.Name) {
return nil, false
}
return ident, true
}
func (obj Object) Match(m *Matcher, node any) (any, bool) {
r, ok := match(m, Ident(obj), node)
if !ok {
return nil, false
}
ident := r.(*ast.Ident)
id := m.TypesInfo.ObjectOf(ident)
_, ok = match(m, obj.Name, ident.Name)
return id, ok
}
func (fn Symbol) Match(m *Matcher, node any) (any, bool) {
var name string
var obj types.Object
base := []Node{
Ident{Any{}},
SelectorExpr{Any{}, Any{}},
}
p := Or{
Nodes: append(base,
IndexExpr{Or{Nodes: base}, Any{}},
IndexListExpr{Or{Nodes: base}, Any{}})}
r, ok := match(m, p, node)
if !ok {
return nil, false
}
fun := r.(ast.Expr)
switch idx := fun.(type) {
case *ast.IndexExpr:
fun = idx.X
case *ast.IndexListExpr:
fun = idx.X
}
switch fun := ast.Unparen(fun).(type) {
case *ast.Ident:
obj = m.TypesInfo.ObjectOf(fun)
case *ast.SelectorExpr:
obj = m.TypesInfo.ObjectOf(fun.Sel)
default:
panic("unreachable")
}
switch obj := obj.(type) {
case *types.Func:
// OPT(dh): optimize this similar to code.FuncName
name = obj.FullName()
case *types.Builtin:
name = obj.Name()
case *types.TypeName:
origObj := obj
for {
if obj.Parent() != obj.Pkg().Scope() {
return nil, false
}
name = types.TypeString(obj.Type(), nil)
_, ok = match(m, fn.Name, name)
if ok || !obj.IsAlias() {
return origObj, ok
} else {
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
// github.com/golang/go/issues/66559
switch typ := types.Unalias(obj.Type()).(type) {
case interface{ Obj() *types.TypeName }:
obj = typ.Obj()
case *types.Basic:
return match(m, fn.Name, typ.Name())
default:
return nil, false
}
}
}
case *types.Const, *types.Var:
if obj.Pkg() == nil {
return nil, false
}
if obj.Parent() != obj.Pkg().Scope() {
return nil, false
}
name = fmt.Sprintf("%s.%s", obj.Pkg().Path(), obj.Name())
default:
return nil, false
}
_, ok = match(m, fn.Name, name)
return obj, ok
}
func (or Or) Match(m *Matcher, node any) (any, bool) {
for _, opt := range or.Nodes {
m.push()
if ret, ok := match(m, opt, node); ok {
m.merge()
return ret, true
} else {
m.pop()
}
}
return nil, false
}
func (not Not) Match(m *Matcher, node any) (any, bool) {
_, ok := match(m, not.Node, node)
if ok {
return nil, false
}
return node, true
}
var integerLiteralQ = MustParse(`(Or (BasicLit "INT" _) (UnaryExpr (Or "+" "-") (IntegerLiteral _)))`)
func (lit IntegerLiteral) Match(m *Matcher, node any) (any, bool) {
matched, ok := match(m, integerLiteralQ.Root, node)
if !ok {
return nil, false
}
tv, ok := m.TypesInfo.Types[matched.(ast.Expr)]
if !ok {
return nil, false
}
if tv.Value == nil {
return nil, false
}
_, ok = match(m, lit.Value, tv)
return matched, ok
}
func (texpr TrulyConstantExpression) Match(m *Matcher, node any) (any, bool) {
expr, ok := node.(ast.Expr)
if !ok {
return nil, false
}
tv, ok := m.TypesInfo.Types[expr]
if !ok {
return nil, false
}
if tv.Value == nil {
return nil, false
}
truly := true
ast.Inspect(expr, func(node ast.Node) bool {
if _, ok := node.(*ast.Ident); ok {
truly = false
return false
}
return true
})
if !truly {
return nil, false
}
_, ok = match(m, texpr.Value, tv)
return expr, ok
}
var (
// Types of fields in go/ast structs that we want to skip
rtTokPos = reflect.TypeFor[token.Pos]()
//lint:ignore SA1019 It's deprecated, but we still want to skip the field.
rtObject = reflect.TypeFor[*ast.Object]()
rtCommentGroup = reflect.TypeFor[*ast.CommentGroup]()
)
var (
_ matcher = Binding{}
_ matcher = Any{}
_ matcher = List{}
_ matcher = String("")
_ matcher = Token(0)
_ matcher = Nil{}
_ matcher = Builtin{}
_ matcher = Object{}
_ matcher = Symbol{}
_ matcher = Or{}
_ matcher = Not{}
_ matcher = IntegerLiteral{}
_ matcher = TrulyConstantExpression{}
)
================================================
FILE: pattern/parser.go
================================================
package pattern
import (
"errors"
"fmt"
"go/ast"
"go/token"
"iter"
"reflect"
"strings"
)
type Pattern struct {
Root Node
// EntryNodes contains instances of ast.Node that could potentially
// initiate a successful match of the pattern.
EntryNodes []ast.Node
// SymbolsPattern is a pattern consisting or Any, Or, And, and IndexSymbol,
// that can be used to implement fast rejection of whole packages using
// typeindex.
SymbolsPattern Node
// If non-empty, all possible candidate nodes for this pattern can be found
// by finding all call expressions for this list of symbols.
RootCallSymbols []IndexSymbol
// Mapping from binding index to binding name
Bindings []string
}
func MustParse(s string) Pattern {
p := &Parser{AllowTypeInfo: true}
pat, err := p.Parse(s)
if err != nil {
panic(err)
}
return pat
}
func symbolToIndexSymbol(name string) IndexSymbol {
if len(name) == 0 {
return IndexSymbol{}
}
if name[0] == '(' {
end := strings.IndexAny(name, ")")
// Ensure there's a ), and also that there are at least two more
// characters after it, for a dot and an identifier.
if end == -1 || end > len(name)-2 {
return IndexSymbol{}
}
pathAndType := strings.TrimPrefix(name[1:end], "*")
dot := strings.LastIndex(pathAndType, ".")
if dot == -1 {
return IndexSymbol{}
}
path := pathAndType[:dot]
typ := pathAndType[dot+1:]
ident := name[end+2:]
return IndexSymbol{path, typ, ident}
} else {
dot := strings.LastIndex(name, ".")
if dot == -1 {
return IndexSymbol{"", "", name}
}
path := name[:dot]
ident := name[dot+1:]
return IndexSymbol{path, "", ident}
}
}
func collectSymbols(node Node, inSymbol bool) Node {
and := func(c Node, out *And) {
switch cc := c.(type) {
case And:
out.Nodes = append(out.Nodes, cc.Nodes...)
case Any:
case nil:
default:
out.Nodes = append(out.Nodes, c)
}
}
switch node := node.(type) {
case Or:
s := Or{}
for _, el := range node.Nodes {
c := collectSymbols(el, inSymbol)
switch cc := c.(type) {
case Or:
s.Nodes = append(s.Nodes, cc.Nodes...)
case Any:
return Any{}
case nil:
default:
s.Nodes = append(s.Nodes, c)
}
}
switch len(s.Nodes) {
case 0:
return nil
case 1:
return s.Nodes[0]
default:
return s
}
case Not, Token, nil:
return Any{}
case Symbol:
return collectSymbols(node.Name, true)
case String:
if !inSymbol {
return Any{}
}
// In logically correct patterns, all Strings that are children of
// Symbols describe the names of symbols.
return symbolToIndexSymbol(string(node))
case Binding:
return collectSymbols(node.Node, inSymbol)
case Any:
return Any{}
case List:
var out And
and(collectSymbols(node.Head, inSymbol), &out)
and(collectSymbols(node.Tail, inSymbol), &out)
switch len(out.Nodes) {
case 0:
return Any{}
case 1:
return out.Nodes[0]
default:
return out
}
default:
var out And
rv := reflect.ValueOf(node)
for i := range rv.NumField() {
c := collectSymbols(rv.Field(i).Interface().(Node), inSymbol)
and(c, &out)
}
switch len(out.Nodes) {
case 0:
return Any{}
case 1:
return out.Nodes[0]
default:
return out
}
}
}
func collectRootCallSymbols(node Node) []IndexSymbol {
root, ok := node.(CallExpr)
if !ok {
return nil
}
var names []String
var handleSymName func(name Node) bool
handleSymName = func(name Node) bool {
switch name := name.(type) {
case String:
names = append(names, name)
case Or:
for _, node := range name.Nodes {
if name, ok := node.(String); ok {
names = append(names, name)
} else {
return false
}
}
case Binding:
return handleSymName(name.Node)
default:
return false
}
return true
}
var handleRootFun func(node Node) bool
handleRootFun = func(node Node) bool {
switch fun := node.(type) {
case Binding:
return handleRootFun(fun.Node)
case Symbol:
return handleSymName(fun.Name)
case Or:
for _, node := range fun.Nodes {
if sym, ok := node.(Symbol); !ok || !handleSymName(sym.Name) {
return false
}
}
return true
default:
return false
}
}
if !handleRootFun(root.Fun) {
return nil
}
out := make([]IndexSymbol, len(names))
for i, name := range names {
out[i] = symbolToIndexSymbol(string(name))
}
return out
}
func collectEntryNodes(node Node, m map[reflect.Type]struct{}) {
switch node := node.(type) {
case Or:
for _, el := range node.Nodes {
collectEntryNodes(el, m)
}
case Not:
collectEntryNodes(node.Node, m)
case Binding:
collectEntryNodes(node.Node, m)
case Nil, nil:
// this branch is reached via bindings
for _, T := range allTypes {
m[T] = struct{}{}
}
default:
Ts, ok := nodeToASTTypes[reflect.TypeOf(node)]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
for _, T := range Ts {
m[T] = struct{}{}
}
}
}
var allTypes = []reflect.Type{
reflect.TypeFor[*ast.RangeStmt](),
reflect.TypeFor[*ast.AssignStmt](),
reflect.TypeFor[*ast.IndexExpr](),
reflect.TypeFor[*ast.Ident](),
reflect.TypeFor[*ast.ValueSpec](),
reflect.TypeFor[*ast.GenDecl](),
reflect.TypeFor[*ast.BinaryExpr](),
reflect.TypeFor[*ast.ForStmt](),
reflect.TypeFor[*ast.ArrayType](),
reflect.TypeFor[*ast.DeferStmt](),
reflect.TypeFor[*ast.MapType](),
reflect.TypeFor[*ast.ReturnStmt](),
reflect.TypeFor[*ast.SliceExpr](),
reflect.TypeFor[*ast.StarExpr](),
reflect.TypeFor[*ast.UnaryExpr](),
reflect.TypeFor[*ast.SendStmt](),
reflect.TypeFor[*ast.SelectStmt](),
reflect.TypeFor[*ast.ImportSpec](),
reflect.TypeFor[*ast.IfStmt](),
reflect.TypeFor[*ast.GoStmt](),
reflect.TypeFor[*ast.Field](),
reflect.TypeFor[*ast.SelectorExpr](),
reflect.TypeFor[*ast.StructType](),
reflect.TypeFor[*ast.KeyValueExpr](),
reflect.TypeFor[*ast.FuncType](),
reflect.TypeFor[*ast.FuncLit](),
reflect.TypeFor[*ast.FuncDecl](),
reflect.TypeFor[*ast.ChanType](),
reflect.TypeFor[*ast.CallExpr](),
reflect.TypeFor[*ast.CaseClause](),
reflect.TypeFor[*ast.CommClause](),
reflect.TypeFor[*ast.CompositeLit](),
reflect.TypeFor[*ast.EmptyStmt](),
reflect.TypeFor[*ast.SwitchStmt](),
reflect.TypeFor[*ast.TypeSwitchStmt](),
reflect.TypeFor[*ast.TypeAssertExpr](),
reflect.TypeFor[*ast.TypeSpec](),
reflect.TypeFor[*ast.InterfaceType](),
reflect.TypeFor[*ast.BranchStmt](),
reflect.TypeFor[*ast.IncDecStmt](),
reflect.TypeFor[*ast.BasicLit](),
}
var nodeToASTTypes = map[reflect.Type][]reflect.Type{
reflect.TypeFor[String](): nil,
reflect.TypeFor[Token](): nil,
reflect.TypeFor[List](): {reflect.TypeFor[*ast.BlockStmt](), reflect.TypeFor[*ast.FieldList]()},
reflect.TypeFor[Builtin](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[Object](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[Symbol](): {reflect.TypeFor[*ast.Ident](), reflect.TypeFor[*ast.SelectorExpr]()},
reflect.TypeFor[Any](): allTypes,
reflect.TypeFor[RangeStmt](): {reflect.TypeFor[*ast.RangeStmt]()},
reflect.TypeFor[AssignStmt](): {reflect.TypeFor[*ast.AssignStmt]()},
reflect.TypeFor[IndexExpr](): {reflect.TypeFor[*ast.IndexExpr]()},
reflect.TypeFor[Ident](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[ValueSpec](): {reflect.TypeFor[*ast.ValueSpec]()},
reflect.TypeFor[GenDecl](): {reflect.TypeFor[*ast.GenDecl]()},
reflect.TypeFor[BinaryExpr](): {reflect.TypeFor[*ast.BinaryExpr]()},
reflect.TypeFor[ForStmt](): {reflect.TypeFor[*ast.ForStmt]()},
reflect.TypeFor[ArrayType](): {reflect.TypeFor[*ast.ArrayType]()},
reflect.TypeFor[DeferStmt](): {reflect.TypeFor[*ast.DeferStmt]()},
reflect.TypeFor[MapType](): {reflect.TypeFor[*ast.MapType]()},
reflect.TypeFor[ReturnStmt](): {reflect.TypeFor[*ast.ReturnStmt]()},
reflect.TypeFor[SliceExpr](): {reflect.TypeFor[*ast.SliceExpr]()},
reflect.TypeFor[StarExpr](): {reflect.TypeFor[*ast.StarExpr]()},
reflect.TypeFor[UnaryExpr](): {reflect.TypeFor[*ast.UnaryExpr]()},
reflect.TypeFor[SendStmt](): {reflect.TypeFor[*ast.SendStmt]()},
reflect.TypeFor[SelectStmt](): {reflect.TypeFor[*ast.SelectStmt]()},
reflect.TypeFor[ImportSpec](): {reflect.TypeFor[*ast.ImportSpec]()},
reflect.TypeFor[IfStmt](): {reflect.TypeFor[*ast.IfStmt]()},
reflect.TypeFor[GoStmt](): {reflect.TypeFor[*ast.GoStmt]()},
reflect.TypeFor[Field](): {reflect.TypeFor[*ast.Field]()},
reflect.TypeFor[SelectorExpr](): {reflect.TypeFor[*ast.SelectorExpr]()},
reflect.TypeFor[StructType](): {reflect.TypeFor[*ast.StructType]()},
reflect.TypeFor[KeyValueExpr](): {reflect.TypeFor[*ast.KeyValueExpr]()},
reflect.TypeFor[FuncType](): {reflect.TypeFor[*ast.FuncType]()},
reflect.TypeFor[FuncLit](): {reflect.TypeFor[*ast.FuncLit]()},
reflect.TypeFor[FuncDecl](): {reflect.TypeFor[*ast.FuncDecl]()},
reflect.TypeFor[ChanType](): {reflect.TypeFor[*ast.ChanType]()},
reflect.TypeFor[CallExpr](): {reflect.TypeFor[*ast.CallExpr]()},
reflect.TypeFor[CaseClause](): {reflect.TypeFor[*ast.CaseClause]()},
reflect.TypeFor[CommClause](): {reflect.TypeFor[*ast.CommClause]()},
reflect.TypeFor[CompositeLit](): {reflect.TypeFor[*ast.CompositeLit]()},
reflect.TypeFor[EmptyStmt](): {reflect.TypeFor[*ast.EmptyStmt]()},
reflect.TypeFor[SwitchStmt](): {reflect.TypeFor[*ast.SwitchStmt]()},
reflect.TypeFor[TypeSwitchStmt](): {reflect.TypeFor[*ast.TypeSwitchStmt]()},
reflect.TypeFor[TypeAssertExpr](): {reflect.TypeFor[*ast.TypeAssertExpr]()},
reflect.TypeFor[TypeSpec](): {reflect.TypeFor[*ast.TypeSpec]()},
reflect.TypeFor[InterfaceType](): {reflect.TypeFor[*ast.InterfaceType]()},
reflect.TypeFor[BranchStmt](): {reflect.TypeFor[*ast.BranchStmt]()},
reflect.TypeFor[IncDecStmt](): {reflect.TypeFor[*ast.IncDecStmt]()},
reflect.TypeFor[BasicLit](): {reflect.TypeFor[*ast.BasicLit]()},
reflect.TypeFor[IntegerLiteral](): {reflect.TypeFor[*ast.BasicLit](), reflect.TypeFor[*ast.UnaryExpr]()},
reflect.TypeFor[TrulyConstantExpression](): allTypes, // this is an over-approximation, which is fine
}
var requiresTypeInfo = map[string]bool{
"Symbol": true,
"Builtin": true,
"Object": true,
"IntegerLiteral": true,
"TrulyConstantExpression": true,
}
type Parser struct {
// Allow nodes that rely on type information
AllowTypeInfo bool
f *token.File
cur item
last *item
nextItem func() (item, bool)
bindings map[string]int
}
func (p *Parser) bindingIndex(name string) int {
if p.bindings == nil {
p.bindings = map[string]int{}
}
if idx, ok := p.bindings[name]; ok {
return idx
}
idx := len(p.bindings)
p.bindings[name] = idx
return idx
}
func (p *Parser) Parse(s string) (Pattern, error) {
f := token.NewFileSet().AddFile(" ", -1, len(s))
// Run the lexer iterator as a coroutine.
// The parser will call 'next' to consume each item.
// After the parser returns, we must call 'stop' to
// terminate the coroutine.
next, stop := iter.Pull(lex(f, s))
defer stop()
p.cur = item{}
p.last = nil
p.f = f
p.nextItem = next
// Parse.
root, err := p.node()
if err != nil {
return Pattern{}, err
}
// Consume final EOF token.
if item, ok := next(); !ok || item.typ != itemEOF {
return Pattern{}, fmt.Errorf("unexpected token %s after end of pattern", item.typ)
}
if len(p.bindings) > 64 {
return Pattern{}, errors.New("encountered more than 64 bindings")
}
bindings := make([]string, len(p.bindings))
for name, idx := range p.bindings {
bindings[idx] = name
}
_, isSymbol := root.(Symbol)
sym := collectSymbols(root, isSymbol)
rootSyms := collectRootCallSymbols(root)
relevantMap := map[reflect.Type]struct{}{}
collectEntryNodes(root, relevantMap)
relevantNodes := make([]ast.Node, 0, len(relevantMap))
for k := range relevantMap {
relevantNodes = append(relevantNodes, reflect.Zero(k).Interface().(ast.Node))
}
return Pattern{
Root: root,
EntryNodes: relevantNodes,
SymbolsPattern: sym,
RootCallSymbols: rootSyms,
Bindings: bindings,
}, nil
}
func (p *Parser) next() item {
if p.last != nil {
n := *p.last
p.last = nil
return n
}
var ok bool
p.cur, ok = p.nextItem()
if !ok {
p.cur = item{typ: eof}
}
return p.cur
}
func (p *Parser) rewind() {
p.last = &p.cur
}
func (p *Parser) peek() item {
n := p.next()
p.rewind()
return n
}
func (p *Parser) accept(typ itemType) (item, bool) {
n := p.next()
if n.typ == typ {
return n, true
}
p.rewind()
return item{}, false
}
func (p *Parser) unexpectedToken(valid string) error {
if p.cur.typ == itemError {
return fmt.Errorf("error lexing input: %s", p.cur.val)
}
var got string
switch p.cur.typ {
case itemTypeName, itemVariable, itemString:
got = p.cur.val
default:
got = "'" + p.cur.typ.String() + "'"
}
pos := p.f.Position(token.Pos(p.cur.pos))
return fmt.Errorf("%s: expected %s, found %s", pos, valid, got)
}
func (p *Parser) node() (Node, error) {
if _, ok := p.accept(itemLeftParen); !ok {
return nil, p.unexpectedToken("'('")
}
typ, ok := p.accept(itemTypeName)
if !ok {
return nil, p.unexpectedToken("Node type")
}
var objs []Node
for {
if _, ok := p.accept(itemRightParen); ok {
break
} else {
p.rewind()
obj, err := p.object()
if err != nil {
return nil, err
}
objs = append(objs, obj)
}
}
node, err := p.populateNode(typ.val, objs)
if err != nil {
return nil, err
}
if node, ok := node.(Binding); ok {
node.idx = p.bindingIndex(node.Name)
}
return node, nil
}
func populateNode(typ string, objs []Node, allowTypeInfo bool) (Node, error) {
T, ok := structNodes[typ]
if !ok {
return nil, fmt.Errorf("unknown node %s", typ)
}
if !allowTypeInfo && requiresTypeInfo[typ] {
return nil, fmt.Errorf("Node %s requires type information", typ)
}
pv := reflect.New(T)
v := pv.Elem()
if v.NumField() == 1 {
f := v.Field(0)
if f.Type().Kind() == reflect.Slice {
// Variadic node
f.Set(reflect.AppendSlice(f, reflect.ValueOf(objs)))
return v.Interface().(Node), nil
}
}
n := -1
for i := 0; i < T.NumField(); i++ {
if !T.Field(i).IsExported() {
break
}
n = i
}
if len(objs) != n+1 {
return nil, fmt.Errorf("tried to initialize node %s with %d values, expected %d", typ, len(objs), n+1)
}
for i := 0; i < v.NumField(); i++ {
if !T.Field(i).IsExported() {
break
}
f := v.Field(i)
if f.Kind() == reflect.String {
if obj, ok := objs[i].(String); ok {
f.Set(reflect.ValueOf(string(obj)))
} else {
return nil, fmt.Errorf("first argument of (Binding name node) must be string, but got %s", objs[i])
}
} else {
f.Set(reflect.ValueOf(objs[i]))
}
}
return v.Interface().(Node), nil
}
func (p *Parser) populateNode(typ string, objs []Node) (Node, error) {
return populateNode(typ, objs, p.AllowTypeInfo)
}
var structNodes = map[string]reflect.Type{
"Any": reflect.TypeFor[Any](),
"Ellipsis": reflect.TypeFor[Ellipsis](),
"List": reflect.TypeFor[List](),
"Binding": reflect.TypeFor[Binding](),
"RangeStmt": reflect.TypeFor[RangeStmt](),
"AssignStmt": reflect.TypeFor[AssignStmt](),
"IndexExpr": reflect.TypeFor[IndexExpr](),
"Ident": reflect.TypeFor[Ident](),
"Builtin": reflect.TypeFor[Builtin](),
"ValueSpec": reflect.TypeFor[ValueSpec](),
"GenDecl": reflect.TypeFor[GenDecl](),
"BinaryExpr": reflect.TypeFor[BinaryExpr](),
"ForStmt": reflect.TypeFor[ForStmt](),
"ArrayType": reflect.TypeFor[ArrayType](),
"DeferStmt": reflect.TypeFor[DeferStmt](),
"MapType": reflect.TypeFor[MapType](),
"ReturnStmt": reflect.TypeFor[ReturnStmt](),
"SliceExpr": reflect.TypeFor[SliceExpr](),
"StarExpr": reflect.TypeFor[StarExpr](),
"UnaryExpr": reflect.TypeFor[UnaryExpr](),
"SendStmt": reflect.TypeFor[SendStmt](),
"SelectStmt": reflect.TypeFor[SelectStmt](),
"ImportSpec": reflect.TypeFor[ImportSpec](),
"IfStmt": reflect.TypeFor[IfStmt](),
"GoStmt": reflect.TypeFor[GoStmt](),
"Field": reflect.TypeFor[Field](),
"SelectorExpr": reflect.TypeFor[SelectorExpr](),
"StructType": reflect.TypeFor[StructType](),
"KeyValueExpr": reflect.TypeFor[KeyValueExpr](),
"FuncType": reflect.TypeFor[FuncType](),
"FuncLit": reflect.TypeFor[FuncLit](),
"FuncDecl": reflect.TypeFor[FuncDecl](),
"ChanType": reflect.TypeFor[ChanType](),
"CallExpr": reflect.TypeFor[CallExpr](),
"CaseClause": reflect.TypeFor[CaseClause](),
"CommClause": reflect.TypeFor[CommClause](),
"CompositeLit": reflect.TypeFor[CompositeLit](),
"EmptyStmt": reflect.TypeFor[EmptyStmt](),
"SwitchStmt": reflect.TypeFor[SwitchStmt](),
"TypeSwitchStmt": reflect.TypeFor[TypeSwitchStmt](),
"TypeAssertExpr": reflect.TypeFor[TypeAssertExpr](),
"TypeSpec": reflect.TypeFor[TypeSpec](),
"InterfaceType": reflect.TypeFor[InterfaceType](),
"BranchStmt": reflect.TypeFor[BranchStmt](),
"IncDecStmt": reflect.TypeFor[IncDecStmt](),
"BasicLit": reflect.TypeFor[BasicLit](),
"Object": reflect.TypeFor[Object](),
"Symbol": reflect.TypeFor[Symbol](),
"Or": reflect.TypeFor[Or](),
"Not": reflect.TypeFor[Not](),
"IntegerLiteral": reflect.TypeFor[IntegerLiteral](),
"TrulyConstantExpression": reflect.TypeFor[TrulyConstantExpression](),
}
func (p *Parser) object() (Node, error) {
n := p.next()
switch n.typ {
case itemLeftParen:
p.rewind()
node, err := p.node()
if err != nil {
return node, err
}
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return node, err
}
return List{Head: node, Tail: tail}, nil
}
return node, nil
case itemLeftBracket:
p.rewind()
return p.array()
case itemVariable:
v := n
if v.val == "nil" {
return Nil{}, nil
}
var b Binding
if _, ok := p.accept(itemAt); ok {
o, err := p.node()
if err != nil {
return nil, err
}
b = Binding{
Name: v.val,
Node: o,
idx: p.bindingIndex(v.val),
}
} else {
p.rewind()
b = Binding{
Name: v.val,
idx: p.bindingIndex(v.val),
}
}
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return b, err
}
return List{Head: b, Tail: tail}, nil
}
return b, nil
case itemBlank:
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return Any{}, err
}
return List{Head: Any{}, Tail: tail}, nil
}
return Any{}, nil
case itemString:
return String(n.val), nil
default:
return nil, p.unexpectedToken("object")
}
}
func (p *Parser) array() (Node, error) {
if _, ok := p.accept(itemLeftBracket); !ok {
return nil, p.unexpectedToken("'['")
}
var objs []Node
for {
if _, ok := p.accept(itemRightBracket); ok {
break
} else {
p.rewind()
obj, err := p.object()
if err != nil {
return nil, err
}
objs = append(objs, obj)
}
}
tail := List{}
for i := len(objs) - 1; i >= 0; i-- {
l := List{
Head: objs[i],
Tail: tail,
}
tail = l
}
return tail, nil
}
/*
Node ::= itemLeftParen itemTypeName Object* itemRightParen
Object ::= Node | Array | Binding | itemVariable | itemBlank | itemString
Array := itemLeftBracket Object* itemRightBracket
Array := Object itemColon Object
Binding ::= itemVariable itemAt Node
*/
================================================
FILE: pattern/parser_test.go
================================================
package pattern
import (
"fmt"
"go/ast"
goparser "go/parser"
"go/token"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"honnef.co/go/tools/debug"
)
func TestParse(t *testing.T) {
inputs := []string{
`(Binding "name" _)`,
`(Binding "name" _:[])`,
`(Binding "name" _:_:[])`,
}
p := Parser{}
for _, input := range inputs {
if _, err := p.Parse(input); err != nil {
t.Errorf("failed to parse %q: %s", input, err)
}
}
}
func FuzzParse(f *testing.F) {
var files []*ast.File
fset := token.NewFileSet()
// Ideally we'd check against as much source code as possible, but that's fairly slow, on the order of 500ms per
// pattern when checking against the whole standard library.
//
// We pick the runtime package in the hopes that it contains the most diverse, and weird, code.
//
//lint:ignore SA1019 runtime.GOROOT is deprecated, we'll have to update this eventually
filepath.Walk(runtime.GOROOT()+"/src/runtime", func(path string, info os.FileInfo, err error) error {
if err != nil {
// XXX error handling
panic(err)
}
if !strings.HasSuffix(path, ".go") {
return nil
}
f, err := goparser.ParseFile(fset, path, nil, goparser.SkipObjectResolution)
if err != nil {
return nil
}
files = append(files, f)
return nil
})
parse := func(in string, allowTypeInfo bool) (Pattern, bool) {
p := Parser{
AllowTypeInfo: allowTypeInfo,
}
pat, err := p.Parse(string(in))
if err != nil {
if strings.Contains(err.Error(), "internal error") {
panic(err)
}
return Pattern{}, false
}
return pat, true
}
f.Fuzz(func(t *testing.T, in []byte) {
defer func() {
if err := recover(); err != nil {
str := fmt.Sprint(err)
if strings.Contains(str, "binding already created:") {
// This is an invalid pattern, not a real failure
} else {
// Re-panic the original panic
panic(err)
}
}
}()
// Parse twice, once with AllowTypeInfo set to true to exercise the parser, and once with it set to false so we
// can actually use it in Match, as we don't have type information available.
pat, ok := parse(string(in), true)
if !ok {
return
}
// Make sure we can turn it back into a string
_ = pat.Root.String()
pat, ok = parse(string(in), false)
if !ok {
return
}
// Make sure we can turn it back into a string
_ = pat.Root.String()
entryNodesMap := make(map[reflect.Type]struct{})
for _, node := range pat.EntryNodes {
entryNodesMap[reflect.TypeOf(node)] = struct{}{}
}
// Don't check patterns with too many relevant nodes; it's too expensive
if len(pat.EntryNodes) < 20 {
// Make sure trying to match nodes doesn't panic
for _, f := range files {
ast.Inspect(f, func(node ast.Node) bool {
rt := reflect.TypeOf(node)
// We'd prefer calling Match on all nodes, not just those the pattern deems relevant, to find more bugs.
// However, doing so has a 10x cost in execution time.
if _, ok := entryNodesMap[rt]; ok {
Match(pat, node)
}
return true
})
}
}
})
}
func TestMatchAlias(t *testing.T) {
p1 := MustParse(`(CallExpr (Symbol "foo.Alias") _)`)
p2 := MustParse(`(CallExpr (Symbol "int") _)`)
f, _, info, err := debug.TypeCheck(`
package pkg
type Alias = int
func _() { _ = Alias(0) }
`)
if err != nil {
t.Fatal(err)
}
m := &Matcher{
TypesInfo: info,
}
node := f.Decls[1].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt).Rhs[0]
if debug.AliasesEnabled() {
// Check that we can match on the name of the alias
if ok := m.Match(p1, node); !ok {
t.Errorf("%s did not match", p1.Root)
}
}
// Check that we can match on the name of the alias's target
if ok := m.Match(p2, node); !ok {
t.Errorf("%s did not match", p2.Root)
}
}
func TestCollectSymbols(t *testing.T) {
for _, tt := range []struct {
in string
out string
}{
{
`(Or (Symbol "foo") (Symbol "bar"))`,
`(Or (IndexSymbol "" "" "foo") (IndexSymbol "" "" "bar"))`,
},
{
`(CallExpr (Symbol "foo") [(Symbol "bar") (Symbol "baz")])`,
`(And (IndexSymbol "" "" "foo") (IndexSymbol "" "" "bar") (IndexSymbol "" "" "baz"))`,
},
{
`(Symbol (Or "foo" "bar"))`,
`(Or (IndexSymbol "" "" "foo") (IndexSymbol "" "" "bar"))`,
},
{
// (Or) never matches anything, so we do need the "foo" symbol for a
// successful match
`(Or (Symbol "foo") (Or))`,
`(IndexSymbol "" "" "foo")`,
},
{
// This tests (And ...)
`(BasicLit (Symbol "foo") (Ident "bar"))`,
`(IndexSymbol "" "" "foo")`,
},
{
`(Or (Symbol "foo") (Ident _))`,
`_`,
},
{
`(Or (Symbol "foo") (EmptyStmt))`,
`_`,
},
{
`(Or (Symbol "foo") nil)`,
`_`,
},
{
`(Symbol "example.com/foo.Get")`,
`(IndexSymbol "example.com/foo" "" "Get")`,
},
{
`(Symbol "(*example.com/foo.Client).Get")`,
`(IndexSymbol "example.com/foo" "Client" "Get")`,
},
// Don't crash on malformed symbols
{
`(Symbol "")`,
`(IndexSymbol "" "" "")`,
},
{
`(Symbol "foo.")`,
`(IndexSymbol "foo" "" "")`,
},
{
`(Symbol "(foo")`,
`(IndexSymbol "" "" "")`,
},
{
`(Symbol "(foo)")`,
`(IndexSymbol "" "" "")`,
},
{
`(Symbol "(foo.Bar)")`,
`(IndexSymbol "" "" "")`,
},
{
`(Symbol "(foo.Bar).")`,
`(IndexSymbol "foo" "Bar" "")`,
},
{
`(Symbol "(foo.Bar.")`,
`(IndexSymbol "" "" "")`,
},
{
`(Symbol "(foo).Bar")`,
`(IndexSymbol "" "" "")`,
},
} {
p := &Parser{AllowTypeInfo: true}
pat, err := p.Parse(tt.in)
if err != nil {
t.Fatal(err)
}
s := pat.SymbolsPattern.String()
if s != tt.out {
t.Fatalf("Symbol requirements for %s: got %s, want %s", tt.in, s, tt.out)
}
}
}
func BenchmarkParser(b *testing.B) {
const input = `
(CallExpr
(Symbol
name@(Or
"math/rand.Int31n"
"math/rand.Int63n"
"math/rand.Intn"
"(*math/rand.Rand).Int31n"
"(*math/rand.Rand).Int63n"
"(*math/rand.Rand).Intn"
"math/rand/v2.Int32N"
"math/rand/v2.Int64N"
"math/rand/v2.IntN"
"math/rand/v2.N"
"math/rand/v2.Uint32N"
"math/rand/v2.Uint64N"
"math/rand/v2.UintN"
"(*math/rand/v2.Rand).Int32N"
"(*math/rand/v2.Rand).Int64N"
"(*math/rand/v2.Rand).IntN"
"(*math/rand/v2.Rand).Uint32N"
"(*math/rand/v2.Rand).Uint64N"
"(*math/rand/v2.Rand).UintN"))
[(IntegerLiteral "1")])`
for range b.N {
p := &Parser{AllowTypeInfo: true}
_, err := p.Parse(input)
if err != nil {
b.Fatal(err)
}
}
}
================================================
FILE: pattern/pattern.go
================================================
package pattern
import (
"fmt"
"go/token"
"reflect"
"strings"
)
var (
_ Node = Ellipsis{}
_ Node = Binding{}
_ Node = RangeStmt{}
_ Node = AssignStmt{}
_ Node = IndexExpr{}
_ Node = IndexListExpr{}
_ Node = Ident{}
_ Node = Builtin{}
_ Node = String("")
_ Node = Any{}
_ Node = ValueSpec{}
_ Node = List{}
_ Node = GenDecl{}
_ Node = BinaryExpr{}
_ Node = ForStmt{}
_ Node = ArrayType{}
_ Node = DeferStmt{}
_ Node = MapType{}
_ Node = ReturnStmt{}
_ Node = SliceExpr{}
_ Node = StarExpr{}
_ Node = UnaryExpr{}
_ Node = SendStmt{}
_ Node = SelectStmt{}
_ Node = ImportSpec{}
_ Node = IfStmt{}
_ Node = GoStmt{}
_ Node = Field{}
_ Node = SelectorExpr{}
_ Node = StructType{}
_ Node = KeyValueExpr{}
_ Node = FuncType{}
_ Node = FuncLit{}
_ Node = FuncDecl{}
_ Node = Token(0)
_ Node = ChanType{}
_ Node = CallExpr{}
_ Node = CaseClause{}
_ Node = CommClause{}
_ Node = CompositeLit{}
_ Node = EmptyStmt{}
_ Node = SwitchStmt{}
_ Node = TypeSwitchStmt{}
_ Node = TypeAssertExpr{}
_ Node = TypeSpec{}
_ Node = InterfaceType{}
_ Node = BranchStmt{}
_ Node = IncDecStmt{}
_ Node = BasicLit{}
_ Node = Nil{}
_ Node = Object{}
_ Node = Symbol{}
_ Node = Not{}
_ Node = Or{}
_ Node = IntegerLiteral{}
_ Node = TrulyConstantExpression{}
)
type Symbol struct {
Name Node
}
type Token token.Token
type Nil struct {
}
type Ellipsis struct {
Elt Node
}
type IncDecStmt struct {
X Node
Tok Node
}
type BranchStmt struct {
Tok Node
Label Node
}
type InterfaceType struct {
Methods Node
}
type TypeSpec struct {
Name Node
Type Node
}
type TypeAssertExpr struct {
X Node
Type Node
}
type TypeSwitchStmt struct {
Init Node
Assign Node
Body Node
}
type SwitchStmt struct {
Init Node
Tag Node
Body Node
}
type EmptyStmt struct {
}
type CompositeLit struct {
Type Node
Elts Node
}
type CommClause struct {
Comm Node
Body Node
}
type CaseClause struct {
List Node
Body Node
}
type CallExpr struct {
Fun Node
Args Node
// XXX handle ellipsis
}
// TODO(dh): add a ChanDir node, and a way of instantiating it.
type ChanType struct {
Dir Node
Value Node
}
type FuncDecl struct {
Recv Node
Name Node
Type Node
Body Node
}
type FuncLit struct {
Type Node
Body Node
}
type FuncType struct {
Params Node
Results Node
}
type KeyValueExpr struct {
Key Node
Value Node
}
type StructType struct {
Fields Node
}
type SelectorExpr struct {
X Node
Sel Node
}
type Field struct {
Names Node
Type Node
Tag Node
}
type GoStmt struct {
Call Node
}
type IfStmt struct {
Init Node
Cond Node
Body Node
Else Node
}
type ImportSpec struct {
Name Node
Path Node
}
type SelectStmt struct {
Body Node
}
type ArrayType struct {
Len Node
Elt Node
}
type DeferStmt struct {
Call Node
}
type MapType struct {
Key Node
Value Node
}
type ReturnStmt struct {
Results Node
}
type SliceExpr struct {
X Node
Low Node
High Node
Max Node
}
type StarExpr struct {
X Node
}
type UnaryExpr struct {
Op Node
X Node
}
type SendStmt struct {
Chan Node
Value Node
}
type Binding struct {
Name string
Node Node
idx int
}
type RangeStmt struct {
Key Node
Value Node
Tok Node
X Node
Body Node
}
type AssignStmt struct {
Lhs Node
Tok Node
Rhs Node
}
type IndexExpr struct {
X Node
Index Node
}
type IndexListExpr struct {
X Node
Indices Node
}
type Node interface {
String() string
isNode()
}
type Ident struct {
Name Node
}
type Object struct {
Name Node
}
type Builtin struct {
Name Node
}
type String string
type Any struct{}
type ValueSpec struct {
Names Node
Type Node
Values Node
}
type List struct {
Head Node
Tail Node
}
type GenDecl struct {
Tok Node
Specs Node
}
type BasicLit struct {
Kind Node
Value Node
}
// An IntegerLiteral is a constant expression made up of only integer basic literals and the "+" and "-" unary operators.
// That is, 0, -4, -+42 are all integer literals, but 1 + 2 is not.
type IntegerLiteral struct {
Value Node
}
type BinaryExpr struct {
X Node
Op Node
Y Node
}
type ForStmt struct {
Init Node
Cond Node
Post Node
Body Node
}
type Or struct {
Nodes []Node
}
type And struct {
Nodes []Node
}
type Not struct {
Node Node
}
// A TrulyConstantExpression is a constant expression that does not make use of any identifiers.
// It is constant even under varying build tags.
type TrulyConstantExpression struct {
Value Node
}
type IndexSymbol struct {
Path string
Type string
Ident string
}
func stringify(n Node) string {
v := reflect.ValueOf(n)
var parts []string
parts = append(parts, v.Type().Name())
for i := 0; i < v.NumField(); i++ {
parts = append(parts, fmt.Sprintf("%s", v.Field(i)))
}
return "(" + strings.Join(parts, " ") + ")"
}
func (stmt AssignStmt) String() string { return stringify(stmt) }
func (expr IndexExpr) String() string { return stringify(expr) }
func (expr IndexListExpr) String() string { return stringify(expr) }
func (id Ident) String() string { return stringify(id) }
func (spec ValueSpec) String() string { return stringify(spec) }
func (decl GenDecl) String() string { return stringify(decl) }
func (lit BasicLit) String() string { return stringify(lit) }
func (expr BinaryExpr) String() string { return stringify(expr) }
func (stmt ForStmt) String() string { return stringify(stmt) }
func (stmt RangeStmt) String() string { return stringify(stmt) }
func (typ ArrayType) String() string { return stringify(typ) }
func (stmt DeferStmt) String() string { return stringify(stmt) }
func (typ MapType) String() string { return stringify(typ) }
func (stmt ReturnStmt) String() string { return stringify(stmt) }
func (expr SliceExpr) String() string { return stringify(expr) }
func (expr StarExpr) String() string { return stringify(expr) }
func (expr UnaryExpr) String() string { return stringify(expr) }
func (stmt SendStmt) String() string { return stringify(stmt) }
func (spec ImportSpec) String() string { return stringify(spec) }
func (stmt SelectStmt) String() string { return stringify(stmt) }
func (stmt IfStmt) String() string { return stringify(stmt) }
func (stmt IncDecStmt) String() string { return stringify(stmt) }
func (stmt GoStmt) String() string { return stringify(stmt) }
func (field Field) String() string { return stringify(field) }
func (expr SelectorExpr) String() string { return stringify(expr) }
func (typ StructType) String() string { return stringify(typ) }
func (expr KeyValueExpr) String() string { return stringify(expr) }
func (typ FuncType) String() string { return stringify(typ) }
func (lit FuncLit) String() string { return stringify(lit) }
func (decl FuncDecl) String() string { return stringify(decl) }
func (stmt BranchStmt) String() string { return stringify(stmt) }
func (expr CallExpr) String() string { return stringify(expr) }
func (clause CaseClause) String() string { return stringify(clause) }
func (typ ChanType) String() string { return stringify(typ) }
func (clause CommClause) String() string { return stringify(clause) }
func (lit CompositeLit) String() string { return stringify(lit) }
func (stmt EmptyStmt) String() string { return stringify(stmt) }
func (typ InterfaceType) String() string { return stringify(typ) }
func (stmt SwitchStmt) String() string { return stringify(stmt) }
func (expr TypeAssertExpr) String() string { return stringify(expr) }
func (spec TypeSpec) String() string { return stringify(spec) }
func (stmt TypeSwitchStmt) String() string { return stringify(stmt) }
func (nil Nil) String() string { return "nil" }
func (builtin Builtin) String() string { return stringify(builtin) }
func (obj Object) String() string { return stringify(obj) }
func (fn Symbol) String() string { return stringify(fn) }
func (el Ellipsis) String() string { return stringify(el) }
func (not Not) String() string { return stringify(not) }
func (lit IntegerLiteral) String() string { return stringify(lit) }
func (expr TrulyConstantExpression) String() string { return stringify(expr) }
func (sym IndexSymbol) String() string {
return fmt.Sprintf("(IndexSymbol %q %q %q)", sym.Path, sym.Type, sym.Ident)
}
func (or Or) String() string {
var s strings.Builder
s.WriteString("(Or")
for _, node := range or.Nodes {
s.WriteString(" ")
s.WriteString(node.String())
}
s.WriteString(")")
return s.String()
}
func (and And) String() string {
var s strings.Builder
s.WriteString("(And")
for _, node := range and.Nodes {
s.WriteString(" ")
s.WriteString(node.String())
}
s.WriteString(")")
return s.String()
}
func isProperList(l List) bool {
if l.Head == nil && l.Tail == nil {
return true
}
switch tail := l.Tail.(type) {
case nil:
return false
case List:
return isProperList(tail)
default:
return false
}
}
func (l List) String() string {
if l.Head == nil && l.Tail == nil {
return "[]"
}
if isProperList(l) {
// pretty-print the list
var objs []string
for l.Head != nil {
objs = append(objs, l.Head.String())
l = l.Tail.(List)
}
return fmt.Sprintf("[%s]", strings.Join(objs, " "))
}
return fmt.Sprintf("%s:%s", l.Head, l.Tail)
}
func (bind Binding) String() string {
if bind.Node == nil {
return bind.Name
}
return fmt.Sprintf("%s@%s", bind.Name, bind.Node)
}
func (s String) String() string { return fmt.Sprintf("%q", string(s)) }
func (tok Token) String() string {
return fmt.Sprintf("%q", strings.ToUpper(token.Token(tok).String()))
}
func (Any) String() string { return "_" }
func (AssignStmt) isNode() {}
func (IndexExpr) isNode() {}
func (IndexListExpr) isNode() {}
func (Ident) isNode() {}
func (ValueSpec) isNode() {}
func (GenDecl) isNode() {}
func (BasicLit) isNode() {}
func (BinaryExpr) isNode() {}
func (ForStmt) isNode() {}
func (RangeStmt) isNode() {}
func (ArrayType) isNode() {}
func (DeferStmt) isNode() {}
func (MapType) isNode() {}
func (ReturnStmt) isNode() {}
func (SliceExpr) isNode() {}
func (StarExpr) isNode() {}
func (UnaryExpr) isNode() {}
func (SendStmt) isNode() {}
func (ImportSpec) isNode() {}
func (SelectStmt) isNode() {}
func (IfStmt) isNode() {}
func (IncDecStmt) isNode() {}
func (GoStmt) isNode() {}
func (Field) isNode() {}
func (SelectorExpr) isNode() {}
func (StructType) isNode() {}
func (KeyValueExpr) isNode() {}
func (FuncType) isNode() {}
func (FuncLit) isNode() {}
func (FuncDecl) isNode() {}
func (BranchStmt) isNode() {}
func (CallExpr) isNode() {}
func (CaseClause) isNode() {}
func (ChanType) isNode() {}
func (CommClause) isNode() {}
func (CompositeLit) isNode() {}
func (EmptyStmt) isNode() {}
func (InterfaceType) isNode() {}
func (SwitchStmt) isNode() {}
func (TypeAssertExpr) isNode() {}
func (TypeSpec) isNode() {}
func (TypeSwitchStmt) isNode() {}
func (Nil) isNode() {}
func (Builtin) isNode() {}
func (Object) isNode() {}
func (Symbol) isNode() {}
func (Ellipsis) isNode() {}
func (Or) isNode() {}
func (And) isNode() {}
func (List) isNode() {}
func (String) isNode() {}
func (Token) isNode() {}
func (Any) isNode() {}
func (Binding) isNode() {}
func (Not) isNode() {}
func (IntegerLiteral) isNode() {}
func (TrulyConstantExpression) isNode() {}
func (IndexSymbol) isNode() {}
================================================
FILE: pattern/testdata/fuzz/FuzzParse/0001cdcefc5f03f99c21d4ef8232d8f0d8510d9c48e8105c927bc70ac02034a9
================================================
go test fuzz v1
[]byte("(AssignStmt ident \":=\" expr)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/00ec3673b415e2f6fc4a3f0d31413096921fbd1faa1cbabdd3637480af027a72
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr recv (Ident \"String\")) [])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/02f183192c9bcfbb22db5afa08e5a9a84babfca022726d0121f42c68d3feecee
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (Symbol \\\"math.Pow\\\") [x (IntegerLiteral n)])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/04fca5bfcc4a67c0d97de75fd6dc13a4a3e5c2dc68e5061f7bcb7e19852efe56
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t\\t(Or\\n\\t\\t\\t(CallExpr\\n\\t\\t\\t\\tfn@(Or\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Print\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Sprint\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Println\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Sprintln\\\"))\\n\\t\\t\\t\\t[(CallExpr (Symbol \\\"fmt.Sprintf\\\") f:_)])\\n\\t\\t\\t(CallExpr\\n\\t\\t\\t\\tfn@(Or\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Fprint\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Fprintln\\\"))\\n\\t\\t\\t\\t[_ (CallExpr (Symbol \\\"fmt.Sprintf\\\") f:_)]))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/05eea82b6791ec62e197e6128c608c67f5393ff98e94a9c1ba1311e763778749
================================================
go test fuzz v1
[]byte("(Or________________________________________________________________________________________________________________________________)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/06b3cbf8b7806ca08ce1ca466e83488ca32abb5db6b0ca4b07c54aa7be47adf3
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr lhs (Ident \"Equal\")) rhs)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/09c3a6a518c0e44fe60591523655ba4d7dcf62cb477f7e316a51e089adea74c2
================================================
go test fuzz v1
[]byte("(AssignStmt x tok@(Or \"+=\" \"-=\") (BasicLit \"INT\" \"1\"))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/0a21c29e926184ebb3c293c9cea3465ef5e1fc5c1b81be7d0770d5d69ee838a3
================================================
go test fuzz v1
[]byte("\n\t\t(ForStmt\n\t\t\tnil nil nil\n\t\t\tselect@(SelectStmt\n\t\t\t\t(CommClause\n\t\t\t\t\t(Or\n\t\t\t\t\t\t(UnaryExpr \"<-\" _)\n\t\t\t\t\t\t(AssignStmt _ _ (UnaryExpr \"<-\" _)))\n\t\t\t\t\t_)))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/0ce7ffb3713ec9373531b2903b8f8751e280cdae2b625dcf35dc1fcd88c592bf
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(CallExpr\\n\\t\\tfn@(Or\\n\\t\\t\\t(Symbol \\\"fmt.Sprint\\\")\\n\\t\\t\\t(Symbol \\\"fmt.Sprintf\\\"))\\n\\t\\t[lit@(BasicLit \\\"STRING\\\" _)])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/170704499ec0c05bf39fb37f6c5604e13624c4fb531e41305b2439308e370f35
================================================
go test fuzz v1
[]byte("(CallExpr fun@(SelectorExpr _ (Ident \"Seek\")) [arg1@(SelectorExpr (Ident \"io\") (Ident (Or \"SeekStart\" \"SeekCurrent\" \"SeekEnd\"))) arg2])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/1a3c741fba42577fac3c5035a3d44e5a78bcefa11f9ccc3bb2919376d984e4a2
================================================
go test fuzz v1
[]byte("(BinaryExpr left@(TrulyConstantExpression _) tok@(Or \"==\" \"!=\") right@(Not (TrulyConstantExpression _)))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/1eb6c2e8b8e0be47a019f0345b68ebfdba5f05804204e810166d1fe7c12e8556
================================================
go test fuzz v1
[]byte("\n\t(IfStmt\n\t\tnil\n\t\t(BinaryExpr x@(Object _) \"!=\" (Builtin \"nil\"))\n\t\t[(RangeStmt _ _ _ x _)]\n\t\tnil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/27e5f99d63fed488c4e9c3ac4a1e364f809ad894cb109aacc9bd6a85c015fdb7
================================================
go test fuzz v1
[]byte("(IfStmt nil cond [(ReturnStmt [ret@(Builtin (Or \"true\" \"false\"))])] nil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/2bac99d4a450641e3ae239588965c64323b1ee9eb2351cc53019d430d3a59efa
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(CallExpr\\n\\t\\t(SelectorExpr recv (Ident \\\"Write\\\"))\\n\\t\\t(CallExpr (ArrayType nil (Ident \\\"byte\\\"))\\n\\t\\t\\t(CallExpr\\n\\t\\t\\t\\tfn@(Or\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Sprint\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Sprintf\\\")\\n\\t\\t\\t\\t\\t(Symbol \\\"fmt.Sprintln\\\"))\\n\\t\\t\\t\\targs)\\n\\t))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/2c72a4a6b571446d5374dc5174fa44767bdcc8197e38c54738e50f8b58903230
================================================
go test fuzz v1
[]byte("(CallExpr(Ident(SelectorExpr(Ident\"\")(Ident\"\")))[])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/2f1cdb43e9c62bdb5f8777bc2cb4eee3e8fe173c4361f54833c48d06833ec8fe
================================================
go test fuzz v1
[]byte("\n\t\t(IfStmt\n\t\t\tnil\n\t\t\t(BinaryExpr lhs@(Object _) \"!=\" (Builtin \"nil\"))\n\t\t\t[\n\t\t\t\tifstmt@(IfStmt\n\t\t\t\t\t(AssignStmt [(Ident \"_\") ok@(Object _)] _ [(TypeAssertExpr lhs _)])\n\t\t\t\t\tok\n\t\t\t\t\t_\n\t\t\t\t\tnil)\n\t\t\t]\n\t\t\tnil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/312c49b9d41ad52e7beaa65ab01f5416e4f4d1db78b4e0001260ac888256b609
================================================
go test fuzz v1
[]byte("(CallExpr (Ident \"string\") [arg])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3148b044a5e00e508bfd9ac4d139e032503a590c36bd458a8291b77502d13561
================================================
go test fuzz v1
[]byte("(AssignStmt x@(Object _) \":=\" assign@(Builtin b@(Or \"true\" \"false\")))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/31ac2ece486bde345a4ac42fb989efa8835e72e82e357d5d82a313d6ba03eca2
================================================
go test fuzz v1
[]byte("(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt \"BREAK\" nil) nil):_)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/359bf5d248c22a3fc8d67de10279802663a767d4bf2d11dad3209bee13953ee0
================================================
go test fuzz v1
[]byte("(CallExpr (Builtin \"make\") [typ size size])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3895395d667f576d7f3891a63e4cc0157b2ec73dbe55745c1cba65f31e8cc5db
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (CallExpr (SelectorExpr recv (Ident \"Query\")) []) (Ident meth)) _)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3a3ef35129ccc131fc582363751397ad5723fb8ae891c31eaa5ad86ba402a27e
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (Symbol \\\"fmt.Sprintf\\\") [format arg])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3d78313ab191ebe8647428cd6d896208cb6dcfdd19eb87ae388315548176445a
================================================
go test fuzz v1
[]byte("(UnaryExpr \"!\" expr@(BinaryExpr _ _ _))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3e0e018aca3103af7824d729c88c028b8e0d60d3de223c786f46acac3e910cdb
================================================
go test fuzz v1
[]byte("\n\t(IfStmt\n\t\t(AssignStmt\n\t\t\t[(Ident \"_\") ok@(Ident _)]\n\t\t\t\":=\"\n\t\t\t(IndexExpr m key))\n\t\tok\n\t\t[call@(CallExpr (Builtin \"delete\") [m key])]\n\t\tnil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/3f66b015db9a62175f277eab5f76a62397c681b7e4ed6564f452e6159d4cb454
================================================
go test fuzz v1
[]byte("\n\t\t(AssignStmt\n\t\t\t(Ident \"_\") _ recv@(UnaryExpr \"<-\" _))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/4115b01752356dd12fd6499da369daec6031f62d315aecc4afad56c97f61b904
================================================
go test fuzz v1
[]byte("(BinaryExpr duration \"*\" (SelectorExpr (Ident \"time\") (Ident \"Second\")))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/465963a68302ca54f21c75fc3f680d6a5e1065682fb05a1350ed105883436a82
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"bytes\") (Ident \"Equal\")) args)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/47857edd56b46ac9c16e788e9295d1dafb910c345899aafd618ddaa12793f4f9
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (Symbol \\\"errors.New\\\") [(CallExpr (Symbol \\\"fmt.Sprintf\\\") args)])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/4dec90e6083b5e195501df63d8e1ed6813b623bef60ad8d9e0a1df1f251a58f3
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(CallExpr\\n\\t\\t(Symbol\\n\\t\\t\\tname@(Or\\n\\t\\t\\t\\t\\\"math/rand.Int31n\\\"\\n\\t\\t\\t\\t\\\"math/rand.Int63n\\\"\\n\\t\\t\\t\\t\\\"math/rand.Intn\\\"\\n\\t\\t\\t\\t\\\"(*math/rand.Rand).Int31n\\\"\\n\\t\\t\\t\\t\\\"(*math/rand.Rand).Int63n\\\"\\n\\t\\t\\t\\t\\\"(*math/rand.Rand).Intn\\\"))\\n\\t\\t[(IntegerLiteral \\\"1\\\")])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/51390f40de42348adb99c613cd8367db404851ce3ea1a4e02ea316b5b7e915b7
================================================
go test fuzz v1
[]byte("\n\t\t(ForStmt\n\t\t\t(AssignStmt initvar@(Ident _) _ (IntegerLiteral \"0\"))\n\t\t\t(BinaryExpr initvar \"<\" limit@(Ident _))\n\t\t\t(IncDecStmt initvar \"++\")\n\t\t\t[(AssignStmt\n\t\t\t\t(IndexExpr slice@(Ident _) initvar)\n\t\t\t\t\"=\"\n\t\t\t\t(IndexExpr slice (BinaryExpr offset@(Ident _) \"+\" initvar)))])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/53da8fdd88cd66de33bbdbbf564e2b14b69d02f32102d8a96171ed4b05dbc92e
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t\\t(CallExpr\\n\\t\\t\\t(Symbol\\n\\t\\t\\t\\t(Or\\n\\t\\t\\t\\t\\t\\\"log.Fatal\\\"\\n\\t\\t\\t\\t\\t\\\"log.Fatalln\\\"\\n\\t\\t\\t\\t\\t\\\"log.Panic\\\"\\n\\t\\t\\t\\t\\t\\\"log.Panicln\\\"\\n\\t\\t\\t\\t\\t\\\"log.Print\\\"\\n\\t\\t\\t\\t\\t\\\"log.Println\\\"))\\n\\t\\t\\t[(CallExpr (Symbol \\\"fmt.Sprintf\\\") args)])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/56a234ae7b32577f770d5b997f037de709344d7be6fd9ca6e1f44fc8c4367f5b
================================================
go test fuzz v1
[]byte("(ForStmt a a a(IfStmt a b(BranchStmt nil a)a):a)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/5baab7b6c2c18988c27aefc55f5d48c6ac583f790fb6763cda34375f9b07da40
================================================
go test fuzz v1
[]byte("\n\t(Or\n\t\t(IfStmt\n\t\t\t(AssignStmt [(Ident \"_\") ok@(Ident _)] \":=\" indexexpr@(IndexExpr _ _))\n\t\t\tok\n\t\t\tset@(AssignStmt indexexpr \"=\" (CallExpr (Builtin \"append\") indexexpr:values))\n\t\t\t(AssignStmt indexexpr \"=\" (CompositeLit _ values)))\n\t\t(IfStmt\n\t\t\t(AssignStmt [(Ident \"_\") ok] \":=\" indexexpr@(IndexExpr _ _))\n\t\t\tok\n\t\t\tset@(AssignStmt indexexpr \"+=\" value)\n\t\t\t(AssignStmt indexexpr \"=\" value))\n\t\t(IfStmt\n\t\t\t(AssignStmt [(Ident \"_\") ok] \":=\" indexexpr@(IndexExpr _ _))\n\t\t\tok\n\t\t\tset@(IncDecStmt indexexpr \"++\")\n\t\t\t(AssignStmt indexexpr \"=\" (IntegerLiteral \"1\"))))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/5cc91809f9225a218b9cfb3a31d5baed3c5a44b5da3a74184fa97abe3bbf178f
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"time\") (Ident \"Since\")) [arg])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/5d9a745f26174c61e5ab0966e4821f75b71de80345be52d4b81aa1515158b735
================================================
go test fuzz v1
[]byte("(BinaryExpr\n\t\t(UnaryExpr \"&\" _)\n\t\t(Or \"==\" \"!=\")\n\t\t(Builtin \"nil\"))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/5e8383a425cf9bc34f43d60f7586184ae7a544e3ad10405ef7aca57246c2ab66
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(BinaryExpr\\n\\t\\t(CallExpr fun@(Symbol (Or \\\"strings.ToLower\\\" \\\"strings.ToUpper\\\")) [a])\\n \\t\\ttok@(Or \\\"==\\\" \\\"!=\\\")\\n \\t\\t(CallExpr fun [b]))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/60c36a14214281c0c2c31599563afec69016f469a0f25222a9500e307b159d11
================================================
go test fuzz v1
[]byte("(Or[a a])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/614ea1474d223cb45716d531aa8afac2dfd52938aeb38c64b70a351f0cf509b2
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (SelectorExpr (CallExpr (Symbol \\\"time.Now\\\") []) (Symbol \\\"(time.Time).Sub\\\")) [arg])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/6490b1471fef1f39
================================================
go test fuzz v1
[]byte("(Or_:)E)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/6a4d6ea339df8f59816483834329cc4310816de0223bd3607b2af6c91367a59b
================================================
go test fuzz v1
[]byte("\n\t\t(AssignStmt\n\t\t\t[_ (Ident \"_\")]\n\t\t\t_\n\t\t\t(Or\n\t\t\t\t(IndexExpr _ _)\n\t\t\t\t(UnaryExpr \"<-\" _))) ")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/6aa9975401e9a24c46284ea6ea1740740fc58950a021c56e1376c2e108ee3b90
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr recv (Ident \"Bytes\")) [])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/6ac1f5e27fbe6d979efae1abf9b2439a824b83f4b2a27508dbeb5dc95b4f9960
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(Or\\n\\t\\t(CallExpr\\n\\t\\t\\t(Symbol (Or\\n\\t\\t\\t\\t\\\"fmt.Print\\\"\\n\\t\\t\\t\\t\\\"fmt.Println\\\"\\n\\t\\t\\t\\t\\\"fmt.Sprint\\\"\\n\\t\\t\\t\\t\\\"fmt.Sprintln\\\"\\n\\t\\t\\t\\t\\\"log.Fatal\\\"\\n\\t\\t\\t\\t\\\"log.Fatalln\\\"\\n\\t\\t\\t\\t\\\"log.Panic\\\"\\n\\t\\t\\t\\t\\\"log.Panicln\\\"\\n\\t\\t\\t\\t\\\"log.Print\\\"\\n\\t\\t\\t\\t\\\"log.Println\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Fatal\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Fatalln\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Panic\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Panicln\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Print\\\"\\n\\t\\t\\t\\t\\\"(*log.Logger).Println\\\")) args)\\n\\n\\t\\t(CallExpr (Symbol (Or\\n\\t\\t\\t\\\"fmt.Fprint\\\"\\n\\t\\t\\t\\\"fmt.Fprintln\\\")) _:args))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/6fa1e1e283fd220866a9e5878510db574b761fbd5a0e863e66f40fd4acbbaf07
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(SelectStmt (CommClause (UnaryExpr \\\"<-\\\" (CallExpr (Symbol \\\"time.After\\\") [arg])) body))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/71e2fa0db72c309e630267beac45c90d37e4b8f9d2d2ed52100d1abca7b72965
================================================
go test fuzz v1
[]byte("\n\t(Or\n\t\t(UnaryExpr\n\t\t\t\"-\"\n\t\t\t(BasicLit \"FLOAT\" \"0.0\"))\n\n\t\t(UnaryExpr\n\t\t\t\"-\"\n\t\t\t(CallExpr conv@(Object (Or \"float32\" \"float64\")) lit@(Or (BasicLit \"INT\" \"0\") (BasicLit \"FLOAT\" \"0.0\"))))\n\n\t\t(CallExpr\n\t\t\tconv@(Object (Or \"float32\" \"float64\"))\n\t\t\t(UnaryExpr \"-\" lit@(BasicLit \"INT\" \"0\"))))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/76d91998f39bf2e25bd361453a73968274ffe16677cf02d872222d4c799552f8
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(Or\\n\\t(CallExpr fn@(Symbol \\\"strings.Replace\\\") [_ _ _ lit@(IntegerLiteral \\\"-1\\\")])\\n\\t(CallExpr fn@(Symbol \\\"strings.SplitN\\\") [_ _ lit@(IntegerLiteral \\\"-1\\\")])\\n\\t(CallExpr fn@(Symbol \\\"strings.SplitAfterN\\\") [_ _ lit@(IntegerLiteral \\\"-1\\\")])\\n\\t(CallExpr fn@(Symbol \\\"bytes.Replace\\\") [_ _ _ lit@(IntegerLiteral \\\"-1\\\")])\\n\\t(CallExpr fn@(Symbol \\\"bytes.SplitN\\\") [_ _ lit@(IntegerLiteral \\\"-1\\\")])\\n\\t(CallExpr fn@(Symbol \\\"bytes.SplitAfterN\\\") [_ _ lit@(IntegerLiteral \\\"-1\\\")]))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/78eaf491672242d08770ab22b67853f639c767f65346de39c6f3e677b1cd879d
================================================
go test fuzz v1
[]byte("(UnaryExpr \"&\" (StarExpr obj))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/7ba6359207886a1c2c7bbe254835555e87a037ecd3af0301a11a43ec2287c487
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr fun@(Symbol _) (Builtin \\\"nil\\\"):_)\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/7ec87621ab148929b69125a04edd13ff104007ca0d8dff12f281753ea93ffb80
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(BinaryExpr (CallExpr (Symbol \\\"bytes.Compare\\\") args) op@(Or \\\"==\\\" \\\"!=\\\") (IntegerLiteral \\\"0\\\"))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/8203d4ee0690ca0d0c4b907e1f1c8d6c1724c4771ec3a685b56b440f52b4282a
================================================
go test fuzz v1
[]byte("(BinaryExpr _ \"%\" (IntegerLiteral \"1\"))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/8324b925e52410ab88b6265538881346436b67d95ad808b8f9220a84b0772ab7
================================================
go test fuzz v1
[]byte("\n\t\t(IfStmt\n\t\t\t(AssignStmt [(Ident \"_\") ok@(Object _)] _ [(TypeAssertExpr assert@(Object _) _)])\n\t\t\t(Or\n\t\t\t\t(BinaryExpr ok \"&&\" (BinaryExpr assert \"!=\" (Builtin \"nil\")))\n\t\t\t\t(BinaryExpr (BinaryExpr assert \"!=\" (Builtin \"nil\")) \"&&\" ok))\n\t\t\t_\n\t\t\t_)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/84e67732ffe4ba2d8fdb8cfc8690804579623dbc9c56a378ca483f088348296a
================================================
go test fuzz v1
[]byte("(ReturnStmt [ret@(Builtin (Or \"true\" \"false\"))])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/87839b3497143dd5ea14963b78c011edceb40d13fe1d8cd9b894a81b5dae2200
================================================
go test fuzz v1
[]byte("(CallExpr (Builtin \"make\") [typ size@(IntegerLiteral \"0\")])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/87f42498d6f57dc40c9972487f0e35d9820acbbce6cf61f3b90dabaa9cb8a8fc
================================================
go test fuzz v1
[]byte("(AssignStmt[a(Ident\"_\")]a(Or(IndexExpr_a)(UnaryExpr\"\"a)))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/90a846c5b88ccf4fe765113a3580ecc90a5cf083a97f0bc4b3bb53a1f00e3fd8
================================================
go test fuzz v1
[]byte("(SliceExpr x@(Object _) low (CallExpr (Builtin \"len\") [x]) nil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/9437b751fb0f1f07f5dcb8c8a10d0f3d4470a77b7ec77df6be872a109184bd1b
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (Symbol \\\"time.Sleep\\\") lit@(IntegerLiteral value))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/94b7dc35d595dd794b4f65cd35f94ae8fe7c7214e6da8caa69f0b841e9a099af
================================================
go test fuzz v1
[]byte("(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/9d603847ed1c030c81f2289ee576971cd63564cc811afb5c18d5a51db7aefa76
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t\\t(CallExpr\\n\\t\\t\\tsel@(SelectorExpr\\n\\t\\t\\t\\trecv\\n\\t\\t\\t\\t(Ident\\n\\t\\t\\t\\t\\tname@(Or\\n\\t\\t\\t\\t\\t\\t\\\"Error\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Fatal\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Fatalln\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Log\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Panic\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Panicln\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Print\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Println\\\"\\n\\t\\t\\t\\t\\t\\t\\\"Skip\\\")))\\n\\t\\t\\t[(CallExpr (Symbol \\\"fmt.Sprintf\\\") args)])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/a3b8af4d027db37d44e58995ed2ab3cd9f2cb415669287e9e7ce7186534b4b1f
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"time\") (Ident \"Until\")) [arg])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/aa520290f4868dc3c01f15d2769941654a404b87327f5dde790c99fc2c63d875
================================================
go test fuzz v1
[]byte("(IfStmt (AssignStmt [obj@(Ident _) ok@(Ident _)] \":=\" assert@(TypeAssertExpr obj _)) ok _ elseBranch)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ac1b69c690b399207dd7fe32f03a12d2731fa2d1704f6b15cfdc7f772b0f3187
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(CallExpr\\n\\t\\t(SelectorExpr recv (Ident \\\"WriteString\\\"))\\n\\t\\t(CallExpr\\n\\t\\t\\tfn@(Or\\n\\t\\t\\t\\t(Symbol \\\"fmt.Sprint\\\")\\n\\t\\t\\t\\t(Symbol \\\"fmt.Sprintf\\\")\\n\\t\\t\\t\\t(Symbol \\\"fmt.Sprintln\\\"))\\n\\t\\t\\targs))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ad86b3632aca0a27fef3d6d79de5c2bcf1c21f7a6caa1260aab964edc21f3f65
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t(GoStmt\\n\\t\\t(CallExpr\\n\\t\\t\\t(FuncLit\\n\\t\\t\\t\\t_\\n\\t\\t\\t\\tcall@(CallExpr (Symbol \\\"(*sync.WaitGroup).Add\\\") _):_) _))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/af10598249def731ec19ebffa3cbc464892d0e445dbefab9ccf578eae136236a
================================================
go test fuzz v1
[]byte("(IntegerLiteral tv)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/afe5949d38d9171e39ad413d31abfab6bf45d066b700b4e84a232a6b3aa53085
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"\\n\\t\\t(Or\\n\\t\\t\\t(RangeStmt\\n\\t\\t\\t\\tkey@(Ident _) value@(Ident _) \\\":=\\\" src\\n\\t\\t\\t\\t[(AssignStmt (IndexExpr dst key) \\\"=\\\" value)])\\n\\t\\t\\t(RangeStmt\\n\\t\\t\\t\\tkey@(Ident _) nil \\\":=\\\" src\\n\\t\\t\\t\\t[(AssignStmt (IndexExpr dst key) \\\"=\\\" (IndexExpr src key))])\\n\\t\\t\\t(ForStmt\\n\\t\\t\\t\\t(AssignStmt key@(Ident _) \\\":=\\\" (IntegerLiteral \\\"0\\\"))\\n\\t\\t\\t\\t(BinaryExpr key \\\"<\\\" (CallExpr (Symbol \\\"len\\\") [src]))\\n\\t\\t\\t\\t(IncDecStmt key \\\"++\\\")\\n\\t\\t\\t\\t[(AssignStmt (IndexExpr dst key) \\\"=\\\" (IndexExpr src key))]))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/b44bceab2d84f09950aa80d8541c18e31a3d5dde6e874fd0bfe2e4ce54606db0
================================================
go test fuzz v1
[]byte("(AssignStmt a a@(Or\"0\"\"0\")(BasicLit\"000\"\"\"))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/b553c9e015253a9e3d4e202fdb2d90764151e24219f26f7510a433d30323666e
================================================
go test fuzz v1
[]byte("\n\t(Or\n\t\t(BinaryExpr\n\t\t\t(IntegerLiteral \"0\")\n\t\t\t\">\"\n\t\t\t(CallExpr builtin@(Builtin (Or \"len\" \"cap\")) _))\n\t\t(BinaryExpr\n\t\t\t(CallExpr builtin@(Builtin (Or \"len\" \"cap\")) _)\n\t\t\t\"<\"\n\t\t\t(IntegerLiteral \"0\")))\n")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/b6a22c4a4f5e0cf4a291f2d6f03860631075934e4069959665d1f8097c69d0d0
================================================
go test fuzz v1
[]byte("(UnaryExpr \"!\" (CallExpr (SelectorExpr (Ident \"bytes\") (Ident \"Equal\")) args))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/b95053e6ea7644faad4e0f2e5f308ca66d6a05c47bf36d0fde268fc12e09ca63
================================================
go test fuzz v1
[]byte("\n(Or\n\t(RangeStmt\n\t\t(Ident \"_\")\n\t\tval@(Object _)\n\t\t_\n\t\tx\n\t\t[(AssignStmt [lhs] \"=\" [(CallExpr (Builtin \"append\") [lhs val])])])\n\t(RangeStmt\n\t\tidx@(Ident _)\n\t\tnil\n\t\t_\n\t\tx\n\t\t[(AssignStmt [lhs] \"=\" [(CallExpr (Builtin \"append\") [lhs (IndexExpr x idx)])])])\n\t(RangeStmt\n\t\tidx@(Ident _)\n\t\tnil\n\t\t_\n\t\tx\n\t\t[(AssignStmt val@(Object _) \":=\" (IndexExpr x idx))\n\t\t(AssignStmt [lhs] \"=\" [(CallExpr (Builtin \"append\") [lhs val])])]))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ba95d1477ea1b35a949c6b469077d908b1cbcaf7fbf3ce9ef544bfeb24f877fb
================================================
go test fuzz v1
[]byte("(BinaryExpr duration \"*\" (SelectorExpr (Ident \"time\") (Ident \"Nanosecond\")))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/bb62ca358e19867f7d31400cb2a65aac1e918308212c43d10cca21feeb9c99d2
================================================
go test fuzz v1
[]byte("\n\t\t(TypeSwitchStmt\n\t\t\tnil\n\t\t\texpr@(TypeAssertExpr ident@(Ident _) _)\n\t\t\tbody)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/c1a2c8141751527604100e865db8d0e711ce25fc5c291b7702752496ac4b2546
================================================
go test fuzz v1
[]byte("(CallExpr fun [arg2 arg1])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/c30ca6d4801d71144c641960df6919115149d2b6fae5f7d9b2bac2b8cd6b8d25
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(AssignStmt target@(Ident _) \\\"=\\\" (CallExpr typ@(Symbol (Or \\\"sort.Float64Slice\\\" \\\"sort.IntSlice\\\" \\\"sort.StringSlice\\\")) [target]))\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/c5f48734d853b82016955671d916daaf72da20a5f8335dddf7640fab1f5a3acb
================================================
go test fuzz v1
[]byte("(BinaryExpr right tok left)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/c6d06d254dee12276b9b46ef9be863a1eefc4d0673946a706ec7a164625595f0
================================================
go test fuzz v1
[]byte("(SelectStmt (CommClause _ _))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/c7abb7fc60634bb8d57b5b7c225a6accf0d2eb56c88bfe5e44cdd3e0c3e29666
================================================
go test fuzz v1
[]byte("(IfStmt _ cond@(BinaryExpr lhs op@(Or \"==\" \"!=\") (Builtin \"nil\")) _ _)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ca92b01f6dbdcb91e335219081aa48c16893c217bf6edc020fcb78b3ebabcd1f
================================================
go test fuzz v1
[]byte("(BinaryExpr (IntegerLiteral _) \"/\" (IntegerLiteral _))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/e027a03ee012e289def51d770ead1e8a136b60989d3d1fb9388a394da2f595da
================================================
go test fuzz v1
[]byte("go test fuzz v1\n[]byte(\"(CallExpr (Symbol \\\"(time.Time).Sub\\\") [(CallExpr (Symbol \\\"time.Now\\\") [])])\")")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/e1e59b9e6718f5089e98c955c391d38c7e243495ece9598826492ab734e5171f
================================================
go test fuzz v1
[]byte("(CallExpr (Builtin \"append\") [_])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/e43aa6da655e6c326cfb1f8c9970b603411caf262af4a50980c5a5987ee696f3
================================================
go test fuzz v1
[]byte("(IfStmt nil cond [(AssignStmt x@(Object _) \"=\" (Builtin b@(Or \"true\" \"false\")))] nil)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/e48990bfca21324ab7a29098b9a4b40fbd22bd5adcfa316b4b8af460a232b638
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"strings\") (Ident \"EqualFold\")) [a b])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/eb0ecf0066fdafbe218a736d3fc071a52408311637cc527db239f110418e8616
================================================
go test fuzz v1
[]byte("(StarExpr (UnaryExpr \"&\" _))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ed6769b59df864327fba2b109f0cb965e5b8a6e5f1085e36f5635f1d65003a00
================================================
go test fuzz v1
[]byte("\n\t\t(CallExpr\n\t\t\t(Ident \"copy\")\n\t\t\t[(SliceExpr slice nil limit nil)\n\t\t\t\t(SliceExpr slice offset nil nil)])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/f640eee2b04d1b52793ba88998a86702893e23d2563d017be9be90efc04a43c6
================================================
go test fuzz v1
[]byte("(Or (BasicLit \"INT\" _) (UnaryExpr (Or \"+\" \"-\") (IntegerLiteral _)))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/f855d335a52bd8b6ed4472abb33c0eb8f67a63d84f1c27398c23689fb2720645
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"time\") (Ident \"Sleep\")) [arg])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/fac160433f2d82b3c15a8c6ad3938fd85825a4f248108538938a57914e80f114
================================================
go test fuzz v1
[]byte("(UnaryExpr \"!\" single@(UnaryExpr \"!\" x))")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/fb2c5ef5801f44e5bee94b82dbb1bc787cc4b7fbdb17e5cfcc4283f2c726a99f
================================================
go test fuzz v1
[]byte("(CallExpr (SelectorExpr (Ident \"fmt\") (Ident \"Errorf\")) args)")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/fe6c6578776a5ce92474e943ac14979a308d4151d779fd4cfd782f7fb970165e
================================================
go test fuzz v1
[]byte("(Or[a_a_a_a])")
================================================
FILE: pattern/testdata/fuzz/FuzzParse/ff2017b5c630d7225812cfa8b29b6ebad665505492db847722ba79da5d2c89eb
================================================
go test fuzz v1
[]byte("\n\t\t(Or\n\t\t\t(AssignStmt _ (Or \">>=\" \"<<=\") _)\n\t\t\t(BinaryExpr _ (Or \">>\" \"<<\") _))\n\t")
================================================
FILE: printf/fuzz.go
================================================
//go:build gofuzz
package printf
func Fuzz(data []byte) int {
_, err := Parse(string(data))
if err == nil {
return 1
}
return 0
}
================================================
FILE: printf/printf.go
================================================
// Package printf implements a parser for fmt.Printf-style format
// strings.
//
// It parses verbs according to the following syntax:
//
// Numeric -> '0'-'9'
// Letter -> 'a'-'z' | 'A'-'Z'
// Index -> '[' Numeric+ ']'
// Star -> '*'
// Star -> Index '*'
//
// Precision -> Numeric+ | Star
// Width -> Numeric+ | Star
//
// WidthAndPrecision -> Width '.' Precision
// WidthAndPrecision -> Width '.'
// WidthAndPrecision -> Width
// WidthAndPrecision -> '.' Precision
// WidthAndPrecision -> '.'
//
// Flag -> '+' | '-' | '#' | ' ' | '0'
// Verb -> Letter | '%'
//
// Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
package printf
import (
"errors"
"regexp"
"strconv"
"strings"
)
// ErrInvalid is returned for invalid format strings or verbs.
var ErrInvalid = errors.New("invalid format string")
type Verb struct {
Letter rune
Flags string
Width Argument
Precision Argument
// Which value in the argument list the verb uses.
// -1 denotes the next argument,
// values > 0 denote explicit arguments.
// The value 0 denotes that no argument is consumed. This is the case for %%.
Value int
Raw string
}
// Argument is an implicit or explicit width or precision.
type Argument interface {
isArgument()
}
// The Default value, when no width or precision is provided.
type Default struct{}
// Zero is the implicit zero value.
// This value may only appear for precisions in format strings like %6.f
type Zero struct{}
// Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
type Star struct{ Index int }
// A Literal value, such as 6 in %6d.
type Literal int
func (Default) isArgument() {}
func (Zero) isArgument() {}
func (Star) isArgument() {}
func (Literal) isArgument() {}
// Parse parses f and returns a list of actions.
// An action may either be a literal string, or a Verb.
func Parse(f string) ([]any, error) {
var out []any
for len(f) > 0 {
if f[0] == '%' {
v, n, err := ParseVerb(f)
if err != nil {
return nil, err
}
f = f[n:]
out = append(out, v)
} else {
n := strings.IndexByte(f, '%')
if n > -1 {
out = append(out, f[:n])
f = f[n:]
} else {
out = append(out, f)
f = ""
}
}
}
return out, nil
}
func atoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
// ParseVerb parses the verb at the beginning of f.
// It returns the verb, how much of the input was consumed, and an error, if any.
func ParseVerb(f string) (Verb, int, error) {
if len(f) < 2 {
return Verb{}, 0, ErrInvalid
}
const (
flags = 1
width = 2
widthStar = 3
widthIndex = 5
dot = 6
prec = 7
precStar = 8
precIndex = 10
verbIndex = 11
verb = 12
)
m := re.FindStringSubmatch(f)
if m == nil {
return Verb{}, 0, ErrInvalid
}
v := Verb{
Letter: []rune(m[verb])[0],
Flags: m[flags],
Raw: m[0],
}
if m[width] != "" {
// Literal width
v.Width = Literal(atoi(m[width]))
} else if m[widthStar] != "" {
// Star width
if m[widthIndex] != "" {
v.Width = Star{atoi(m[widthIndex])}
} else {
v.Width = Star{-1}
}
} else {
// Default width
v.Width = Default{}
}
if m[dot] == "" {
// default precision
v.Precision = Default{}
} else {
if m[prec] != "" {
// Literal precision
v.Precision = Literal(atoi(m[prec]))
} else if m[precStar] != "" {
// Star precision
if m[precIndex] != "" {
v.Precision = Star{atoi(m[precIndex])}
} else {
v.Precision = Star{-1}
}
} else {
// Zero precision
v.Precision = Zero{}
}
}
if m[verb] == "%" {
v.Value = 0
} else if m[verbIndex] != "" {
v.Value = atoi(m[verbIndex])
} else {
v.Value = -1
}
return v, len(m[0]), nil
}
const (
flags = `([+#0 -]*)`
verb = `([a-zA-Z%])`
index = `(?:\[([0-9]+)\])`
star = `((` + index + `)?\*)`
width1 = `([0-9]+)`
width2 = star
width = `(?:` + width1 + `|` + width2 + `)`
precision = width
widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
)
var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)
================================================
FILE: printf/printf_test.go
================================================
package printf
import "testing"
func BenchmarkParseVerb(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseVerb("%[3]*.[2]*[1]f")
}
}
func TestParseVerb(t *testing.T) {
var tests = []struct {
in string
out Verb
}{
{
`%d`,
Verb{
Letter: 'd',
Width: Default{},
Precision: Default{},
Value: -1,
},
},
{
`%#d`,
Verb{
Letter: 'd',
Flags: "#",
Width: Default{},
Precision: Default{},
Value: -1,
},
},
{
`%+#d`,
Verb{
Letter: 'd',
Flags: "+#",
Width: Default{},
Precision: Default{},
Value: -1,
},
},
{
`%[2]d`,
Verb{
Letter: 'd',
Width: Default{},
Precision: Default{},
Value: 2,
},
},
{
`%[3]*.[2]*[1]f`,
Verb{
Letter: 'f',
Width: Star{3},
Precision: Star{2},
Value: 1,
},
},
{
`%6.2f`,
Verb{
Letter: 'f',
Width: Literal(6),
Precision: Literal(2),
Value: -1,
},
},
{
`%#[1]x`,
Verb{
Letter: 'x',
Flags: "#",
Width: Default{},
Precision: Default{},
Value: 1,
},
},
{
"%%",
Verb{
Letter: '%',
Width: Default{},
Precision: Default{},
Value: 0,
},
},
{
"%*%",
Verb{
Letter: '%',
Width: Star{Index: -1},
Precision: Default{},
Value: 0,
},
},
{
"%[1]%",
Verb{
Letter: '%',
Width: Default{},
Precision: Default{},
Value: 0,
},
},
}
for _, tt := range tests {
tt.out.Raw = tt.in
v, n, err := ParseVerb(tt.in)
if err != nil {
t.Errorf("unexpected error %s while parsing %s", err, tt.in)
}
if n != len(tt.in) {
t.Errorf("ParseVerb only consumed %d of %d bytes", n, len(tt.in))
}
if v != tt.out {
t.Errorf("%s parsed to %#v, want %#v", tt.in, v, tt.out)
}
}
}
================================================
FILE: quickfix/analysis.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package quickfix
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/quickfix/qf1001"
"honnef.co/go/tools/quickfix/qf1002"
"honnef.co/go/tools/quickfix/qf1003"
"honnef.co/go/tools/quickfix/qf1004"
"honnef.co/go/tools/quickfix/qf1005"
"honnef.co/go/tools/quickfix/qf1006"
"honnef.co/go/tools/quickfix/qf1007"
"honnef.co/go/tools/quickfix/qf1008"
"honnef.co/go/tools/quickfix/qf1009"
"honnef.co/go/tools/quickfix/qf1010"
"honnef.co/go/tools/quickfix/qf1011"
"honnef.co/go/tools/quickfix/qf1012"
)
var Analyzers = []*lint.Analyzer{
qf1001.SCAnalyzer,
qf1002.SCAnalyzer,
qf1003.SCAnalyzer,
qf1004.SCAnalyzer,
qf1005.SCAnalyzer,
qf1006.SCAnalyzer,
qf1007.SCAnalyzer,
qf1008.SCAnalyzer,
qf1009.SCAnalyzer,
qf1010.SCAnalyzer,
qf1011.SCAnalyzer,
qf1012.SCAnalyzer,
}
================================================
FILE: quickfix/doc.go
================================================
//go:generate go run ../generate.go
// Package quickfix contains analyzes that implement code refactorings.
// None of these analyzers produce diagnostics that have to be followed.
// Most of the time, they only provide alternative ways of doing things,
// requiring users to make informed decisions.
//
// None of these analyzes should fail a build, and they are likely useless in CI as a whole.
package quickfix
================================================
FILE: quickfix/qf1001/qf1001.go
================================================
package qf1001
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1001",
Run: CheckDeMorgan,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Apply De Morgan's law",
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var demorganQ = pattern.MustParse(`(UnaryExpr "!" expr@(BinaryExpr _ _ _))`)
func CheckDeMorgan(pass *analysis.Pass) (any, error) {
// TODO(dh): support going in the other direction, e.g. turning `!a && !b && !c` into `!(a || b || c)`
// hasFloats reports whether any subexpression is of type float.
hasFloats := func(expr ast.Expr) bool {
found := false
ast.Inspect(expr, func(node ast.Node) bool {
if expr, ok := node.(ast.Expr); ok {
if typ := pass.TypesInfo.TypeOf(expr); typ != nil {
if basic, ok := typ.Underlying().(*types.Basic); ok {
if (basic.Info() & types.IsFloat) != 0 {
found = true
return false
}
}
}
}
return true
})
return found
}
for c := range code.Cursor(pass).Preorder((*ast.UnaryExpr)(nil)) {
node := c.Node()
matcher, ok := code.Match(pass, demorganQ, node)
if !ok {
continue
}
expr := matcher.State["expr"].(ast.Expr)
// be extremely conservative when it comes to floats
if hasFloats(expr) {
continue
}
n := astutil.NegateDeMorgan(expr, false)
nr := astutil.NegateDeMorgan(expr, true)
nc, ok := astutil.CopyExpr(n)
if !ok {
continue
}
ns := astutil.SimplifyParentheses(nc)
nrc, ok := astutil.CopyExpr(nr)
if !ok {
continue
}
nrs := astutil.SimplifyParentheses(nrc)
var bn, bnr, bns, bnrs string
switch c.Parent().Node().(type) {
case *ast.BinaryExpr, *ast.IfStmt, *ast.ForStmt, *ast.SwitchStmt:
// Always add parentheses for if, for and switch. If
// they're unnecessary, go/printer will strip them when
// the whole file gets formatted.
bn = report.Render(pass, &ast.ParenExpr{X: n})
bnr = report.Render(pass, &ast.ParenExpr{X: nr})
bns = report.Render(pass, &ast.ParenExpr{X: ns})
bnrs = report.Render(pass, &ast.ParenExpr{X: nrs})
default:
// TODO are there other types where we don't want to strip parentheses?
bn = report.Render(pass, n)
bnr = report.Render(pass, nr)
bns = report.Render(pass, ns)
bnrs = report.Render(pass, nrs)
}
// Note: we cannot compare the ASTs directly, because
// simplifyParentheses might have rebalanced trees without
// affecting the rendered form.
var fixes []analysis.SuggestedFix
fixes = append(fixes, edit.Fix("Apply De Morgan's law", edit.ReplaceWithString(node, bn)))
if bn != bns {
fixes = append(fixes, edit.Fix("Apply De Morgan's law & simplify", edit.ReplaceWithString(node, bns)))
}
if bn != bnr {
fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively", edit.ReplaceWithString(node, bnr)))
if bnr != bnrs {
fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively & simplify", edit.ReplaceWithString(node, bnrs)))
}
}
report.Report(pass, node, "could apply De Morgan's law", report.Fixes(fixes...))
}
return nil, nil
}
================================================
FILE: quickfix/qf1001/qf1001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1001/testdata/go1.0/CheckDeMorgan/CheckDeMorgan.go
================================================
package pkg
func fn() {
var a, b, c bool
var e, f, g int
var h, i float64
_ = !(a && b && (!c || e > f) && g == f) //@ diag(`could apply De Morgan's law`)
_ = !(a && h > i)
}
================================================
FILE: quickfix/qf1001/testdata/go1.0/CheckDeMorgan/CheckDeMorgan.go.golden
================================================
-- Apply De Morgan's law --
package pkg
func fn() {
var a, b, c bool
var e, f, g int
var h, i float64
_ = !a || !b || !(!c || e > f) || g != f //@ diag(`could apply De Morgan's law`)
_ = !(a && h > i)
}
-- Apply De Morgan's law recursively --
package pkg
func fn() {
var a, b, c bool
var e, f, g int
var h, i float64
_ = !a || !b || (c && e <= f) || g != f //@ diag(`could apply De Morgan's law`)
_ = !(a && h > i)
}
-- Apply De Morgan's law recursively & simplify --
package pkg
func fn() {
var a, b, c bool
var e, f, g int
var h, i float64
_ = !a || !b || c && e <= f || g != f //@ diag(`could apply De Morgan's law`)
_ = !(a && h > i)
}
================================================
FILE: quickfix/qf1001/testdata/go1.0/CheckDeMorgan/kvexpr.go
================================================
package pkg
func do() bool {
type Info struct {
idx int
}
var state map[Info]int
// Don't crash on KeyValueExpr
return !(state[Info{idx: 6}] == 6 || false) //@ diag(`could apply De Morgan's law`)
}
================================================
FILE: quickfix/qf1001/testdata/go1.0/CheckDeMorgan/kvexpr.go.golden
================================================
-- Apply De Morgan's law --
package pkg
func do() bool {
type Info struct {
idx int
}
var state map[Info]int
// Don't crash on KeyValueExpr
return state[Info{idx: 6}] != 6 && !false //@ diag(`could apply De Morgan's law`)
}
================================================
FILE: quickfix/qf1002/qf1002.go
================================================
package qf1002
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1002",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Convert untagged switch to tagged switch",
Text: `
An untagged switch that compares a single variable against a series of
values can be replaced with a tagged switch.`,
Before: `
switch {
case x == 1 || x == 2, x == 3:
...
case x == 4:
...
default:
...
}`,
After: `
switch x {
case 1, 2, 3:
...
case 4:
...
default:
...
}`,
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
swtch := node.(*ast.SwitchStmt)
if swtch.Tag != nil || len(swtch.Body.List) == 0 {
return
}
pairs := make([][]*ast.BinaryExpr, len(swtch.Body.List))
for i, stmt := range swtch.Body.List {
stmt := stmt.(*ast.CaseClause)
for _, cond := range stmt.List {
if !findSwitchPairs(pass, cond, &pairs[i]) {
return
}
}
}
var x ast.Expr
for _, pair := range pairs {
if len(pair) == 0 {
continue
}
if x == nil {
x = pair[0].X
} else {
if !astutil.Equal(x, pair[0].X) {
return
}
}
}
if x == nil {
// the switch only has a default case
if len(pairs) > 1 {
panic("found more than one case clause with no pairs")
}
return
}
edits := make([]analysis.TextEdit, 0, len(swtch.Body.List)+1)
for i, stmt := range swtch.Body.List {
stmt := stmt.(*ast.CaseClause)
if stmt.List == nil {
continue
}
var values []string
for _, binexpr := range pairs[i] {
y := binexpr.Y
if p, ok := y.(*ast.ParenExpr); ok {
y = p.X
}
values = append(values, report.Render(pass, y))
}
edits = append(edits, edit.ReplaceWithString(edit.Range{stmt.List[0].Pos(), stmt.Colon}, strings.Join(values, ", ")))
}
pos := swtch.Body.Lbrace
edits = append(edits, edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, x)))
report.Report(pass, swtch, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)),
report.Fixes(edit.Fix("Replace with tagged switch", edits...)),
report.ShortRange())
}
code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
return nil, nil
}
func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool {
binexpr, ok := astutil.Unparen(expr).(*ast.BinaryExpr)
if !ok {
return false
}
switch binexpr.Op {
case token.EQL:
if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) {
return false
}
// syntactic identity should suffice. we do not allow side
// effects in the case clauses, so there should be no way for
// values to change.
if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) {
return false
}
*pairs = append(*pairs, binexpr)
return true
case token.LOR:
return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs)
default:
return false
}
}
================================================
FILE: quickfix/qf1002/qf1002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1002/testdata/go1.0/CheckTaglessSwitch/CheckTaglessSwitch.go
================================================
package pkg
func foo() int { return 0 }
func fn1() {
var x, y int
var z map[string][]int
var a bool
switch { //@ diag(`could use tagged switch on x`)
case x == 4: // comment
case x == 1 || x == 2, x == 3:
}
switch { //@ diag(`could use tagged switch on x`)
case x == 1 || x == 2, x == 3:
case x == 4:
default:
}
switch { //@ diag(`could use tagged switch on z[""][0]`)
case z[""][0] == 1 || z[""][0] == 2:
}
switch { //@ diag(`could use tagged switch on a`)
case a == (x == y) || a == (x != y):
}
switch {
case z[""][0] == 1 || z[""][1] == 2:
}
switch {
case x == 1 || x == 2, y == 3:
case x == 4:
default:
}
switch {
case x == 1 || x == 2, x == 3:
case y == 4:
}
switch {
case x == 1 || x == 2, x == foo():
case x == 4:
default:
}
switch {
}
switch {
default:
}
switch {
case x == 1 && x == 2:
}
switch b := 42; { //@ diag(`could use tagged switch on b`)
case b == 0:
case b == 1:
default:
_ = b
}
}
================================================
FILE: quickfix/qf1002/testdata/go1.0/CheckTaglessSwitch/CheckTaglessSwitch.go.golden
================================================
package pkg
func foo() int { return 0 }
func fn1() {
var x, y int
var z map[string][]int
var a bool
switch x { //@ diag(`could use tagged switch on x`)
case 4: // comment
case 1, 2, 3:
}
switch x { //@ diag(`could use tagged switch on x`)
case 1, 2, 3:
case 4:
default:
}
switch z[""][0] { //@ diag(`could use tagged switch on z[""][0]`)
case 1, 2:
}
switch a { //@ diag(`could use tagged switch on a`)
case x == y, x != y:
}
switch {
case z[""][0] == 1 || z[""][1] == 2:
}
switch {
case x == 1 || x == 2, y == 3:
case x == 4:
default:
}
switch {
case x == 1 || x == 2, x == 3:
case y == 4:
}
switch {
case x == 1 || x == 2, x == foo():
case x == 4:
default:
}
switch {
}
switch {
default:
}
switch {
case x == 1 && x == 2:
}
switch b := 42; b { //@ diag(`could use tagged switch on b`)
case 0:
case 1:
default:
_ = b
}
}
================================================
FILE: quickfix/qf1003/qf1003.go
================================================
package qf1003
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1003",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Convert if/else-if chain to tagged switch",
Text: `
A series of if/else-if checks comparing the same variable against
values can be replaced with a tagged switch.`,
Before: `
if x == 1 || x == 2 {
...
} else if x == 3 {
...
} else {
...
}`,
After: `
switch x {
case 1, 2:
...
case 3:
...
default:
...
}`,
Since: "2021.1",
Severity: lint.SeverityInfo,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
nodeLoop:
for c := range code.Cursor(pass).Preorder((*ast.IfStmt)(nil)) {
node := c.Node()
if _, ok := c.Parent().Node().(*ast.IfStmt); ok {
// this if statement is part of an if-else chain
continue
}
ifstmt := node.(*ast.IfStmt)
m := map[ast.Expr][]*ast.BinaryExpr{}
for item := ifstmt; item != nil; {
if item.Init != nil {
continue nodeLoop
}
if item.Body == nil {
continue nodeLoop
}
skip := false
ast.Inspect(item.Body, func(node ast.Node) bool {
if branch, ok := node.(*ast.BranchStmt); ok && branch.Tok != token.GOTO {
skip = true
return false
}
return true
})
if skip {
continue nodeLoop
}
var pairs []*ast.BinaryExpr
if !findSwitchPairs(pass, item.Cond, &pairs) {
continue nodeLoop
}
m[item.Cond] = pairs
switch els := item.Else.(type) {
case *ast.IfStmt:
item = els
case *ast.BlockStmt, nil:
item = nil
default:
panic(fmt.Sprintf("unreachable: %T", els))
}
}
var x ast.Expr
for _, pair := range m {
if len(pair) == 0 {
continue
}
if x == nil {
x = pair[0].X
} else {
if !astutil.Equal(x, pair[0].X) {
continue nodeLoop
}
}
}
if x == nil {
// shouldn't happen
continue nodeLoop
}
// We require at least two 'if' to make this suggestion, to
// avoid clutter in the editor.
if len(m) < 2 {
continue nodeLoop
}
// Note that we insert the switch statement as the first text edit instead of the last one so that gopls has an
// easier time converting it to an LSP-conforming edit.
//
// Specifically:
// > Text edits ranges must never overlap, that means no part of the original
// > document must be manipulated by more than one edit. However, it is
// > possible that multiple edits have the same start position: multiple
// > inserts, or any number of inserts followed by a single remove or replace
// > edit. If multiple inserts have the same position, the order in the array
// > defines the order in which the inserted strings appear in the resulting
// > text.
//
// See https://go.dev/issue/63930
//
// FIXME this edit forces the first case to begin in column 0 because we ignore indentation. try to fix that.
edits := []analysis.TextEdit{edit.ReplaceWithString(edit.Range{ifstmt.If, ifstmt.If}, fmt.Sprintf("switch %s {\n", report.Render(pass, x)))}
for item := ifstmt; item != nil; {
var end token.Pos
if item.Else != nil {
end = item.Else.Pos()
} else {
// delete up to but not including the closing brace.
end = item.Body.Rbrace
}
var conds []string
for _, cond := range m[item.Cond] {
y := cond.Y
if p, ok := y.(*ast.ParenExpr); ok {
y = p.X
}
conds = append(conds, report.Render(pass, y))
}
sconds := strings.Join(conds, ", ")
edits = append(edits,
edit.ReplaceWithString(edit.Range{item.If, item.Body.Lbrace + 1}, "case "+sconds+":"),
edit.Delete(edit.Range{item.Body.Rbrace, end}))
switch els := item.Else.(type) {
case *ast.IfStmt:
item = els
case *ast.BlockStmt:
edits = append(edits, edit.ReplaceWithString(edit.Range{els.Lbrace, els.Lbrace + 1}, "default:"))
item = nil
case nil:
item = nil
default:
panic(fmt.Sprintf("unreachable: %T", els))
}
}
report.Report(pass, ifstmt, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)),
report.Fixes(edit.Fix("Replace with tagged switch", edits...)),
report.ShortRange())
}
return nil, nil
}
func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool {
binexpr, ok := astutil.Unparen(expr).(*ast.BinaryExpr)
if !ok {
return false
}
switch binexpr.Op {
case token.EQL:
if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) {
return false
}
// syntactic identity should suffice. we do not allow side
// effects in the case clauses, so there should be no way for
// values to change.
if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) {
return false
}
*pairs = append(*pairs, binexpr)
return true
case token.LOR:
return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs)
default:
return false
}
}
================================================
FILE: quickfix/qf1003/qf1003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1003/testdata/go1.0/CheckIfElseToSwitch/CheckIfElseToSwitch.go
================================================
package pkg
func fn() {
var x, y int
var z []int
var a bool
if x == 1 || x == 2 { //@ diag(`could use tagged switch on x`)
} else if x == 3 {
}
if x == 1 || x == 2 { //@ diag(`could use tagged switch on x`)
} else if x == 3 {
} else {
}
if x == 1 || x == 2 {
} else if y == 3 {
} else {
}
if a == (x == y) { //@ diag(`could use tagged switch on a`)
} else if a == (x != y) {
}
if z[0] == 1 || z[0] == 2 { //@ diag(`could use tagged switch on z[0]`)
} else if z[0] == 3 {
}
for {
if x == 1 || x == 2 { //@ diag(`could use tagged switch on x`)
} else if x == 3 {
}
}
for {
if x == 1 || x == 2 {
} else if x == 3 {
break
}
}
if x == 1 || x == 2 {
}
}
================================================
FILE: quickfix/qf1003/testdata/go1.0/CheckIfElseToSwitch/CheckIfElseToSwitch.go.golden
================================================
package pkg
func fn() {
var x, y int
var z []int
var a bool
switch x {
case 1, 2: //@ diag(`could use tagged switch on x`)
case 3:
}
switch x {
case 1, 2: //@ diag(`could use tagged switch on x`)
case 3:
default:
}
if x == 1 || x == 2 {
} else if y == 3 {
} else {
}
switch a {
case x == y: //@ diag(`could use tagged switch on a`)
case x != y:
}
switch z[0] {
case 1, 2: //@ diag(`could use tagged switch on z[0]`)
case 3:
}
for {
switch x {
case 1, 2: //@ diag(`could use tagged switch on x`)
case 3:
}
}
for {
if x == 1 || x == 2 {
} else if x == 3 {
break
}
}
if x == 1 || x == 2 {
}
}
================================================
FILE: quickfix/qf1004/qf1004.go
================================================
package qf1004
import (
"fmt"
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
typeindexanalyzer "honnef.co/go/tools/internal/analysisinternal/typeindex"
"honnef.co/go/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1004",
Run: run,
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use \'strings.ReplaceAll\' instead of \'strings.Replace\' with \'n == -1\'`,
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var fns = []struct {
path string
name string
replacement string
}{
{"strings", "Replace", "strings.ReplaceAll"},
{"strings", "SplitN", "strings.Split"},
{"strings", "SplitAfterN", "strings.SplitAfter"},
{"bytes", "Replace", "bytes.ReplaceAll"},
{"bytes", "SplitN", "bytes.Split"},
{"bytes", "SplitAfterN", "bytes.SplitAfter"},
}
func run(pass *analysis.Pass) (any, error) {
// XXX respect minimum Go version
// FIXME(dh): create proper suggested fix for renamed import
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for _, fn := range fns {
for c := range index.Calls(index.Object(fn.path, fn.name)) {
call := c.Node().(*ast.CallExpr)
if op, ok := call.Args[len(call.Args)-1].(*ast.UnaryExpr); ok && op.Op == token.SUB {
if lit, ok := op.X.(*ast.BasicLit); ok && lit.Value == "1" {
report.Report(pass, call.Fun, fmt.Sprintf("could use %s instead", fn.replacement),
report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead", fn.replacement),
edit.ReplaceWithString(call.Fun, fn.replacement),
edit.Delete(op))))
}
}
}
}
return nil, nil
}
================================================
FILE: quickfix/qf1004/qf1004_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1004
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1004/testdata/go1.0/CheckStringsReplaceAll/CheckStringsReplaceAll.go
================================================
package pkg
import (
"bytes"
"strings"
)
func fn() {
strings.Replace("", "", "", -1) //@ diag(`could use strings.ReplaceAll instead`)
strings.Replace("", "", "", 0)
strings.Replace("", "", "", 1)
strings.SplitN("", "", -1) //@ diag(`could use strings.Split instead`)
strings.SplitN("", "", 0)
strings.SplitN("", "", 1)
strings.SplitAfterN("", "", -1) //@ diag(`could use strings.SplitAfter instead`)
strings.SplitAfterN("", "", 0)
strings.SplitAfterN("", "", 1)
bytes.Replace(nil, nil, nil, -1) //@ diag(`could use bytes.ReplaceAll instead`)
bytes.Replace(nil, nil, nil, 0)
bytes.Replace(nil, nil, nil, 1)
bytes.SplitN(nil, nil, -1) //@ diag(`could use bytes.Split instead`)
bytes.SplitN(nil, nil, 0)
bytes.SplitN(nil, nil, 1)
bytes.SplitAfterN(nil, nil, -1) //@ diag(`could use bytes.SplitAfter instead`)
bytes.SplitAfterN(nil, nil, 0)
bytes.SplitAfterN(nil, nil, 1)
}
================================================
FILE: quickfix/qf1004/testdata/go1.0/CheckStringsReplaceAll/CheckStringsReplaceAll.go.golden
================================================
package pkg
import (
"bytes"
"strings"
)
func fn() {
strings.ReplaceAll("", "", "") //@ diag(`could use strings.ReplaceAll instead`)
strings.Replace("", "", "", 0)
strings.Replace("", "", "", 1)
strings.Split("", "") //@ diag(`could use strings.Split instead`)
strings.SplitN("", "", 0)
strings.SplitN("", "", 1)
strings.SplitAfter("", "") //@ diag(`could use strings.SplitAfter instead`)
strings.SplitAfterN("", "", 0)
strings.SplitAfterN("", "", 1)
bytes.ReplaceAll(nil, nil, nil) //@ diag(`could use bytes.ReplaceAll instead`)
bytes.Replace(nil, nil, nil, 0)
bytes.Replace(nil, nil, nil, 1)
bytes.Split(nil, nil) //@ diag(`could use bytes.Split instead`)
bytes.SplitN(nil, nil, 0)
bytes.SplitN(nil, nil, 1)
bytes.SplitAfter(nil, nil) //@ diag(`could use bytes.SplitAfter instead`)
bytes.SplitAfterN(nil, nil, 0)
bytes.SplitAfterN(nil, nil, 1)
}
================================================
FILE: quickfix/qf1005/qf1005.go
================================================
package qf1005
import (
"go/ast"
"go/constant"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1005",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Expand call to \'math.Pow\'`,
Text: `Some uses of \'math.Pow\' can be simplified to basic multiplication.`,
Before: `math.Pow(x, 2)`,
After: `x * x`,
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var mathPowQ = pattern.MustParse(`(CallExpr (Symbol "math.Pow") [x (IntegerLiteral n)])`)
func run(pass *analysis.Pass) (any, error) {
for node, matcher := range code.Matches(pass, mathPowQ) {
x := matcher.State["x"].(ast.Expr)
if code.MayHaveSideEffects(pass, x, nil) {
continue
}
n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value))
if !ok {
continue
}
needConversion := false
if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil {
info := types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
}
// determine if the constant expression would have type float64 if used on its own
if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil {
// This should not happen
continue
}
if T, ok := info.Types[x].Type.(*types.Basic); ok {
if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 {
needConversion = true
}
} else {
needConversion = true
}
}
var replacement ast.Expr
switch n {
case 0:
replacement = &ast.BasicLit{
Kind: token.FLOAT,
Value: "1.0",
}
case 1:
replacement = x
case 2, 3:
r := &ast.BinaryExpr{
X: x,
Op: token.MUL,
Y: x,
}
for i := 3; i <= int(n); i++ {
r = &ast.BinaryExpr{
X: r,
Op: token.MUL,
Y: x,
}
}
rc, ok := astutil.CopyExpr(r)
if !ok {
continue
}
replacement = astutil.SimplifyParentheses(rc)
default:
continue
}
if needConversion && n != 0 {
replacement = &ast.CallExpr{
Fun: &ast.Ident{Name: "float64"},
Args: []ast.Expr{replacement},
}
}
report.Report(pass, node, "could expand call to math.Pow",
report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement))))
}
return nil, nil
}
================================================
FILE: quickfix/qf1005/qf1005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1005/testdata/go1.0/CheckMathPow/CheckMathPow.go
================================================
package pkg
import "math"
func fn() {
var x float64
_ = math.Pow(x, 0) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, 1) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, 3) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, 6)
const a = 2
const b = 2.0
const c float64 = 2
_ = math.Pow(a, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(b, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(c, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(a*1.0, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x*2, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x+2, 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, x)
_ = math.Pow(x, -1)
}
================================================
FILE: quickfix/qf1005/testdata/go1.0/CheckMathPow/CheckMathPow.go.golden
================================================
package pkg
import "math"
func fn() {
var x float64
_ = 1.0 //@ diag(`could expand call to math.Pow`)
_ = x //@ diag(`could expand call to math.Pow`)
_ = x * x //@ diag(`could expand call to math.Pow`)
_ = x * x * x //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, 6)
const a = 2
const b = 2.0
const c float64 = 2
_ = float64(a * a) //@ diag(`could expand call to math.Pow`)
_ = b * b //@ diag(`could expand call to math.Pow`)
_ = c * c //@ diag(`could expand call to math.Pow`)
_ = a * 1.0 * a * 1.0 //@ diag(`could expand call to math.Pow`)
_ = x * 2 * x * 2 //@ diag(`could expand call to math.Pow`)
_ = (x + 2) * (x + 2) //@ diag(`could expand call to math.Pow`)
_ = math.Pow(x, x)
_ = math.Pow(x, -1)
}
================================================
FILE: quickfix/qf1006/qf1006.go
================================================
package qf1006
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1006",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Lift \'if\'+\'break\' into loop condition`,
Before: `
for {
if done {
break
}
...
}`,
After: `
for !done {
...
}`,
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkForLoopIfBreak = pattern.MustParse(`(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt "BREAK" nil) nil):_)`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkForLoopIfBreak) {
pos := node.Pos() + token.Pos(len("for"))
r := astutil.NegateDeMorgan(m.State["cond"].(ast.Expr), false)
// FIXME(dh): we're leaving behind an empty line when we
// delete the old if statement. However, we can't just delete
// an additional character, in case there closing curly brace
// is followed by a comment, or Windows newlines.
report.Report(pass, m.State["if"].(ast.Node), "could lift into loop condition",
report.Fixes(edit.Fix("Lift into loop condition",
edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, r)),
edit.Delete(m.State["if"].(ast.Node)))))
}
return nil, nil
}
================================================
FILE: quickfix/qf1006/qf1006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1006/testdata/go1.0/CheckForLoopIfBreak/CheckForLoopIfBreak.go
================================================
package pkg
func done() bool { return false }
var a, b int
var x bool
func fn() {
for {
if done() { //@ diag(`could lift into loop condition`)
break
}
}
for {
if !done() { //@ diag(`could lift into loop condition`)
break
}
}
for {
if a > b || b > a { //@ diag(`could lift into loop condition`)
break
}
}
for {
if x && (a == b) { //@ diag(`could lift into loop condition`)
break
}
}
for {
if done() { //@ diag(`could lift into loop condition`)
break
}
println()
}
for {
println()
if done() {
break
}
}
for {
if done() {
println()
break
}
}
}
================================================
FILE: quickfix/qf1006/testdata/go1.0/CheckForLoopIfBreak/CheckForLoopIfBreak.go.golden
================================================
package pkg
func done() bool { return false }
var a, b int
var x bool
func fn() {
for !done() {
}
for done() {
}
for a <= b && b <= a {
}
for !x || !(a == b) {
}
for !done() {
println()
}
for {
println()
if done() {
break
}
}
for {
if done() {
println()
break
}
}
}
================================================
FILE: quickfix/qf1007/qf1007.go
================================================
package qf1007
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1007",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Merge conditional assignment into variable declaration",
Before: `
x := false
if someCondition {
x = true
}`,
After: `x := someCondition`,
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkConditionalAssignmentQ = pattern.MustParse(`(AssignStmt x@(Object _) ":=" assign@(Builtin b@(Or "true" "false")))`)
var checkConditionalAssignmentIfQ = pattern.MustParse(`(IfStmt nil cond [(AssignStmt x@(Object _) "=" (Builtin b@(Or "true" "false")))] nil)`)
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
var body *ast.BlockStmt
switch node := node.(type) {
case *ast.FuncDecl:
body = node.Body
case *ast.FuncLit:
body = node.Body
default:
panic("unreachable")
}
if body == nil {
return
}
stmts := body.List
if len(stmts) < 2 {
return
}
for i, first := range stmts[:len(stmts)-1] {
second := stmts[i+1]
m1, ok := code.Match(pass, checkConditionalAssignmentQ, first)
if !ok {
continue
}
m2, ok := code.Match(pass, checkConditionalAssignmentIfQ, second)
if !ok {
continue
}
if m1.State["x"] != m2.State["x"] {
continue
}
if m1.State["b"] == m2.State["b"] {
continue
}
v := m2.State["cond"].(ast.Expr)
if m1.State["b"] == "true" {
v = &ast.UnaryExpr{
Op: token.NOT,
X: v,
}
}
report.Report(pass, first, "could merge conditional assignment into variable declaration",
report.Fixes(edit.Fix("Merge conditional assignment into variable declaration",
edit.ReplaceWithNode(pass.Fset, m1.State["assign"].(ast.Node), v),
edit.Delete(second))))
}
}
code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
return nil, nil
}
================================================
FILE: quickfix/qf1007/qf1007_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1007
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1007/testdata/go1.0/CheckConditionalAssignment/CheckConditionalAssignment.go
================================================
package pkg
func foo() bool { return true }
var bar bool
var baz bool
func fn() {
x := false //@ diag(`merge conditional assignment`)
if foo() || (bar && !baz) {
x = true
}
x = false
if foo() || (bar && !baz) {
x = true
}
y := false
if true {
y = true
println(y)
}
z := false
if true {
z = false
}
a := false
if true {
x = true
}
b := true //@ diag(`merge conditional assignment`)
if foo() || (bar && !baz) {
b = false
}
c := false
if true {
c = false
}
d := true
if true {
d = true
}
_ = x
_ = y
_ = z
_ = a
_ = b
_ = c
_ = d
}
================================================
FILE: quickfix/qf1007/testdata/go1.0/CheckConditionalAssignment/CheckConditionalAssignment.go.golden
================================================
package pkg
func foo() bool { return true }
var bar bool
var baz bool
func fn() {
x := foo() || (bar && !baz) //@ diag(`merge conditional assignment`)
x = false
if foo() || (bar && !baz) {
x = true
}
y := false
if true {
y = true
println(y)
}
z := false
if true {
z = false
}
a := false
if true {
x = true
}
b := !(foo() || (bar && !baz)) //@ diag(`merge conditional assignment`)
c := false
if true {
c = false
}
d := true
if true {
d = true
}
_ = x
_ = y
_ = z
_ = a
_ = b
_ = c
_ = d
}
================================================
FILE: quickfix/qf1008/qf1008.go
================================================
package qf1008
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1008",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Omit embedded fields from selector expression",
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
type Selector struct {
Node *ast.SelectorExpr
X ast.Expr
Fields []*ast.Ident
}
// extractSelectors extracts uninterrupted sequences of selector expressions.
// For example, for a.b.c().d.e[0].f.g three sequences will be returned: (X=a, X.b.c), (X=a.b.c(), X.d.e), and (X=a.b.c().d.e[0], X.f.g)
//
// It returns nil if the provided selector expression is not the root of a set of sequences.
// For example, for a.b.c, if node is b.c, no selectors will be returned.
extractSelectors := func(expr *ast.SelectorExpr) []Selector {
path, _ := astutil.PathEnclosingInterval(code.File(pass, expr), expr.Pos(), expr.Pos())
for i := len(path) - 1; i >= 0; i-- {
if el, ok := path[i].(*ast.SelectorExpr); ok {
if el != expr {
// this expression is a subset of the entire chain, don't look at it.
return nil
}
break
}
}
inChain := false
var out []Selector
for _, el := range path {
if expr, ok := el.(*ast.SelectorExpr); ok {
if !inChain {
inChain = true
out = append(out, Selector{X: expr.X})
}
sel := &out[len(out)-1]
sel.Fields = append(sel.Fields, expr.Sel)
sel.Node = expr
} else if inChain {
inChain = false
}
}
return out
}
fn := func(node ast.Node) {
expr := node.(*ast.SelectorExpr)
if _, ok := expr.X.(*ast.SelectorExpr); !ok {
// Avoid the expensive call to PathEnclosingInterval for the common 1-level deep selector, which cannot be shortened.
return
}
sels := extractSelectors(expr)
if len(sels) == 0 {
return
}
for _, sel := range sels {
fieldLoop:
for base, fields := pass.TypesInfo.TypeOf(sel.X), sel.Fields; len(fields) >= 2; base, fields = pass.TypesInfo.ObjectOf(fields[0]).Type(), fields[1:] {
hop1 := fields[0]
hop2 := fields[1]
// the selector expression might be a qualified identifier, which cannot be simplified
if base == types.Typ[types.Invalid] {
continue fieldLoop
}
// Check if we can skip a field in the chain of selectors.
// We can skip a field 'b' if a.b.c and a.c resolve to the same object and take the same path.
//
// We set addressable to true unconditionally because we've already successfully type-checked the program,
// which means either the selector doesn't need addressability, or it is addressable.
leftObj, leftLeg, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop1.Name)
// We can't skip fields that aren't embedded
if !leftObj.(*types.Var).Embedded() {
continue fieldLoop
}
directObj, directPath, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop2.Name)
// Fail fast if omitting the embedded field leads to a different object
if directObj != pass.TypesInfo.ObjectOf(hop2) {
continue fieldLoop
}
_, rightLeg, _ := types.LookupFieldOrMethod(leftObj.Type(), true, pass.Pkg, hop2.Name)
// Fail fast if the paths are obviously different
if len(directPath) != len(leftLeg)+len(rightLeg) {
continue fieldLoop
}
// Make sure that omitting the embedded field will take the same path to the final object.
// Multiple paths involving different fields may lead to the same type-checker object, causing different runtime behavior.
for i := range directPath {
if i < len(leftLeg) {
if leftLeg[i] != directPath[i] {
continue fieldLoop
}
} else {
if rightLeg[i-len(leftLeg)] != directPath[i] {
continue fieldLoop
}
}
}
e := edit.Delete(edit.Range{hop1.Pos(), hop2.Pos()})
report.Report(pass, hop1, fmt.Sprintf("could remove embedded field %q from selector", hop1.Name),
report.Fixes(edit.Fix(fmt.Sprintf("Remove embedded field %q from selector", hop1.Name), e)))
}
}
}
code.Preorder(pass, fn, (*ast.SelectorExpr)(nil))
return nil, nil
}
================================================
FILE: quickfix/qf1008/qf1008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-anon.go
================================================
package pkg
type AnonOuter struct{ AnonInner }
type AnonInner struct{ F4 struct{ F5 int } }
func fnAnon() {
var anon AnonOuter
_ = anon.AnonInner.F4.F5 //@ diag(`could remove embedded field "AnonInner" from selector`)
_ = anon.F4.F5 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-anon.go.golden
================================================
package pkg
type AnonOuter struct{ AnonInner }
type AnonInner struct{ F4 struct{ F5 int } }
func fnAnon() {
var anon AnonOuter
_ = anon.F4.F5 //@ diag(`could remove embedded field "AnonInner" from selector`)
_ = anon.F4.F5 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-basic.go
================================================
package pkg
type BasicOuter struct{ BasicInner }
type BasicInner struct{ F1 int }
func fnBasic() {
var basic BasicOuter
_ = basic.BasicInner.F1 //@ diag(`could remove embedded field "BasicInner" from selector`)
_ = basic.F1 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-basic.go.golden
================================================
package pkg
type BasicOuter struct{ BasicInner }
type BasicInner struct{ F1 int }
func fnBasic() {
var basic BasicOuter
_ = basic.F1 //@ diag(`could remove embedded field "BasicInner" from selector`)
_ = basic.F1 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-call.go
================================================
package pkg
type FunctionCallOuter struct{ FunctionCallInner }
type FunctionCallInner struct {
F8 func() FunctionCallContinuedOuter
}
type FunctionCallContinuedOuter struct{ FunctionCallContinuedInner }
type FunctionCallContinuedInner struct{ F9 int }
func fnCall() {
var call FunctionCallOuter
_ = call.FunctionCallInner.F8().FunctionCallContinuedInner.F9 //@ diag(`could remove embedded field "FunctionCallInner" from selector`), diag(`could remove embedded field "FunctionCallContinuedInner" from selector`)
_ = call.F8().F9 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-call.go.golden
================================================
-- Remove embedded field "FunctionCallInner" from selector --
package pkg
type FunctionCallOuter struct{ FunctionCallInner }
type FunctionCallInner struct {
F8 func() FunctionCallContinuedOuter
}
type FunctionCallContinuedOuter struct{ FunctionCallContinuedInner }
type FunctionCallContinuedInner struct{ F9 int }
func fnCall() {
var call FunctionCallOuter
_ = call.F8().FunctionCallContinuedInner.F9 //@ diag(`could remove embedded field "FunctionCallInner" from selector`), diag(`could remove embedded field "FunctionCallContinuedInner" from selector`)
_ = call.F8().F9 // minimal form
}
-- Remove embedded field "FunctionCallContinuedInner" from selector --
package pkg
type FunctionCallOuter struct{ FunctionCallInner }
type FunctionCallInner struct {
F8 func() FunctionCallContinuedOuter
}
type FunctionCallContinuedOuter struct{ FunctionCallContinuedInner }
type FunctionCallContinuedInner struct{ F9 int }
func fnCall() {
var call FunctionCallOuter
_ = call.FunctionCallInner.F8().F9 //@ diag(`could remove embedded field "FunctionCallInner" from selector`), diag(`could remove embedded field "FunctionCallContinuedInner" from selector`)
_ = call.F8().F9 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-depth.go
================================================
package pkg
func fnDepth() {
type T4 struct{ F int }
type T5 struct{ T4 }
type T3 struct{ T5 }
type T2 struct{ T4 }
type T1 struct {
T2
T3
}
var v T1
_ = v.F
_ = v.T2.F //@ diag(`could remove embedded field "T2" from selector`)
_ = v.T3.F
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-depth.go.golden
================================================
package pkg
func fnDepth() {
type T4 struct{ F int }
type T5 struct{ T4 }
type T3 struct{ T5 }
type T2 struct{ T4 }
type T1 struct {
T2
T3
}
var v T1
_ = v.F
_ = v.F //@ diag(`could remove embedded field "T2" from selector`)
_ = v.T3.F
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-multi.go
================================================
package pkg
type MultiLevel struct{ BasicOuter }
func fnMulti() {
var multi MultiLevel
_ = multi.BasicOuter.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = multi.BasicInner.F1 //@ diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.F1 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-multi.go.golden
================================================
-- Remove embedded field "BasicInner" from selector --
package pkg
type MultiLevel struct{ BasicOuter }
func fnMulti() {
var multi MultiLevel
_ = multi.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = multi.F1 //@ diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.F1 // minimal form
}
-- Remove embedded field "BasicOuter" from selector --
package pkg
type MultiLevel struct{ BasicOuter }
func fnMulti() {
var multi MultiLevel
_ = multi.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = multi.BasicInner.F1 //@ diag(`could remove embedded field "BasicInner" from selector`)
_ = multi.F1 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-multi2.go
================================================
package pkg
type Embedded2 struct{}
type Embedded struct{ Embedded2 }
type Wrapper struct{ Embedded }
func (e Embedded2) String() string { return "" }
func (w Wrapper) String() string {
// Either Embedded or Embedded2 can be removed, but removing both would
// change the semantics
return w.Embedded.Embedded2.String() //@ diag(`could remove embedded field "Embedded"`), diag(`could remove embedded field "Embedded2`)
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-multi2.go.golden
================================================
-- Remove embedded field "Embedded" from selector --
package pkg
type Embedded2 struct{}
type Embedded struct{ Embedded2 }
type Wrapper struct{ Embedded }
func (e Embedded2) String() string { return "" }
func (w Wrapper) String() string {
// Either Embedded or Embedded2 can be removed, but removing both would
// change the semantics
return w.Embedded2.String() //@ diag(`could remove embedded field "Embedded"`), diag(`could remove embedded field "Embedded2`)
}
-- Remove embedded field "Embedded2" from selector --
package pkg
type Embedded2 struct{}
type Embedded struct{ Embedded2 }
type Wrapper struct{ Embedded }
func (e Embedded2) String() string { return "" }
func (w Wrapper) String() string {
// Either Embedded or Embedded2 can be removed, but removing both would
// change the semantics
return w.Embedded.String() //@ diag(`could remove embedded field "Embedded"`), diag(`could remove embedded field "Embedded2`)
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-partial-multi.go
================================================
package pkg
type PartialMultiLevel struct{ F3 MultiLevel }
type PartialMultiLevel2Outer struct{ PartialMultiLevel2Inner }
type PartialMultiLevel2Inner struct{ F6 PartialMultiLevel2Outer2 }
type PartialMultiLevel2Outer2 struct{ PartialMultiLevel2Inner2 }
type PartialMultiLevel2Inner2 struct{ F7 int }
func fnPartialMulti() {
var partialMulti PartialMultiLevel
_ = partialMulti.F3.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = partialMulti.F3.BasicOuter.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = partialMulti.F3.F1 // minimal form
var partialMulti2 PartialMultiLevel2Outer
_ = partialMulti2.PartialMultiLevel2Inner.F6.PartialMultiLevel2Inner2.F7 //@ diag(`could remove embedded field "PartialMultiLevel2Inner2" from selector`), diag(`could remove embedded field "PartialMultiLevel2Inner" from selector`)
_ = partialMulti2.F6.F7 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-partial-multi.go.golden
================================================
-- Remove embedded field "BasicOuter" from selector --
package pkg
type PartialMultiLevel struct{ F3 MultiLevel }
type PartialMultiLevel2Outer struct{ PartialMultiLevel2Inner }
type PartialMultiLevel2Inner struct{ F6 PartialMultiLevel2Outer2 }
type PartialMultiLevel2Outer2 struct{ PartialMultiLevel2Inner2 }
type PartialMultiLevel2Inner2 struct{ F7 int }
func fnPartialMulti() {
var partialMulti PartialMultiLevel
_ = partialMulti.F3.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = partialMulti.F3.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = partialMulti.F3.F1 // minimal form
var partialMulti2 PartialMultiLevel2Outer
_ = partialMulti2.PartialMultiLevel2Inner.F6.PartialMultiLevel2Inner2.F7 //@ diag(`could remove embedded field "PartialMultiLevel2Inner2" from selector`), diag(`could remove embedded field "PartialMultiLevel2Inner" from selector`)
_ = partialMulti2.F6.F7 // minimal form
}
-- Remove embedded field "BasicInner" from selector --
package pkg
type PartialMultiLevel struct{ F3 MultiLevel }
type PartialMultiLevel2Outer struct{ PartialMultiLevel2Inner }
type PartialMultiLevel2Inner struct{ F6 PartialMultiLevel2Outer2 }
type PartialMultiLevel2Outer2 struct{ PartialMultiLevel2Inner2 }
type PartialMultiLevel2Inner2 struct{ F7 int }
func fnPartialMulti() {
var partialMulti PartialMultiLevel
_ = partialMulti.F3.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = partialMulti.F3.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = partialMulti.F3.F1 // minimal form
var partialMulti2 PartialMultiLevel2Outer
_ = partialMulti2.PartialMultiLevel2Inner.F6.PartialMultiLevel2Inner2.F7 //@ diag(`could remove embedded field "PartialMultiLevel2Inner2" from selector`), diag(`could remove embedded field "PartialMultiLevel2Inner" from selector`)
_ = partialMulti2.F6.F7 // minimal form
}
-- Remove embedded field "PartialMultiLevel2Inner2" from selector --
package pkg
type PartialMultiLevel struct{ F3 MultiLevel }
type PartialMultiLevel2Outer struct{ PartialMultiLevel2Inner }
type PartialMultiLevel2Inner struct{ F6 PartialMultiLevel2Outer2 }
type PartialMultiLevel2Outer2 struct{ PartialMultiLevel2Inner2 }
type PartialMultiLevel2Inner2 struct{ F7 int }
func fnPartialMulti() {
var partialMulti PartialMultiLevel
_ = partialMulti.F3.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = partialMulti.F3.BasicOuter.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = partialMulti.F3.F1 // minimal form
var partialMulti2 PartialMultiLevel2Outer
_ = partialMulti2.PartialMultiLevel2Inner.F6.F7 //@ diag(`could remove embedded field "PartialMultiLevel2Inner2" from selector`), diag(`could remove embedded field "PartialMultiLevel2Inner" from selector`)
_ = partialMulti2.F6.F7 // minimal form
}
-- Remove embedded field "PartialMultiLevel2Inner" from selector --
package pkg
type PartialMultiLevel struct{ F3 MultiLevel }
type PartialMultiLevel2Outer struct{ PartialMultiLevel2Inner }
type PartialMultiLevel2Inner struct{ F6 PartialMultiLevel2Outer2 }
type PartialMultiLevel2Outer2 struct{ PartialMultiLevel2Inner2 }
type PartialMultiLevel2Inner2 struct{ F7 int }
func fnPartialMulti() {
var partialMulti PartialMultiLevel
_ = partialMulti.F3.BasicOuter.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`)
_ = partialMulti.F3.BasicOuter.BasicInner.F1 //@ diag(`could remove embedded field "BasicOuter" from selector`), diag(`could remove embedded field "BasicInner" from selector`)
_ = partialMulti.F3.F1 // minimal form
var partialMulti2 PartialMultiLevel2Outer
_ = partialMulti2.F6.PartialMultiLevel2Inner2.F7 //@ diag(`could remove embedded field "PartialMultiLevel2Inner2" from selector`), diag(`could remove embedded field "PartialMultiLevel2Inner" from selector`)
_ = partialMulti2.F6.F7 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-qualified.go
================================================
package pkg
import (
"io"
assist "example.com/CheckExplicitEmbeddedSelectorassist"
)
func fnQualified() {
_ = io.EOF.Error // minimal form
_ = assist.V.T2.F //@ diag(`could remove embedded field "T2" from selector`)
_ = assist.V.F // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-qualified.go.golden
================================================
package pkg
import (
"io"
assist "example.com/CheckExplicitEmbeddedSelectorassist"
)
func fnQualified() {
_ = io.EOF.Error // minimal form
_ = assist.V.F //@ diag(`could remove embedded field "T2" from selector`)
_ = assist.V.F // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-recursive.go
================================================
package pkg
type T1 struct {
Next *T1
}
type T2 struct {
F int
*T2
T3
}
type T3 struct {
F2 int
}
func (*T1) Foo() {}
func (*T2) Foo() {}
func fn() {
var t1 T1
var t2 T2
_ = t1.Next.Foo
_ = t2.T2.Foo
_ = t2.T2.F
_ = t2.T3.F2 //@ diag(`could remove embedded field "T3" from selector`)
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-recursive.go.golden
================================================
package pkg
type T1 struct {
Next *T1
}
type T2 struct {
F int
*T2
T3
}
type T3 struct {
F2 int
}
func (*T1) Foo() {}
func (*T2) Foo() {}
func fn() {
var t1 T1
var t2 T2
_ = t1.Next.Foo
_ = t2.T2.Foo
_ = t2.T2.F
_ = t2.F2 //@ diag(`could remove embedded field "T3" from selector`)
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-shadowing.go
================================================
package pkg
type Shadowing struct {
F1 int
BasicInner
}
func fnShadowing() {
var shadowing Shadowing
_ = shadowing.BasicInner.F1 // can't be simplified due to shadowing
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-unexported.go
================================================
package pkg
type UnexportedSamePackageOuter struct {
unexportedSamePackageInner
}
type unexportedSamePackageInner struct {
F10 int
}
func fnUnexported() {
var unexportedSame UnexportedSamePackageOuter
_ = unexportedSame.unexportedSamePackageInner.F10 //@ diag(`could remove embedded field "unexportedSamePackageInner" from selector`)
_ = unexportedSame.F10 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelector/CheckExplicitEmbeddedSelector-unexported.go.golden
================================================
package pkg
type UnexportedSamePackageOuter struct {
unexportedSamePackageInner
}
type unexportedSamePackageInner struct {
F10 int
}
func fnUnexported() {
var unexportedSame UnexportedSamePackageOuter
_ = unexportedSame.F10 //@ diag(`could remove embedded field "unexportedSamePackageInner" from selector`)
_ = unexportedSame.F10 // minimal form
}
================================================
FILE: quickfix/qf1008/testdata/go1.0/CheckExplicitEmbeddedSelectorassist/assist.go
================================================
package assist
type T1 struct {
T2
}
type T2 struct {
F int
}
var V T1
================================================
FILE: quickfix/qf1009/qf1009.go
================================================
package qf1009
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1009",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use \'time.Time.Equal\' instead of \'==\' operator`,
Since: "2021.1",
Severity: lint.SeverityInfo,
},
})
var Analyzer = SCAnalyzer.Analyzer
var timeEqualR = pattern.MustParse(`(CallExpr (SelectorExpr lhs (Ident "Equal")) rhs)`)
func run(pass *analysis.Pass) (any, error) {
// FIXME(dh): create proper suggested fix for renamed import
fn := func(node ast.Node) {
expr := node.(*ast.BinaryExpr)
if expr.Op != token.EQL {
return
}
if !code.IsOfTypeWithName(pass, expr.X, "time.Time") || !code.IsOfTypeWithName(pass, expr.Y, "time.Time") {
return
}
report.Report(pass, node, "probably want to use time.Time.Equal instead",
report.Fixes(edit.Fix("Use time.Time.Equal method",
edit.ReplaceWithPattern(pass.Fset, node, timeEqualR, pattern.State{"lhs": expr.X, "rhs": expr.Y}))))
}
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
return nil, nil
}
================================================
FILE: quickfix/qf1009/qf1009_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1009
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1009/testdata/go1.0/CheckTimeEquality/CheckTimeEquality.go
================================================
package pkg
import "time"
func foo() time.Time { return time.Time{} }
func bar() time.Time { return time.Time{} }
func fn() {
var t1, t2 time.Time
if t1 == t2 { //@ diag(`probably want to use time.Time.Equal instead`)
}
if foo() == bar() { //@ diag(`probably want to use time.Time.Equal instead`)
}
}
================================================
FILE: quickfix/qf1009/testdata/go1.0/CheckTimeEquality/CheckTimeEquality.go.golden
================================================
package pkg
import "time"
func foo() time.Time { return time.Time{} }
func bar() time.Time { return time.Time{} }
func fn() {
var t1, t2 time.Time
if t1.Equal(t2) { //@ diag(`probably want to use time.Time.Equal instead`)
}
if foo().Equal(bar()) { //@ diag(`probably want to use time.Time.Equal instead`)
}
}
================================================
FILE: quickfix/qf1010/qf1010.go
================================================
package qf1010
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1010",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: "Convert slice of bytes to string when printing it",
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var byteSlicePrintingQ = pattern.MustParse(`
(Or
(CallExpr
(Symbol (Or
"fmt.Print"
"fmt.Println"
"fmt.Sprint"
"fmt.Sprintln"
"log.Fatal"
"log.Fatalln"
"log.Panic"
"log.Panicln"
"log.Print"
"log.Println"
"(*log.Logger).Fatal"
"(*log.Logger).Fatalln"
"(*log.Logger).Panic"
"(*log.Logger).Panicln"
"(*log.Logger).Print"
"(*log.Logger).Println")) args)
(CallExpr (Symbol (Or
"fmt.Fprint"
"fmt.Fprintln")) _:args))`)
var byteSlicePrintingR = pattern.MustParse(`(CallExpr (Ident "string") [arg])`)
func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, byteSlicePrintingQ) {
args := m.State["args"].([]ast.Expr)
for _, arg := range args {
if !code.IsOfStringConvertibleByteSlice(pass, arg) {
continue
}
if types.Implements(pass.TypesInfo.TypeOf(arg), knowledge.Interfaces["fmt.Stringer"]) {
continue
}
fix := edit.Fix("Convert argument to string", edit.ReplaceWithPattern(pass.Fset, arg, byteSlicePrintingR, pattern.State{"arg": arg}))
report.Report(pass, arg, "could convert argument to string", report.Fixes(fix))
}
}
return nil, nil
}
================================================
FILE: quickfix/qf1010/qf1010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1010/testdata/go1.0/CheckByteSlicePrinting/CheckByteSlicePrinting.go
================================================
package pkg
import "fmt"
type Stringable []byte
func (*Stringable) String() string { return "" }
func fn() {
type ByteSlice []byte
var b1 []byte
var b2 ByteSlice
var b3 *Stringable
var b4 Stringable
var s string
fmt.Print(1, b1, 2, []byte(""), b2, s) //@ diag(`could convert argument to string`), diag(`could convert argument to string`), diag(`could convert argument to string`)
fmt.Fprint(nil, 1, b1, 2, []byte(""), b2, s) //@ diag(`could convert argument to string`), diag(`could convert argument to string`), diag(`could convert argument to string`)
fmt.Print()
fmt.Fprint(nil)
fmt.Println(b3)
fmt.Println(b4) //@ diag(`could convert argument to string`)
}
================================================
FILE: quickfix/qf1010/testdata/go1.0/CheckByteSlicePrinting/CheckByteSlicePrinting.go.golden
================================================
package pkg
import "fmt"
type Stringable []byte
func (*Stringable) String() string { return "" }
func fn() {
type ByteSlice []byte
var b1 []byte
var b2 ByteSlice
var b3 *Stringable
var b4 Stringable
var s string
fmt.Print(1, string(b1), 2, string([]byte("")), string(b2), s) //@ diag(`could convert argument to string`), diag(`could convert argument to string`), diag(`could convert argument to string`)
fmt.Fprint(nil, 1, string(b1), 2, string([]byte("")), string(b2), s) //@ diag(`could convert argument to string`), diag(`could convert argument to string`), diag(`could convert argument to string`)
fmt.Print()
fmt.Fprint(nil)
fmt.Println(b3)
fmt.Println(string(b4)) //@ diag(`could convert argument to string`)
}
================================================
FILE: quickfix/qf1010/testdata/go1.9/CheckByteSlicePrinting/CheckByteSlicePrinting.go
================================================
package pkg
import "fmt"
func fn() {
type ByteSlice []byte
type Alias1 = []byte
type Alias2 = ByteSlice
type Alias3 = byte
type Alias4 = []Alias3
fmt.Print(Alias1{}) //@ diag(`could convert argument to string`)
fmt.Print(Alias2{}) //@ diag(`could convert argument to string`)
fmt.Print(Alias4{}) //@ diag(`could convert argument to string`)
fmt.Print()
fmt.Fprint(nil)
}
================================================
FILE: quickfix/qf1010/testdata/go1.9/CheckByteSlicePrinting/CheckByteSlicePrinting.go.golden
================================================
package pkg
import "fmt"
func fn() {
type ByteSlice []byte
type Alias1 = []byte
type Alias2 = ByteSlice
type Alias3 = byte
type Alias4 = []Alias3
fmt.Print(string(Alias1{})) //@ diag(`could convert argument to string`)
fmt.Print(string(Alias2{})) //@ diag(`could convert argument to string`)
fmt.Print(string(Alias4{})) //@ diag(`could convert argument to string`)
fmt.Print()
fmt.Fprint(nil)
}
================================================
FILE: quickfix/qf1011/qf1011.go
================================================
package qf1011
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/sharedcheck"
)
func init() {
SCAnalyzer.Analyzer.Name = "QF1011"
}
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: sharedcheck.RedundantTypeInDeclarationChecker("could", true),
Doc: &lint.RawDocumentation{
Title: "Omit redundant type from variable declaration",
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
================================================
FILE: quickfix/qf1011/qf1011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1011/testdata/go1.0/CheckRedundantTypeInDeclaration/CheckRedundantTypeInDeclaration.go
================================================
package pkg
import (
"io"
"math"
)
type MyInt int
const X int = 1
const Y = 1
func gen1() int { return 0 }
func gen2() io.ReadCloser { return nil }
func gen3() MyInt { return 0 }
// don't flag global variables
var a int = gen1()
func fn() {
var _ int = gen1() //@ diag(`could omit type int`)
var a int = Y //@ diag(`could omit type int`)
var b int = 1 //@ diag(`could omit type int`)
var c int = 1.0 // different default type
var d MyInt = 1 // different default type
var e io.ReadCloser = gen2() //@ diag(`could omit type io.ReadCloser`)
var f io.Reader = gen2() // different interface type
var g float64 = math.Pi //@ diag(`could omit type float64`)
var h bool = true //@ diag(`could omit type bool`)
var i string = "" //@ diag(`could omit type string`)
var j MyInt = gen3() //@ diag(`could omit type MyInt`)
var k uint8 = Y // different default type on constant
var l uint8 = (Y + Y) / 2 // different default type on rhs
var m int = (Y + Y) / 2 //@ diag(`could omit type int`)
_, _, _, _, _, _, _, _, _, _, _, _, _ = a, b, c, d, e, f, g, h, i, j, k, l, m
}
================================================
FILE: quickfix/qf1011/testdata/go1.0/CheckRedundantTypeInDeclaration/CheckRedundantTypeInDeclaration.go.golden
================================================
package pkg
import (
"io"
"math"
)
type MyInt int
const X int = 1
const Y = 1
func gen1() int { return 0 }
func gen2() io.ReadCloser { return nil }
func gen3() MyInt { return 0 }
// don't flag global variables
var a int = gen1()
func fn() {
var _ = gen1() //@ diag(`could omit type int`)
var a = Y //@ diag(`could omit type int`)
var b = 1 //@ diag(`could omit type int`)
var c int = 1.0 // different default type
var d MyInt = 1 // different default type
var e = gen2() //@ diag(`could omit type io.ReadCloser`)
var f io.Reader = gen2() // different interface type
var g = math.Pi //@ diag(`could omit type float64`)
var h = true //@ diag(`could omit type bool`)
var i = "" //@ diag(`could omit type string`)
var j = gen3() //@ diag(`could omit type MyInt`)
var k uint8 = Y // different default type on constant
var l uint8 = (Y + Y) / 2 // different default type on rhs
var m = (Y + Y) / 2 //@ diag(`could omit type int`)
_, _, _, _, _, _, _, _, _, _, _, _, _ = a, b, c, d, e, f, g, h, i, j, k, l, m
}
================================================
FILE: quickfix/qf1011/testdata/go1.9/CheckRedundantTypeInDeclaration/README
================================================
The cgo test case needs Go 1.9 because modern cgo generates code that
uses aliases.
================================================
FILE: quickfix/qf1011/testdata/go1.9/CheckRedundantTypeInDeclaration/cgo.go
================================================
package pkg
// #include
import "C"
import "unsafe"
func fnCgo(arg C.size_t) {
var ptr unsafe.Pointer
C.realloc(ptr, arg)
}
================================================
FILE: quickfix/qf1011/testdata/go1.9/CheckRedundantTypeInDeclaration/cgo.golden
================================================
package pkg
// #include
import "C"
import "unsafe"
func fnCgo(arg C.size_t) {
var ptr unsafe.Pointer
C.realloc(ptr, arg)
}
================================================
FILE: quickfix/qf1012/qf1012.go
================================================
package qf1012
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1012",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Use \'fmt.Fprintf(x, ...)\' instead of \'x.Write(fmt.Sprintf(...))\'`,
Since: "2022.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkWriteBytesSprintfQ = pattern.MustParse(`
(CallExpr
(SelectorExpr recv (Ident "Write"))
(CallExpr (ArrayType nil (Ident "byte"))
(CallExpr
fn@(Or
(Symbol "fmt.Sprint")
(Symbol "fmt.Sprintf")
(Symbol "fmt.Sprintln"))
args)
))`)
checkWriteStringSprintfQ = pattern.MustParse(`
(CallExpr
(SelectorExpr recv (Ident "WriteString"))
(CallExpr
fn@(Or
(Symbol "fmt.Sprint")
(Symbol "fmt.Sprintf")
(Symbol "fmt.Sprintln"))
args))`)
)
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
getRecv := func(m *pattern.Matcher) (ast.Expr, types.Type) {
recv := m.State["recv"].(ast.Expr)
recvT := pass.TypesInfo.TypeOf(recv)
// Use *N, not N, for the interface check if N
// is a named non-interface type, since the pointer
// has a larger method set (https://staticcheck.dev/issues/1097).
// We assume the receiver expression is addressable
// since otherwise the code wouldn't compile.
if _, ok := types.Unalias(recvT).(*types.Named); ok && !types.IsInterface(recvT) {
recvT = types.NewPointer(recvT)
recv = &ast.UnaryExpr{Op: token.AND, X: recv}
}
return recv, recvT
}
if m, ok := code.Match(pass, checkWriteBytesSprintfQ, node); ok {
recv, recvT := getRecv(m)
if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) {
return
}
name := m.State["fn"].(*types.Func).Name()
newName := "F" + strings.TrimPrefix(name, "S")
msg := fmt.Sprintf("Use fmt.%s(...) instead of Write([]byte(fmt.%s(...)))", newName, name)
args := m.State["args"].([]ast.Expr)
fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent("fmt"),
Sel: ast.NewIdent(newName),
},
Args: append([]ast.Expr{recv}, args...),
}))
report.Report(pass, node, msg, report.Fixes(fix))
} else if m, ok := code.Match(pass, checkWriteStringSprintfQ, node); ok {
recv, recvT := getRecv(m)
if !types.Implements(recvT, knowledge.Interfaces["io.StringWriter"]) {
return
}
// The type needs to implement both StringWriter and Writer.
// If it doesn't implement Writer, then we cannot pass it to fmt.Fprint.
if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) {
return
}
name := m.State["fn"].(*types.Func).Name()
newName := "F" + strings.TrimPrefix(name, "S")
msg := fmt.Sprintf("Use fmt.%s(...) instead of WriteString(fmt.%s(...))", newName, name)
args := m.State["args"].([]ast.Expr)
fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent("fmt"),
Sel: ast.NewIdent(newName),
},
Args: append([]ast.Expr{recv}, args...),
}))
report.Report(pass, node, msg, report.Fixes(fix))
}
}
if !code.CouldMatchAny(pass, checkWriteBytesSprintfQ, checkWriteStringSprintfQ) {
return nil, nil
}
code.Preorder(pass, fn, (*ast.CallExpr)(nil))
return nil, nil
}
================================================
FILE: quickfix/qf1012/qf1012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package qf1012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: quickfix/qf1012/testdata/go1.0/CheckWriteBytesSprintf/CheckWriteBytesSprintf.go
================================================
package pkg
import (
"bytes"
"fmt"
"io"
)
type NotAWriter struct{}
func (NotAWriter) Write(b []byte) {}
func fn1() {
var w io.Writer
var w2 NotAWriter
w.Write([]byte(fmt.Sprint("abc", "de"))) //@ diag(`Use fmt.Fprint`)
w.Write([]byte(fmt.Sprintf("%T", w))) //@ diag(`Use fmt.Fprintf`)
w.Write([]byte(fmt.Sprintln("abc", "de"))) //@ diag(`Use fmt.Fprintln`)
w2.Write([]byte(fmt.Sprint("abc", "de")))
}
func fn2() {
buf := new(bytes.Buffer)
var sw io.StringWriter
buf.WriteString(fmt.Sprint("abc", "de")) //@ diag(`Use fmt.Fprint`)
buf.WriteString(fmt.Sprintf("%T", 0)) //@ diag(`Use fmt.Fprintf`)
buf.WriteString(fmt.Sprintln("abc", "de")) //@ diag(`Use fmt.Fprintln`)
// We can't suggest fmt.Fprint here. We don't know if sw implements io.Writer.
sw.WriteString(fmt.Sprint("abc", "de"))
sw.WriteString(fmt.Sprintf("%T", 0))
sw.WriteString(fmt.Sprintln("abc", "de"))
}
func fn3() {
var buf bytes.Buffer
buf.WriteString(fmt.Sprint("abc", "de")) //@ diag(`Use fmt.Fprint`)
}
================================================
FILE: quickfix/qf1012/testdata/go1.0/CheckWriteBytesSprintf/CheckWriteBytesSprintf.go.golden
================================================
package pkg
import (
"bytes"
"fmt"
"io"
)
type NotAWriter struct{}
func (NotAWriter) Write(b []byte) {}
func fn1() {
var w io.Writer
var w2 NotAWriter
fmt.Fprint(w, "abc", "de") //@ diag(`Use fmt.Fprint`)
fmt.Fprintf(w, "%T", w) //@ diag(`Use fmt.Fprintf`)
fmt.Fprintln(w, "abc", "de") //@ diag(`Use fmt.Fprintln`)
w2.Write([]byte(fmt.Sprint("abc", "de")))
}
func fn2() {
buf := new(bytes.Buffer)
var sw io.StringWriter
fmt.Fprint(buf, "abc", "de") //@ diag(`Use fmt.Fprint`)
fmt.Fprintf(buf, "%T", 0) //@ diag(`Use fmt.Fprintf`)
fmt.Fprintln(buf, "abc", "de") //@ diag(`Use fmt.Fprintln`)
// We can't suggest fmt.Fprint here. We don't know if sw implements io.Writer.
sw.WriteString(fmt.Sprint("abc", "de"))
sw.WriteString(fmt.Sprintf("%T", 0))
sw.WriteString(fmt.Sprintln("abc", "de"))
}
func fn3() {
var buf bytes.Buffer
fmt.Fprint(&buf, "abc", "de") //@ diag(`Use fmt.Fprint`)
}
================================================
FILE: sarif/sarif.go
================================================
package sarif
const Version = "2.1.0"
const Schema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
type Log struct {
Version string `json:"version"`
Schema string `json:"$schema"`
Runs []Run `json:"runs"`
}
type Run struct {
Tool Tool `json:"tool"`
Results []Result `json:"results,omitempty"`
Invocations []Invocation `json:"invocations,omitempty"`
Artifacts []Artifact `json:"artifacts,omitempty"`
}
type Artifact struct {
Location ArtifactLocation `json:"location"`
Length int `json:"length"`
SourceLanguage string `json:"sourceLanguage"`
Roles []string `json:"roles"`
Encoding string `json:"encoding"`
}
const (
AnalysisTarget = "analysisTarget"
UTF8 = "UTF-8"
Fail = "fail"
Warning = "warning"
Error = "error"
Note = "note"
None = "none"
)
type Hash struct {
Sha256 string `json:"sha-256"`
}
type Tool struct {
Driver ToolComponent `json:"driver"`
}
type Invocation struct {
CommandLine string `json:"commandLine,omitempty"`
Arguments []string `json:"arguments,omitempty"`
WorkingDirectory ArtifactLocation `json:"workingDirectory,omitzero"`
ExecutionSuccessful bool `json:"executionSuccessful"`
}
type ToolComponent struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
SemanticVersion string `json:"semanticVersion,omitempty"`
InformationURI string `json:"informationUri,omitempty"`
Rules []ReportingDescriptor `json:"rules,omitempty"`
}
type ReportingDescriptor struct {
ID string `json:"id"`
ShortDescription Message `json:"shortDescription"`
// FullDescription Message `json:"fullDescription"`
Help Message `json:"help"`
HelpURI string `json:"helpUri,omitempty"`
DefaultConfiguration ReportingConfiguration `json:"defaultConfiguration"`
}
type ReportingConfiguration struct {
Enabled bool `json:"enabled"`
Level string `json:"level,omitempty"`
Parameters map[string]any `json:"parameters,omitempty"`
}
type Result struct {
RuleID string `json:"ruleId"`
// RuleIndex int `json:"ruleIndex"`
Kind string `json:"kind"`
Level string `json:"level,omitempty"`
Message Message `json:"message"`
Locations []Location `json:"locations,omitempty"`
RelatedLocations []Location `json:"relatedLocations,omitempty"`
Fixes []Fix `json:"fixes,omitempty"`
Suppressions []Suppression `json:"suppressions"`
}
type Suppression struct {
Kind string `json:"kind"`
Justification string `json:"justification"`
}
type Fix struct {
Description Message `json:"description"`
ArtifactChanges []ArtifactChange `json:"artifactChanges"`
}
type ArtifactChange struct {
ArtifactLocation ArtifactLocation `json:"artifactLocation"`
Replacements []Replacement `json:"replacements"`
}
type Replacement struct {
DeletedRegion Region `json:"deletedRegion"`
InsertedContent ArtifactContent `json:"insertedContent"`
}
type ArtifactContent struct {
Text string `json:"text"`
}
type Message struct {
Text string `json:"text,omitempty"`
Markdown string `json:"markdown,omitempty"`
}
type Location struct {
ID int `json:"id,omitempty"`
Message *Message `json:"message,omitempty"`
PhysicalLocation PhysicalLocation `json:"physicalLocation"`
}
type PhysicalLocation struct {
ArtifactLocation ArtifactLocation `json:"artifactLocation"`
Region Region `json:"region"`
}
type ArtifactLocation struct {
URI string `json:"uri,omitempty"`
Index int `json:"index,omitempty"`
URIBaseID string `json:"uriBaseId,omitempty"`
}
type Region struct {
StartLine int `json:"startLine"`
StartColumn int `json:"startColumn"`
EndLine int `json:"endLine,omitempty"`
EndColumn int `json:"endColumn,omitempty"`
}
================================================
FILE: simple/analysis.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package simple
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/simple/s1000"
"honnef.co/go/tools/simple/s1001"
"honnef.co/go/tools/simple/s1002"
"honnef.co/go/tools/simple/s1003"
"honnef.co/go/tools/simple/s1004"
"honnef.co/go/tools/simple/s1005"
"honnef.co/go/tools/simple/s1006"
"honnef.co/go/tools/simple/s1007"
"honnef.co/go/tools/simple/s1008"
"honnef.co/go/tools/simple/s1009"
"honnef.co/go/tools/simple/s1010"
"honnef.co/go/tools/simple/s1011"
"honnef.co/go/tools/simple/s1012"
"honnef.co/go/tools/simple/s1016"
"honnef.co/go/tools/simple/s1017"
"honnef.co/go/tools/simple/s1018"
"honnef.co/go/tools/simple/s1019"
"honnef.co/go/tools/simple/s1020"
"honnef.co/go/tools/simple/s1021"
"honnef.co/go/tools/simple/s1023"
"honnef.co/go/tools/simple/s1024"
"honnef.co/go/tools/simple/s1025"
"honnef.co/go/tools/simple/s1028"
"honnef.co/go/tools/simple/s1029"
"honnef.co/go/tools/simple/s1030"
"honnef.co/go/tools/simple/s1031"
"honnef.co/go/tools/simple/s1032"
"honnef.co/go/tools/simple/s1033"
"honnef.co/go/tools/simple/s1034"
"honnef.co/go/tools/simple/s1035"
"honnef.co/go/tools/simple/s1036"
"honnef.co/go/tools/simple/s1037"
"honnef.co/go/tools/simple/s1038"
"honnef.co/go/tools/simple/s1039"
"honnef.co/go/tools/simple/s1040"
)
var Analyzers = []*lint.Analyzer{
s1000.SCAnalyzer,
s1001.SCAnalyzer,
s1002.SCAnalyzer,
s1003.SCAnalyzer,
s1004.SCAnalyzer,
s1005.SCAnalyzer,
s1006.SCAnalyzer,
s1007.SCAnalyzer,
s1008.SCAnalyzer,
s1009.SCAnalyzer,
s1010.SCAnalyzer,
s1011.SCAnalyzer,
s1012.SCAnalyzer,
s1016.SCAnalyzer,
s1017.SCAnalyzer,
s1018.SCAnalyzer,
s1019.SCAnalyzer,
s1020.SCAnalyzer,
s1021.SCAnalyzer,
s1023.SCAnalyzer,
s1024.SCAnalyzer,
s1025.SCAnalyzer,
s1028.SCAnalyzer,
s1029.SCAnalyzer,
s1030.SCAnalyzer,
s1031.SCAnalyzer,
s1032.SCAnalyzer,
s1033.SCAnalyzer,
s1034.SCAnalyzer,
s1035.SCAnalyzer,
s1036.SCAnalyzer,
s1037.SCAnalyzer,
s1038.SCAnalyzer,
s1039.SCAnalyzer,
s1040.SCAnalyzer,
}
================================================
FILE: simple/doc.go
================================================
//go:generate go run ../generate.go
// Package simple contains analyzes that simplify code.
// All suggestions made by these analyzes are intended to result in objectively simpler code,
// and following their advice is recommended.
package simple
================================================
FILE: simple/s1000/s1000.go
================================================
package s1000
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1000",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use plain channel send or receive instead of single-case select`,
Text: `Select statements with a single case can be replaced with a simple
send or receive.`,
Before: `
select {
case x := <-ch:
fmt.Println(x)
}`,
After: `
x := <-ch
fmt.Println(x)
`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkSingleCaseSelectQ1 = pattern.MustParse(`
(ForStmt
nil nil nil
select@(SelectStmt
(CommClause
(Or
(UnaryExpr "<-" _)
(AssignStmt _ _ (UnaryExpr "<-" _)))
_)))`)
checkSingleCaseSelectQ2 = pattern.MustParse(`(SelectStmt (CommClause _ _))`)
)
func run(pass *analysis.Pass) (any, error) {
seen := map[ast.Node]struct{}{}
fn := func(node ast.Node) {
if m, ok := code.Match(pass, checkSingleCaseSelectQ1, node); ok {
seen[m.State["select"].(ast.Node)] = struct{}{}
report.Report(pass, node, "should use for range instead of for { select {} }", report.FilterGenerated())
} else if _, ok := code.Match(pass, checkSingleCaseSelectQ2, node); ok {
if _, ok := seen[node]; !ok {
report.Report(pass, node, "should use a simple channel send/receive instead of select with a single case",
report.ShortRange(),
report.FilterGenerated())
}
}
}
code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.SelectStmt)(nil))
return nil, nil
}
================================================
FILE: simple/s1000/s1000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1000/testdata/go1.0/CheckSingleCaseSelect/single-case-select.go
================================================
package pkg
func fn() {
var ch chan int
select { //@ diag(`should use a simple channel send`)
case <-ch:
}
outer:
for { //@ diag(`should use for range`)
select {
case <-ch:
break outer
}
}
for { //@ diag(`should use for range`)
select {
case x := <-ch:
_ = x
}
}
for {
select { //@ diag(`should use a simple channel send`)
case ch <- 0:
}
}
}
================================================
FILE: simple/s1001/s1001.go
================================================
package s1001
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1001",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Replace for loop with call to copy`,
Text: `
Use \'copy()\' for copying elements from one slice to another. For
arrays of identical size, you can use simple assignment.`,
Before: `
for i, x := range src {
dst[i] = x
}`,
After: `copy(dst, src)`,
Since: "2017.1",
// MergeIfAll because the types of src and dst might be different under different build tags.
// You shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkLoopCopyQ = pattern.MustParse(`
(Or
(RangeStmt
key@(Ident _) value@(Ident _) ":=" src
[(AssignStmt (IndexExpr dst key) "=" value)])
(RangeStmt
key@(Ident _) nil ":=" src
[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))])
(ForStmt
(AssignStmt key@(Ident _) ":=" (IntegerLiteral "0"))
(BinaryExpr key "<" (CallExpr (Symbol "len") [src]))
(IncDecStmt key "++")
[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]))`)
)
func run(pass *analysis.Pass) (any, error) {
// TODO revisit once range doesn't require a structural type
isInvariant := func(k, v types.Object, node ast.Expr) bool {
if code.MayHaveSideEffects(pass, node, nil) {
return false
}
invariant := true
ast.Inspect(node, func(node ast.Node) bool {
if node, ok := node.(*ast.Ident); ok {
obj := pass.TypesInfo.ObjectOf(node)
if obj == k || obj == v {
// don't allow loop bodies like 'a[i][i] = v'
invariant = false
return false
}
}
return true
})
return invariant
}
var elType func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool)
elType = func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) {
switch typ := T.Underlying().(type) {
case *types.Slice:
return typ.Elem(), false, false, true
case *types.Array:
return typ.Elem(), true, false, true
case *types.Pointer:
el, isArray, _, ok = elType(typ.Elem())
return el, isArray, true, ok
default:
return nil, false, false, false
}
}
for node, m := range code.Matches(pass, checkLoopCopyQ) {
src := m.State["src"].(ast.Expr)
dst := m.State["dst"].(ast.Expr)
k := pass.TypesInfo.ObjectOf(m.State["key"].(*ast.Ident))
var v types.Object
if value, ok := m.State["value"]; ok {
v = pass.TypesInfo.ObjectOf(value.(*ast.Ident))
}
if !isInvariant(k, v, dst) {
continue
}
if !isInvariant(k, v, src) {
// For example: 'for i := range foo()'
continue
}
Tsrc := pass.TypesInfo.TypeOf(src)
Tdst := pass.TypesInfo.TypeOf(dst)
TsrcElem, TsrcArray, TsrcPointer, ok := elType(Tsrc)
if !ok {
continue
}
if TsrcPointer {
Tsrc = Tsrc.Underlying().(*types.Pointer).Elem()
}
TdstElem, TdstArray, TdstPointer, ok := elType(Tdst)
if !ok {
continue
}
if TdstPointer {
Tdst = Tdst.Underlying().(*types.Pointer).Elem()
}
if !types.Identical(TsrcElem, TdstElem) {
continue
}
if TsrcArray && TdstArray && types.Identical(Tsrc, Tdst) {
if TsrcPointer {
src = &ast.StarExpr{
X: src,
}
}
if TdstPointer {
dst = &ast.StarExpr{
X: dst,
}
}
r := &ast.AssignStmt{
Lhs: []ast.Expr{dst},
Rhs: []ast.Expr{src},
Tok: token.ASSIGN,
}
report.Report(pass, node, "should copy arrays using assignment instead of using a loop",
report.FilterGenerated(),
report.ShortRange(),
report.Fixes(edit.Fix("Replace loop with assignment", edit.ReplaceWithNode(pass.Fset, node, r))))
} else {
tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
if err == nil && tv.IsBuiltin() {
to := "to"
from := "from"
src := m.State["src"].(ast.Expr)
if TsrcArray {
from = "from[:]"
src = &ast.SliceExpr{
X: src,
}
}
dst := m.State["dst"].(ast.Expr)
if TdstArray {
to = "to[:]"
dst = &ast.SliceExpr{
X: dst,
}
}
r := &ast.CallExpr{
Fun: &ast.Ident{Name: "copy"},
Args: []ast.Expr{dst, src},
}
opts := []report.Option{
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace loop with call to copy()", edit.ReplaceWithNode(pass.Fset, node, r))),
}
report.Report(pass, node, fmt.Sprintf("should use copy(%s, %s) instead of a loop", to, from), opts...)
}
}
}
return nil, nil
}
================================================
FILE: simple/s1001/s1001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1001/testdata/go1.0/CheckLoopCopy/copy.go
================================================
package pkg
func fn() {
var b1, b2 []byte
for i, v := range b1 { //@ diag(`should use copy(to, from)`)
b2[i] = v
}
for i := range b1 { //@ diag(`should use copy`)
b2[i] = b1[i]
}
type T [][16]byte
var a T
b := make([]interface{}, len(a))
for i := range b {
b[i] = a[i]
}
var b3, b4 []*byte
for i := range b3 { //@ diag(`should use copy`)
b4[i] = b3[i]
}
var m map[int]byte
for i, v := range b1 {
m[i] = v
}
}
func src() []interface{} { return nil }
func fn1() {
// Don't flag this, the source is dynamic
var dst []interface{}
for i := range src() {
dst[i] = src()[i]
}
}
func fn2() {
type T struct {
b []byte
}
var src []byte
var dst T
for i, v := range src { //@ diag(`should use copy`)
dst.b[i] = v
}
}
func fn3() {
var src []byte
var dst [][]byte
for i, v := range src { //@ diag(`should use copy`)
dst[0][i] = v
}
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[i][i] = v
}
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[v][i] = v
}
}
func fn4() {
var b []byte
var a1 [5]byte
var a2 [10]byte
var a3 [5]byte
for i := range b { //@ diag(`should use copy(to[:], from)`)
a1[i] = b[i]
}
for i := range a1 { //@ diag(`should use copy(to, from[:])`)
b[i] = a1[i]
}
for i := range a1 { //@ diag(`should use copy(to[:], from[:])`)
a2[i] = a1[i]
}
for i := range a1 { //@ diag(`should copy arrays using assignment`)
a3[i] = a1[i]
}
a1p := &a1
a2p := &a2
a3p := &a3
for i := range b { //@ diag(`should use copy`)
a1p[i] = b[i]
}
for i := range a1p { //@ diag(`should use copy`)
b[i] = a1p[i]
}
for i := range a1p { //@ diag(`should use copy`)
a2p[i] = a1p[i]
}
for i := range a1p { //@ diag(`should copy arrays using assignment`)
a3p[i] = a1p[i]
}
for i := range a1 { //@ diag(`should use copy`)
a2p[i] = a1[i]
}
for i := range a1 { //@ diag(`should copy arrays using assignment`)
a3p[i] = a1[i]
}
for i := range a1p { //@ diag(`should use copy`)
a2[i] = a1p[i]
}
for i := range a1p { //@ diag(`should copy arrays using assignment`)
a3[i] = a1p[i]
}
}
func fn5() {
var src, dst []byte
for i := 0; i < len(src); i++ { //@ diag(`should use copy`)
dst[i] = src[i]
}
len := func([]byte) int { return 0 }
for i := 0; i < len(src); i++ {
dst[i] = src[i]
}
}
func fn6() {
var src, dst []byte
copy := func() {}
_ = copy
for i, v := range src {
dst[i] = v
}
}
================================================
FILE: simple/s1001/testdata/go1.0/CheckLoopCopy/copy.go.golden
================================================
package pkg
func fn() {
var b1, b2 []byte
copy(b2, b1)
copy(b2, b1)
type T [][16]byte
var a T
b := make([]interface{}, len(a))
for i := range b {
b[i] = a[i]
}
var b3, b4 []*byte
copy(b4, b3)
var m map[int]byte
for i, v := range b1 {
m[i] = v
}
}
func src() []interface{} { return nil }
func fn1() {
// Don't flag this, the source is dynamic
var dst []interface{}
for i := range src() {
dst[i] = src()[i]
}
}
func fn2() {
type T struct {
b []byte
}
var src []byte
var dst T
copy(dst.b, src)
}
func fn3() {
var src []byte
var dst [][]byte
copy(dst[0], src)
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[i][i] = v
}
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[v][i] = v
}
}
func fn4() {
var b []byte
var a1 [5]byte
var a2 [10]byte
var a3 [5]byte
copy(a1[:], b)
copy(b, a1[:])
copy(a2[:], a1[:])
a3 = a1
a1p := &a1
a2p := &a2
a3p := &a3
copy(a1p[:], b)
copy(b, a1p[:])
copy(a2p[:], a1p[:])
*a3p = *a1p
copy(a2p[:], a1[:])
*a3p = a1
copy(a2[:], a1p[:])
a3 = *a1p
}
func fn5() {
var src, dst []byte
copy(dst, src)
len := func([]byte) int { return 0 }
for i := 0; i < len(src); i++ {
dst[i] = src[i]
}
}
func fn6() {
var src, dst []byte
copy := func() {}
_ = copy
for i, v := range src {
dst[i] = v
}
}
================================================
FILE: simple/s1001/testdata/go1.18/CheckLoopCopy/copy_generics.go
================================================
package pkg
func tpfn[T any]() {
var b1, b2 []T
for i, v := range b1 { //@ diag(`should use copy`)
b2[i] = v
}
for i := range b1 { //@ diag(`should use copy`)
b2[i] = b1[i]
}
type T2 [][16]T
var a T2
b := make([]any, len(a))
for i := range b {
b[i] = a[i]
}
var b3, b4 []*T
for i := range b3 { //@ diag(`should use copy`)
b4[i] = b3[i]
}
var m map[int]T
for i, v := range b1 {
m[i] = v
}
}
func tpsrc[T any]() []T { return nil }
func tpfn1() {
// Don't flag this, the source is dynamic
var dst []any
for i := range tpsrc[any]() {
dst[i] = tpsrc[any]()[i]
}
}
func tpfn2[T any]() {
type T2 struct {
b []T
}
var src []T
var dst T2
for i, v := range src { //@ diag(`should use copy`)
dst.b[i] = v
}
}
func tpfn3[T any]() {
var src []T
var dst [][]T
for i, v := range src { //@ diag(`should use copy`)
dst[0][i] = v
}
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[i][i] = v
}
}
func tpfn4[T any]() {
var b []T
var a1 [5]T
var a2 [10]T
var a3 [5]T
for i := range b { //@ diag(`should use copy`)
a1[i] = b[i]
}
for i := range a1 { //@ diag(`should use copy`)
b[i] = a1[i]
}
for i := range a1 { //@ diag(`should use copy`)
a2[i] = a1[i]
}
for i := range a1 { //@ diag(`should copy arrays using assignment`)
a3[i] = a1[i]
}
a1p := &a1
a2p := &a2
a3p := &a3
for i := range b { //@ diag(`should use copy`)
a1p[i] = b[i]
}
for i := range a1p { //@ diag(`should use copy`)
b[i] = a1p[i]
}
for i := range a1p { //@ diag(`should use copy`)
a2p[i] = a1p[i]
}
for i := range a1p { //@ diag(`should copy arrays using assignment`)
a3p[i] = a1p[i]
}
for i := range a1 { //@ diag(`should use copy`)
a2p[i] = a1[i]
}
for i := range a1 { //@ diag(`should copy arrays using assignment`)
a3p[i] = a1[i]
}
for i := range a1p { //@ diag(`should use copy`)
a2[i] = a1p[i]
}
for i := range a1p { //@ diag(`should copy arrays using assignment`)
a3[i] = a1p[i]
}
}
func tpfn5[T any]() {
var src, dst []T
for i := 0; i < len(src); i++ { //@ diag(`should use copy`)
dst[i] = src[i]
}
len := func([]T) int { return 0 }
for i := 0; i < len(src); i++ {
dst[i] = src[i]
}
}
================================================
FILE: simple/s1001/testdata/go1.18/CheckLoopCopy/copy_generics.go.golden
================================================
package pkg
func tpfn[T any]() {
var b1, b2 []T
copy(b2, b1)
copy(b2, b1)
type T2 [][16]T
var a T2
b := make([]any, len(a))
for i := range b {
b[i] = a[i]
}
var b3, b4 []*T
copy(b4, b3)
var m map[int]T
for i, v := range b1 {
m[i] = v
}
}
func tpsrc[T any]() []T { return nil }
func tpfn1() {
// Don't flag this, the source is dynamic
var dst []any
for i := range tpsrc[any]() {
dst[i] = tpsrc[any]()[i]
}
}
func tpfn2[T any]() {
type T2 struct {
b []T
}
var src []T
var dst T2
copy(dst.b, src)
}
func tpfn3[T any]() {
var src []T
var dst [][]T
copy(dst[0], src)
for i, v := range src {
// Don't flag, destination depends on loop variable
dst[i][i] = v
}
}
func tpfn4[T any]() {
var b []T
var a1 [5]T
var a2 [10]T
var a3 [5]T
copy(a1[:], b)
copy(b, a1[:])
copy(a2[:], a1[:])
a3 = a1
a1p := &a1
a2p := &a2
a3p := &a3
copy(a1p[:], b)
copy(b, a1p[:])
copy(a2p[:], a1p[:])
*a3p = *a1p
copy(a2p[:], a1[:])
*a3p = a1
copy(a2[:], a1p[:])
a3 = *a1p
}
func tpfn5[T any]() {
var src, dst []T
copy(dst, src)
len := func([]T) int { return 0 }
for i := 0; i < len(src); i++ {
dst[i] = src[i]
}
}
================================================
FILE: simple/s1002/s1002.go
================================================
package s1002
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1002",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Omit comparison with boolean constant`,
Before: `if x == true {}`,
After: `if x {}`,
Since: "2017.1",
// MergeIfAll because 'true' might not be the builtin constant under all build tags.
// You shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
if code.IsInTest(pass, node) {
return
}
expr := node.(*ast.BinaryExpr)
if expr.Op != token.EQL && expr.Op != token.NEQ {
return
}
x := code.IsBoolConst(pass, expr.X)
y := code.IsBoolConst(pass, expr.Y)
if !x && !y {
return
}
var other ast.Expr
var val bool
if x {
val = code.BoolConst(pass, expr.X)
other = expr.Y
} else {
val = code.BoolConst(pass, expr.Y)
other = expr.X
}
ok := typeutil.All(pass.TypesInfo.TypeOf(other), func(term *types.Term) bool {
basic, ok := term.Type().Underlying().(*types.Basic)
return ok && basic.Kind() == types.Bool
})
if !ok {
return
}
op := ""
if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) {
op = "!"
}
r := op + report.Render(pass, other)
l1 := len(r)
r = strings.TrimLeft(r, "!")
if (l1-len(r))%2 == 1 {
r = "!" + r
}
report.Report(pass, expr, fmt.Sprintf("should omit comparison to bool constant, can be simplified to %s", r),
report.FilterGenerated(),
report.Fixes(edit.Fix("Simplify bool comparison", edit.ReplaceWithString(expr, r))))
}
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
return nil, nil
}
================================================
FILE: simple/s1002/s1002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1002/testdata/go1.0/CheckIfBoolCmp/bool-cmp.go
================================================
package pkg
func fn1() bool { return false }
func fn2() bool { return false }
func fn() {
type T bool
var x T
const t T = false
if x == t {
}
if fn1() == true { //@ diag(`simplified to fn1()`)
}
if fn1() != true { //@ diag(`simplified to !fn1()`)
}
if fn1() == false { //@ diag(`simplified to !fn1()`)
}
if fn1() != false { //@ diag(`simplified to fn1()`)
}
if fn1() && (fn1() || fn1()) || (fn1() && fn1()) == true { //@ diag(`simplified to (fn1() && fn1())`)
}
if (fn1() && fn2()) == false { //@ diag(`simplified to !(fn1() && fn2())`)
}
var y bool
for y != true { //@ diag(`simplified to !y`)
}
if !y == true { //@ diag(`simplified to !y`)
}
if !y == false { //@ diag(`simplified to y`)
}
if !y != true { //@ diag(`simplified to y`)
}
if !y != false { //@ diag(`simplified to !y`)
}
if !!y == false { //@ diag(`simplified to !y`)
}
if !!!y == false { //@ diag(`simplified to y`)
}
if !!y == true { //@ diag(`simplified to y`)
}
if !!!y == true { //@ diag(`simplified to !y`)
}
if !!y != true { //@ diag(`simplified to !y`)
}
if !!!y != true { //@ diag(`simplified to y`)
}
if !y == !false { // not matched because we expect true/false on one side, not !false
}
var z interface{}
if z == true {
}
}
================================================
FILE: simple/s1002/testdata/go1.0/CheckIfBoolCmp/bool-cmp.go.golden
================================================
package pkg
func fn1() bool { return false }
func fn2() bool { return false }
func fn() {
type T bool
var x T
const t T = false
if x == t {
}
if fn1() { //@ diag(`simplified to fn1()`)
}
if !fn1() { //@ diag(`simplified to !fn1()`)
}
if !fn1() { //@ diag(`simplified to !fn1()`)
}
if fn1() { //@ diag(`simplified to fn1()`)
}
if fn1() && (fn1() || fn1()) || (fn1() && fn1()) { //@ diag(`simplified to (fn1() && fn1())`)
}
if !(fn1() && fn2()) { //@ diag(`simplified to !(fn1() && fn2())`)
}
var y bool
for !y { //@ diag(`simplified to !y`)
}
if !y { //@ diag(`simplified to !y`)
}
if y { //@ diag(`simplified to y`)
}
if y { //@ diag(`simplified to y`)
}
if !y { //@ diag(`simplified to !y`)
}
if !y { //@ diag(`simplified to !y`)
}
if y { //@ diag(`simplified to y`)
}
if y { //@ diag(`simplified to y`)
}
if !y { //@ diag(`simplified to !y`)
}
if !y { //@ diag(`simplified to !y`)
}
if y { //@ diag(`simplified to y`)
}
if !y == !false { // not matched because we expect true/false on one side, not !false
}
var z interface{}
if z == true {
}
}
================================================
FILE: simple/s1002/testdata/go1.0/CheckIfBoolCmp/bool-cmp_test.go
================================================
package pkg
import "testing"
func TestFoo(t *testing.T) {
if fn1() == true {
}
}
================================================
FILE: simple/s1002/testdata/go1.18/CheckIfBoolCmp/bool-cmp_generics.go
================================================
package pkg
func tpfn1[T any]() T { var zero T; return zero }
func tpfn() {
if tpfn1[bool]() == true { //@ diag(`simplified to tpfn1[bool]()`)
}
if tpfn1[any]() == true {
}
}
func tpfn2[T bool](x T) {
if x == true { //@ diag(`omit comparison to bool constant`)
}
}
func tpfn3[T ~bool](x T) {
if x == true { //@ diag(`omit comparison to bool constant`)
}
}
type MyBool bool
func tpfn4[T bool | MyBool](x T) {
if x == true { //@ diag(`omit comparison to bool constant`)
}
}
================================================
FILE: simple/s1002/testdata/go1.18/CheckIfBoolCmp/bool-cmp_generics.go.golden
================================================
package pkg
func tpfn1[T any]() T { var zero T; return zero }
func tpfn() {
if tpfn1[bool]() { //@ diag(`simplified to tpfn1[bool]()`)
}
if tpfn1[any]() == true {
}
}
func tpfn2[T bool](x T) {
if x { //@ diag(`omit comparison to bool constant`)
}
}
func tpfn3[T ~bool](x T) {
if x { //@ diag(`omit comparison to bool constant`)
}
}
type MyBool bool
func tpfn4[T bool | MyBool](x T) {
if x { //@ diag(`omit comparison to bool constant`)
}
}
================================================
FILE: simple/s1003/s1003.go
================================================
package s1003
import (
"fmt"
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1003",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Replace call to \'strings.Index\' with \'strings.Contains\'`,
Before: `if strings.Index(x, y) != -1 {}`,
After: `if strings.Contains(x, y) {}`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// map of value to token to bool value
allowed := map[int64]map[token.Token]bool{
-1: {token.GTR: true, token.NEQ: true, token.EQL: false},
0: {token.GEQ: true, token.LSS: false},
}
fn := func(node ast.Node) {
expr := node.(*ast.BinaryExpr)
switch expr.Op {
case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL:
default:
return
}
value, ok := code.ExprToInt(pass, expr.Y)
if !ok {
return
}
allowedOps, ok := allowed[value]
if !ok {
return
}
b, ok := allowedOps[expr.Op]
if !ok {
return
}
call, ok := expr.X.(*ast.CallExpr)
if !ok {
return
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return
}
pkgIdent, ok := sel.X.(*ast.Ident)
if !ok {
return
}
funIdent := sel.Sel
if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" {
return
}
var r ast.Expr
switch funIdent.Name {
case "IndexRune":
r = &ast.SelectorExpr{
X: pkgIdent,
Sel: &ast.Ident{Name: "ContainsRune"},
}
case "IndexAny":
r = &ast.SelectorExpr{
X: pkgIdent,
Sel: &ast.Ident{Name: "ContainsAny"},
}
case "Index":
r = &ast.SelectorExpr{
X: pkgIdent,
Sel: &ast.Ident{Name: "Contains"},
}
default:
return
}
r = &ast.CallExpr{
Fun: r,
Args: call.Args,
}
if !b {
r = &ast.UnaryExpr{
Op: token.NOT,
X: r,
}
}
report.Report(pass, node, fmt.Sprintf("should use %s instead", report.Render(pass, r)),
report.FilterGenerated(),
report.Fixes(edit.Fix(fmt.Sprintf("Simplify use of %s", report.Render(pass, call.Fun)), edit.ReplaceWithNode(pass.Fset, node, r))))
}
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
return nil, nil
}
================================================
FILE: simple/s1003/s1003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1003/testdata/go1.0/CheckStringsContains/contains.go
================================================
package pkg
import (
"bytes"
"strings"
)
func fn() {
_ = strings.IndexRune("", 'x') > -1 //@ diag(` strings.ContainsRune`)
_ = strings.IndexRune("", 'x') >= 0 //@ diag(` strings.ContainsRune`)
_ = strings.IndexRune("", 'x') > 0
_ = strings.IndexRune("", 'x') >= -1
_ = strings.IndexRune("", 'x') != -1 //@ diag(` strings.ContainsRune`)
_ = strings.IndexRune("", 'x') == -1 //@ diag(`!strings.ContainsRune`)
_ = strings.IndexRune("", 'x') != 0
_ = strings.IndexRune("", 'x') < 0 //@ diag(`!strings.ContainsRune`)
_ = strings.IndexAny("", "") > -1 //@ diag(` strings.ContainsAny`)
_ = strings.IndexAny("", "") >= 0 //@ diag(` strings.ContainsAny`)
_ = strings.IndexAny("", "") > 0
_ = strings.IndexAny("", "") >= -1
_ = strings.IndexAny("", "") != -1 //@ diag(` strings.ContainsAny`)
_ = strings.IndexAny("", "") == -1 //@ diag(`!strings.ContainsAny`)
_ = strings.IndexAny("", "") != 0
_ = strings.IndexAny("", "") < 0 //@ diag(`!strings.ContainsAny`)
_ = strings.Index("", "") > -1 //@ diag(` strings.Contains`)
_ = strings.Index("", "") >= 0 //@ diag(` strings.Contains`)
_ = strings.Index("", "") > 0
_ = strings.Index("", "") >= -1
_ = strings.Index("", "") != -1 //@ diag(` strings.Contains`)
_ = strings.Index("", "") == -1 //@ diag(`!strings.Contains`)
_ = strings.Index("", "") != 0
_ = strings.Index("", "") < 0 //@ diag(`!strings.Contains`)
_ = bytes.IndexRune(nil, 'x') > -1 //@ diag(` bytes.ContainsRune`)
_ = bytes.IndexAny(nil, "") > -1 //@ diag(` bytes.ContainsAny`)
_ = bytes.Index(nil, nil) > -1 //@ diag(` bytes.Contains`)
}
================================================
FILE: simple/s1003/testdata/go1.0/CheckStringsContains/contains.go.golden
================================================
package pkg
import (
"bytes"
"strings"
)
func fn() {
_ = strings.ContainsRune("", 'x') //@ diag(` strings.ContainsRune`)
_ = strings.ContainsRune("", 'x') //@ diag(` strings.ContainsRune`)
_ = strings.IndexRune("", 'x') > 0
_ = strings.IndexRune("", 'x') >= -1
_ = strings.ContainsRune("", 'x') //@ diag(` strings.ContainsRune`)
_ = !strings.ContainsRune("", 'x') //@ diag(`!strings.ContainsRune`)
_ = strings.IndexRune("", 'x') != 0
_ = !strings.ContainsRune("", 'x') //@ diag(`!strings.ContainsRune`)
_ = strings.ContainsAny("", "") //@ diag(` strings.ContainsAny`)
_ = strings.ContainsAny("", "") //@ diag(` strings.ContainsAny`)
_ = strings.IndexAny("", "") > 0
_ = strings.IndexAny("", "") >= -1
_ = strings.ContainsAny("", "") //@ diag(` strings.ContainsAny`)
_ = !strings.ContainsAny("", "") //@ diag(`!strings.ContainsAny`)
_ = strings.IndexAny("", "") != 0
_ = !strings.ContainsAny("", "") //@ diag(`!strings.ContainsAny`)
_ = strings.Contains("", "") //@ diag(` strings.Contains`)
_ = strings.Contains("", "") //@ diag(` strings.Contains`)
_ = strings.Index("", "") > 0
_ = strings.Index("", "") >= -1
_ = strings.Contains("", "") //@ diag(` strings.Contains`)
_ = !strings.Contains("", "") //@ diag(`!strings.Contains`)
_ = strings.Index("", "") != 0
_ = !strings.Contains("", "") //@ diag(`!strings.Contains`)
_ = bytes.ContainsRune(nil, 'x') //@ diag(` bytes.ContainsRune`)
_ = bytes.ContainsAny(nil, "") //@ diag(` bytes.ContainsAny`)
_ = bytes.Contains(nil, nil) //@ diag(` bytes.Contains`)
}
================================================
FILE: simple/s1004/s1004.go
================================================
package s1004
import (
"fmt"
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1004",
Run: CheckBytesCompare,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`,
Before: `if bytes.Compare(x, y) == 0 {}`,
After: `if bytes.Equal(x, y) {}`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkBytesCompareQ = pattern.MustParse(`(BinaryExpr (CallExpr (Symbol "bytes.Compare") args) op@(Or "==" "!=") (IntegerLiteral "0"))`)
checkBytesCompareRe = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`)
checkBytesCompareRn = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`)
)
func CheckBytesCompare(pass *analysis.Pass) (any, error) {
if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
// the bytes package is free to use bytes.Compare as it sees fit
return nil, nil
}
for node, m := range code.Matches(pass, checkBytesCompareQ) {
args := report.RenderArgs(pass, m.State["args"].([]ast.Expr))
prefix := ""
if m.State["op"].(token.Token) == token.NEQ {
prefix = "!"
}
var fix analysis.SuggestedFix
switch tok := m.State["op"].(token.Token); tok {
case token.EQL:
fix = edit.Fix("Simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRe, m.State))
case token.NEQ:
fix = edit.Fix("Simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRn, m.State))
default:
panic(fmt.Sprintf("unexpected token %v", tok))
}
report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix))
}
return nil, nil
}
================================================
FILE: simple/s1004/s1004_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1004
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1004/testdata/go1.0/CheckBytesCompare/compare.go
================================================
package pkg
import "bytes"
func fn() {
_ = bytes.Compare(nil, nil) == 0 //@ diag(` bytes.Equal`)
_ = bytes.Compare(nil, nil) != 0 //@ diag(`!bytes.Equal`)
_ = bytes.Compare(nil, nil) > 0
_ = bytes.Compare(nil, nil) < 0
}
================================================
FILE: simple/s1004/testdata/go1.0/CheckBytesCompare/compare.go.golden
================================================
package pkg
import "bytes"
func fn() {
_ = bytes.Equal(nil, nil) //@ diag(` bytes.Equal`)
_ = !bytes.Equal(nil, nil) //@ diag(`!bytes.Equal`)
_ = bytes.Compare(nil, nil) > 0
_ = bytes.Compare(nil, nil) < 0
}
================================================
FILE: simple/s1005/s1005.go
================================================
package s1005
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1005",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Drop unnecessary use of the blank identifier`,
Text: `In many cases, assigning to the blank identifier is unnecessary.`,
Before: `
for _ = range s {}
x, _ = someMap[key]
_ = <-ch`,
After: `
for range s{}
x = someMap[key]
<-ch`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkUnnecessaryBlankQ1 = pattern.MustParse(`
(AssignStmt
[_ (Ident "_")]
_
(Or
(IndexExpr _ _)
(UnaryExpr "<-" _))) `)
checkUnnecessaryBlankQ2 = pattern.MustParse(`
(AssignStmt
(Ident "_") _ recv@(UnaryExpr "<-" _))`)
)
func run(pass *analysis.Pass) (any, error) {
fn1 := func(node ast.Node) {
if _, ok := code.Match(pass, checkUnnecessaryBlankQ1, node); ok {
r := *node.(*ast.AssignStmt)
r.Lhs = r.Lhs[0:1]
report.Report(pass, node, "unnecessary assignment to the blank identifier",
report.FilterGenerated(),
report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.ReplaceWithNode(pass.Fset, node, &r))))
} else if m, ok := code.Match(pass, checkUnnecessaryBlankQ2, node); ok {
report.Report(pass, node, "unnecessary assignment to the blank identifier",
report.FilterGenerated(),
report.Fixes(edit.Fix("Simplify channel receive operation", edit.ReplaceWithNode(pass.Fset, node, m.State["recv"].(ast.Node)))))
}
}
fn3 := func(node ast.Node) {
rs := node.(*ast.RangeStmt)
if _, ok := pass.TypesInfo.TypeOf(rs.X).Underlying().(*types.Signature); ok {
// iteration variables are not optional with rangefunc
return
}
// for _
if rs.Value == nil && astutil.IsBlank(rs.Key) {
report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
report.FilterGenerated(),
report.MinimumLanguageVersion("go1.4"),
report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
}
// for _, _
if astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) {
// FIXME we should mark both key and value
report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier",
report.FilterGenerated(),
report.MinimumLanguageVersion("go1.4"),
report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1}))))
}
// for x, _
if !astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) {
report.Report(pass, rs.Value, "unnecessary assignment to the blank identifier",
report.FilterGenerated(),
report.MinimumLanguageVersion("go1.4"),
report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.End(), rs.Value.End()}))))
}
}
code.Preorder(pass, fn1, (*ast.AssignStmt)(nil))
code.Preorder(pass, fn3, (*ast.RangeStmt)(nil))
return nil, nil
}
================================================
FILE: simple/s1005/s1005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1005/testdata/go1.0/CheckUnnecessaryBlank/LintBlankOK.go
================================================
package pkg
func fn() {
var m map[int]int
var ch chan int
var fn func() (int, bool)
x, _ := m[0] //@ diag(`unnecessary assignment to the blank identifier`)
x, _ = <-ch //@ diag(`unnecessary assignment to the blank identifier`)
x, _ = fn()
_ = x
}
================================================
FILE: simple/s1005/testdata/go1.0/CheckUnnecessaryBlank/LintBlankOK.go.golden
================================================
package pkg
func fn() {
var m map[int]int
var ch chan int
var fn func() (int, bool)
x := m[0] //@ diag(`unnecessary assignment to the blank identifier`)
x = <-ch //@ diag(`unnecessary assignment to the blank identifier`)
x, _ = fn()
_ = x
}
================================================
FILE: simple/s1005/testdata/go1.0/CheckUnnecessaryBlank/receive-blank.go
================================================
package pkg
func fn2() {
var ch chan int
<-ch
_ = <-ch //@ diag(`unnecessary assignment to the blank identifier`)
select {
case <-ch:
case _ = <-ch: //@ diag(`unnecessary assignment to the blank identifier`)
}
x := <-ch
y, _ := <-ch, <-ch
_, z := <-ch, <-ch
_, _, _ = x, y, z
}
================================================
FILE: simple/s1005/testdata/go1.0/CheckUnnecessaryBlank/receive-blank.go.golden
================================================
package pkg
func fn2() {
var ch chan int
<-ch
<-ch //@ diag(`unnecessary assignment to the blank identifier`)
select {
case <-ch:
case <-ch: //@ diag(`unnecessary assignment to the blank identifier`)
}
x := <-ch
y, _ := <-ch, <-ch
_, z := <-ch, <-ch
_, _, _ = x, y, z
}
================================================
FILE: simple/s1005/testdata/go1.3/CheckUnnecessaryBlank/range.go
================================================
package pkg
func fn() {
var m map[string]int
// with :=
for x, _ := range m {
_ = x
}
// with =
var y string
_ = y
for y, _ = range m {
}
// all OK:
for x := range m {
_ = x
}
for x, y := range m {
_, _ = x, y
}
for _, y := range m {
_ = y
}
var x int
_ = x
for y = range m {
}
for y, x = range m {
}
for _, x = range m {
}
}
================================================
FILE: simple/s1005/testdata/go1.4/CheckUnnecessaryBlank/range.go
================================================
package pkg
func fn() {
var m map[string]int
// with :=
for x, _ := range m { //@ diag(`unnecessary assignment to the blank identifier`)
_ = x
}
// with =
var y string
_ = y
for y, _ = range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
for _ = range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
for _, _ = range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
// all OK:
for x := range m {
_ = x
}
for x, y := range m {
_, _ = x, y
}
for _, y := range m {
_ = y
}
var x int
_ = x
for y = range m {
}
for y, x = range m {
}
for _, x = range m {
}
}
================================================
FILE: simple/s1005/testdata/go1.4/CheckUnnecessaryBlank/range.go.golden
================================================
package pkg
func fn() {
var m map[string]int
// with :=
for x := range m { //@ diag(`unnecessary assignment to the blank identifier`)
_ = x
}
// with =
var y string
_ = y
for y = range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
for range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
for range m { //@ diag(`unnecessary assignment to the blank identifier`)
}
// all OK:
for x := range m {
_ = x
}
for x, y := range m {
_, _ = x, y
}
for _, y := range m {
_ = y
}
var x int
_ = x
for y = range m {
}
for y, x = range m {
}
for _, x = range m {
}
}
================================================
FILE: simple/s1006/s1006.go
================================================
package s1006
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1006",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use \"for { ... }\" for infinite loops`,
Text: `For infinite loops, using \'for { ... }\' is the most idiomatic choice.`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
loop := node.(*ast.ForStmt)
if loop.Init != nil || loop.Post != nil {
return
}
if !code.IsBoolConst(pass, loop.Cond) || !code.BoolConst(pass, loop.Cond) {
return
}
report.Report(pass, loop, "should use for {} instead of for true {}",
report.ShortRange(),
report.FilterGenerated())
}
code.Preorder(pass, fn, (*ast.ForStmt)(nil))
return nil, nil
}
================================================
FILE: simple/s1006/s1006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1006/testdata/go1.0/CheckForTrue/for-true.go
================================================
package pkg
func fn() {
for false {
}
for true { //@ diag(`should use for`)
}
for {
}
for i := 0; true; i++ {
}
}
================================================
FILE: simple/s1006/testdata/go1.0/CheckForTrue/generated.go
================================================
// Code generated by a clever monkey. DO NOT EDIT.
package pkg
func fn3() {
for true {
}
}
//line input.go:2
func fn2() {
for true {
}
}
================================================
FILE: simple/s1006/testdata/go1.0/CheckForTrue/input.go
================================================
package pkg
//@ diag(`should use for {}`)
// the error is produced by generated.go, which pretends that its
// broken code came from this file.
================================================
FILE: simple/s1007/s1007.go
================================================
package s1007
import (
"fmt"
"go/ast"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1007",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Simplify regular expression by using raw string literal`,
Text: `Raw string literals use backticks instead of quotation marks and do not support
any escape sequences. This means that the backslash can be used
freely, without the need of escaping.
Since regular expressions have their own escape sequences, raw strings
can improve their readability.`,
Before: `regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")`,
After: "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)",
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
// TODO(dominikh): support string concat, maybe support constants
var query = pattern.MustParse(`(CallExpr (Symbol fn@(Or "regexp.MustCompile" "regexp.Compile")) [lit@(BasicLit "STRING" _)])`)
func run(pass *analysis.Pass) (any, error) {
outer:
for _, m := range code.Matches(pass, query) {
lit := m.State["lit"].(*ast.BasicLit)
val := lit.Value
if lit.Value[0] != '"' {
// already a raw string
continue
}
if !strings.Contains(val, `\\`) {
continue
}
if strings.Contains(val, "`") {
continue
}
bs := false
for _, c := range val {
if !bs && c == '\\' {
bs = true
continue
}
if bs && c == '\\' {
bs = false
continue
}
if bs {
// backslash followed by non-backslash -> escape sequence
continue outer
}
}
report.Report(pass, lit, fmt.Sprintf("should use raw string (`...`) with %s to avoid having to escape twice", m.State["fn"]), report.FilterGenerated())
}
return nil, nil
}
================================================
FILE: simple/s1007/s1007_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1007
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1007/testdata/go1.0/CheckRegexpRaw/regexp-raw.go
================================================
package pkg
import "regexp"
func fn2() string { return "" }
func fn() {
x := "abc"
const y = "abc"
regexp.MustCompile(`\\.`)
regexp.MustCompile("\\.") //@ diag(re`should use raw string.+\.MustCompile`)
regexp.Compile("\\.") //@ diag(re`should use raw string.+\.Compile`)
regexp.Compile("\\.`")
regexp.MustCompile("(?m:^lease (.+?) {\n((?s).+?)\\n}\n)")
regexp.MustCompile("\\*/[ \t\n\r\f\v]*;")
regexp.MustCompile(fn2())
regexp.MustCompile(x)
regexp.MustCompile(y)
}
================================================
FILE: simple/s1008/s1008.go
================================================
package s1008
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1008",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Simplify returning boolean expression`,
Before: `
if {
return true
}
return false`,
After: `return `,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkIfReturnQIf = pattern.MustParse(`(IfStmt nil cond [(ReturnStmt [ret@(Builtin (Or "true" "false"))])] nil)`)
checkIfReturnQRet = pattern.MustParse(`(ReturnStmt [ret@(Builtin (Or "true" "false"))])`)
)
func run(pass *analysis.Pass) (any, error) {
var cm ast.CommentMap
fn := func(node ast.Node) {
if f, ok := node.(*ast.File); ok {
cm = ast.NewCommentMap(pass.Fset, f, f.Comments)
return
}
block := node.(*ast.BlockStmt)
l := len(block.List)
if l < 2 {
return
}
n1, n2 := block.List[l-2], block.List[l-1]
if len(block.List) >= 3 {
if _, ok := block.List[l-3].(*ast.IfStmt); ok {
// Do not flag a series of if statements
return
}
}
m1, ok := code.Match(pass, checkIfReturnQIf, n1)
if !ok {
return
}
m2, ok := code.Match(pass, checkIfReturnQRet, n2)
if !ok {
return
}
if op, ok := m1.State["cond"].(*ast.BinaryExpr); ok {
switch op.Op {
case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
default:
return
}
}
ret1 := m1.State["ret"].(*ast.Ident)
ret2 := m2.State["ret"].(*ast.Ident)
if ret1.Name == ret2.Name {
// we want the function to return true and false, not the
// same value both times.
return
}
hasComments := func(n ast.Node) bool {
cmf := cm.Filter(n)
for _, groups := range cmf {
for _, group := range groups {
for _, cmt := range group.List {
if strings.HasPrefix(cmt.Text, "//@ diag") {
// Staticcheck test cases use comments to mark
// expected diagnostics. Ignore these comments so we
// can test this check.
continue
}
return true
}
}
}
return false
}
// Don't flag if either branch is commented
if hasComments(n1) || hasComments(n2) {
return
}
cond := m1.State["cond"].(ast.Expr)
origCond := cond
if ret1.Name == "false" {
cond = negate(pass, cond)
}
report.Report(pass, n1,
fmt.Sprintf("should use 'return %s' instead of 'if %s { return %s }; return %s'",
report.Render(pass, cond),
report.Render(pass, origCond), report.Render(pass, ret1), report.Render(pass, ret2)),
report.FilterGenerated())
}
code.Preorder(pass, fn, (*ast.File)(nil), (*ast.BlockStmt)(nil))
return nil, nil
}
func negate(pass *analysis.Pass, expr ast.Expr) ast.Expr {
switch expr := expr.(type) {
case *ast.BinaryExpr:
out := *expr
switch expr.Op {
case token.EQL:
out.Op = token.NEQ
case token.LSS:
out.Op = token.GEQ
case token.GTR:
// Some builtins never return negative ints; "len(x) <= 0" should be "len(x) == 0".
if call, ok := expr.X.(*ast.CallExpr); ok &&
code.IsCallToAny(pass, call, "len", "cap", "copy") &&
code.IsIntegerLiteral(pass, expr.Y, constant.MakeInt64(0)) {
out.Op = token.EQL
} else {
out.Op = token.LEQ
}
case token.NEQ:
out.Op = token.EQL
case token.LEQ:
out.Op = token.GTR
case token.GEQ:
out.Op = token.LSS
}
return &out
case *ast.Ident, *ast.CallExpr, *ast.IndexExpr, *ast.StarExpr:
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
case *ast.UnaryExpr:
if expr.Op == token.NOT {
return expr.X
}
return &ast.UnaryExpr{
Op: token.NOT,
X: expr,
}
default:
return &ast.UnaryExpr{
Op: token.NOT,
X: &ast.ParenExpr{
X: expr,
},
}
}
}
================================================
FILE: simple/s1008/s1008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1008/testdata/go1.0/CheckIfReturn/comment.go
================================================
package pkg
func cmt1(x string) bool {
// A
if len(x) > 0 {
return false
}
// B
return true
}
func cmt2(x string) bool {
if len(x) > 0 { // A
return false
}
return true // B
}
func cmt3(x string) bool {
if len(x) > 0 {
return false // A
}
return true // B
}
func cmt4(x string) bool {
if len(x) > 0 {
return false // A
}
return true
// B
}
func cmt5(x string) bool {
if len(x) > 0 {
return false
}
return true // A
}
func cmt6(x string) bool {
if len(x) > 0 {
return false // A
}
return true
}
func cmt7(x string) bool {
if len(x) > 0 {
// A
return false
}
// B
return true
}
================================================
FILE: simple/s1008/testdata/go1.0/CheckIfReturn/if-return.go
================================================
package pkg
func fn() bool { return true }
func fn1() bool {
x := true
if x { //@ diag(`should use 'return x'`)
return true
}
return false
}
func fn2() bool {
x := true
if !x {
return true
}
if x {
return true
}
return false
}
func fn3() int {
var x bool
if x {
return 1
}
return 2
}
func fn4() bool { return true }
func fn5() bool {
if fn() { //@ diag(`should use 'return !fn()'`)
return false
}
return true
}
func fn6() bool {
if fn3() != fn3() { //@ diag(`should use 'return fn3() != fn3()'`)
return true
}
return false
}
func fn7() bool {
if 1 > 2 { //@ diag(`should use 'return 1 > 2'`)
return true
}
return false
}
func fn8() bool {
if fn() || fn() {
return true
}
return false
}
func fn9(x int) bool {
if x > 0 {
return true
}
return true
}
func fn10(x int) bool {
if x > 0 { //@ diag(`should use 'return x <= 0'`)
return false
}
return true
}
func fn11(x bool) bool {
if x { //@ diag(`should use 'return !x'`)
return false
}
return true
}
func fn12() bool {
var x []bool
if x[0] { //@ diag(`should use 'return !x[0]'`)
return false
}
return true
}
func fn13(a, b int) bool {
if a != b { //@ diag(`should use 'return a == b' instead of 'if a != b`)
return false
}
return true
}
func fn14(a, b int) bool {
if a >= b { //@ diag(`should use 'return a < b' instead of 'if a >= b`)
return false
}
return true
}
func fn15() bool {
if !fn() { //@ diag(`should use 'return fn()'`)
return false
}
return true
}
func fn16() <-chan bool {
x := make(chan bool, 1)
x <- true
return x
}
func fn17() bool {
if <-fn16() { //@ diag(`should use 'return !<-fn16()'`)
return false
}
return true
}
func fn18() *bool {
x := true
return &x
}
func fn19() bool {
if *fn18() { //@ diag(`should use 'return !*fn18()'`)
return false
}
return true
}
const a = true
const b = false
func fn20(x bool) bool {
// Don't match on constants other than the predeclared true and false. This protects us both from build tag woes,
// and from code that breaks when the constant values change.
if x {
return a
}
return b
}
func fn21(x bool) bool {
// Don't flag, 'true' isn't the predeclared identifier.
const true = false
if x {
return true
}
return false
}
func fn22(x string) bool {
if len(x) > 0 { //@ diag(`should use 'return len(x) == 0'`)
return false
}
return true
}
================================================
FILE: simple/s1009/s1009.go
================================================
package s1009
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1009",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Omit redundant nil check on slices, maps, and channels`,
Text: `The \'len\' function is defined for all slices, maps, and
channels, even nil ones, which have a length of zero. It is not necessary to
check for nil before checking that their length is not zero.`,
Before: `if x != nil && len(x) != 0 {}`,
After: `if len(x) != 0 {}`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`
(BinaryExpr
(BinaryExpr
x
lhsOp@(Or "==" "!=")
nilly)
outerOp@(Or "&&" "||")
(BinaryExpr
(CallExpr (Builtin "len") [x])
rhsOp
k))`)
// run checks for the following redundant nil-checks:
//
// if x == nil || len(x) == 0 {}
// if x == nil || len(x) < N {} (where N != 0)
// if x == nil || len(x) <= N {}
// if x != nil && len(x) != 0 {}
// if x != nil && len(x) == N {} (where N != 0)
// if x != nil && len(x) > N {}
// if x != nil && len(x) >= N {} (where N != 0)
func run(pass *analysis.Pass) (any, error) {
isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) {
_, ok := expr.(*ast.BasicLit)
if ok {
return true, code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
}
id, ok := expr.(*ast.Ident)
if !ok {
return false, false
}
c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const)
if !ok {
return false, false
}
return true, c.Val().Kind() == constant.Int && c.Val().String() == "0"
}
for node, m := range code.Matches(pass, query) {
x := m.State["x"].(ast.Expr)
outerOp := m.State["outerOp"].(token.Token)
lhsOp := m.State["lhsOp"].(token.Token)
rhsOp := m.State["rhsOp"].(token.Token)
nilly := m.State["nilly"].(ast.Expr)
k := m.State["k"].(ast.Expr)
eqNil := outerOp == token.LOR
if code.MayHaveSideEffects(pass, x, nil) {
continue
}
if eqNil && lhsOp != token.EQL {
continue
}
if !eqNil && lhsOp != token.NEQ {
continue
}
if !code.IsNil(pass, nilly) {
continue
}
isConst, isZero := isConstZero(k)
if !isConst {
continue
}
if eqNil {
switch rhsOp {
case token.EQL:
// avoid false positive for "xx == nil || len(xx) == "
if !isZero {
continue
}
case token.LEQ:
// ok
case token.LSS:
// avoid false positive for "xx == nil || len(xx) < 0"
if isZero {
continue
}
default:
continue
}
} else {
switch rhsOp {
case token.EQL:
// avoid false positive for "xx != nil && len(xx) == 0"
if isZero {
continue
}
case token.GEQ:
// avoid false positive for "xx != nil && len(xx) >= 0"
if isZero {
continue
}
case token.NEQ:
// avoid false positive for "xx != nil && len(xx) != "
if !isZero {
continue
}
case token.GTR:
// ok
default:
continue
}
}
// finally check that xx type is one of array, slice, map or chan
// this is to prevent false positive in case if xx is a pointer to an array
typ := pass.TypesInfo.TypeOf(x)
var nilType string
ok := typeutil.All(typ, func(term *types.Term) bool {
switch term.Type().Underlying().(type) {
case *types.Slice:
nilType = "nil slices"
return true
case *types.Map:
nilType = "nil maps"
return true
case *types.Chan:
nilType = "nil channels"
return true
case *types.Pointer:
return false
case *types.TypeParam:
return false
default:
lint.ExhaustiveTypeSwitch(term.Type().Underlying())
return false
}
})
if !ok {
continue
}
report.Report(pass, node,
fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType),
report.FilterGenerated())
}
return nil, nil
}
================================================
FILE: simple/s1009/s1009_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1009
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1009/testdata/go1.0/CheckRedundantNilCheckWithLen/nil-len.go
================================================
package pkg
func gen() []int { return make([]int, 1) }
func fn() {
var pa *[5]int
var s []int
var m map[int]int
var ch chan int
if s == nil || len(s) == 0 { //@ diag(re`should omit nil check.+for nil slices`)
}
if m == nil || len(m) == 0 { //@ diag(re`should omit nil check.+for nil maps`)
}
if ch == nil || len(ch) == 0 { //@ diag(re`should omit nil check.+for nil channels`)
}
if s != nil && len(s) != 0 { //@ diag(`should omit nil check`)
}
if m != nil && len(m) > 0 { //@ diag(`should omit nil check`)
}
if s != nil && len(s) > 5 { //@ diag(`should omit nil check`)
}
if s != nil && len(s) >= 5 { //@ diag(`should omit nil check`)
}
const five = 5
if s != nil && len(s) == five { //@ diag(`should omit nil check`)
}
if ch != nil && len(ch) == 5 { //@ diag(`should omit nil check`)
}
if pa == nil || len(pa) == 0 { // nil check cannot be removed with pointer to an array
}
if s == nil || len(m) == 0 { // different variables
}
if s != nil && len(m) == 1 { // different variables
}
var ch2 chan int
if ch == ch2 || len(ch) == 0 { // not comparing with nil
}
if ch != ch2 && len(ch) != 0 { // not comparing with nil
}
const zero = 0
if s != nil && len(s) == zero { // nil check is not redundant here
}
if s != nil && len(s) == 0 { // nil check is not redundant here
}
if s != nil && len(s) >= 0 { // nil check is not redundant here (though len(s) >= 0 is)
}
one := 1
if s != nil && len(s) == one { // nil check is not redundant here
}
if s != nil && len(s) == len(m) { // nil check is not redundant here
}
if s != nil && len(s) != 1 { // nil check is not redundant here
}
if s != nil && len(s) < 5 { // nil check is not redundant here
}
if s != nil && len(s) <= 5 { // nil check is not redundant here
}
if s != nil && len(s) != len(ch) { // nil check is not redundant here
}
if gen() != nil && len(gen()) != 0 { // nil check is not redundant, gen() isn't pure
}
}
func fn3() {
var x []int
if x == nil || len(x) == 0 { //@ diag(`should omit nil check`)
}
len := func([]int) int { return 10 }
if x == nil || len(x) == 0 {
}
}
func issue1527() {
var t struct {
pa *[5]int
s []int
m map[uint64]bool
ch chan int
}
if t.s == nil || len(t.s) == 0 { //@ diag(`should omit nil check`)
}
if t.m == nil || len(t.m) == 0 { //@ diag(`should omit nil check`)
}
if t.ch == nil || len(t.ch) == 0 { //@ diag(`should omit nil check`)
}
if t.pa == nil || len(t.pa) == 0 { // nil check cannot be removed with pointer to an array
}
}
func issue1605() {
var s []int
var m map[int]int
var ch chan int
if s == nil || len(s) <= 0 { //@ diag(`should omit nil check`)
}
if m == nil || len(m) <= 0 { //@ diag(`should omit nil check`)
}
if ch == nil || len(ch) <= 0 { //@ diag(`should omit nil check`)
}
if s == nil || len(s) < 2 { //@ diag(`should omit nil check`)
}
if m == nil || len(m) < 2 { //@ diag(`should omit nil check`)
}
if ch == nil || len(ch) < 2 { //@ diag(`should omit nil check`)
}
if s == nil || len(s) <= 2 { //@ diag(`should omit nil check`)
}
if m == nil || len(m) <= 2 { //@ diag(`should omit nil check`)
}
if ch == nil || len(ch) <= 2 { //@ diag(`should omit nil check`)
}
if s == nil || len(s) < 0 { // nil check is not redundant here (len(s) < 0 is impossible)
}
if m == nil || len(m) < 0 { // nil check is not redundant here (len(m) < 0 is impossible)
}
if ch == nil || len(ch) < 0 { // nil check is not redundant here (len(ch) < 0 is impossible)
}
if s == nil || len(s) > 2 { // nil check is not redundant here
}
if m == nil || len(m) > 2 { // nil check is not redundant here
}
if ch == nil || len(ch) > 2 { // nil check is not redundant here
}
}
================================================
FILE: simple/s1009/testdata/go1.18/CheckRedundantNilCheckWithLen/nil-len_generics.go
================================================
package pkg
func fn1[T []int | *[4]int](a T) {
if a != nil && len(a) > 0 { // don't flag, because of the pointer
}
}
func fn2[T []int | []string | map[string]int](a T) {
if a != nil && len(a) > 0 { //@ diag(`should omit nil check`)
}
}
================================================
FILE: simple/s1010/s1010.go
================================================
package s1010
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1010",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Omit default slice index`,
Text: `When slicing, the second index defaults to the length of the value,
making \'s[n:len(s)]\' and \'s[n:]\' equivalent.`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkSlicingQ = pattern.MustParse(`(SliceExpr x@(Object _) low (CallExpr (Builtin "len") [x]) nil)`)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, checkSlicingQ) {
expr := node.(*ast.SliceExpr)
report.Report(pass, expr.High,
"should omit second index in slice, s[a:len(s)] is identical to s[a:]",
report.FilterGenerated(),
report.Fixes(edit.Fix("Simplify slice expression", edit.Delete(expr.High))))
}
return nil, nil
}
================================================
FILE: simple/s1010/s1010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1010/testdata/go1.0/CheckSlicing/slicing.go
================================================
package pkg
func fn() {
var s [5]int
_ = s[:len(s)] //@ diag(`omit second index`)
len := func(s [5]int) int { return -1 }
_ = s[:len(s)]
}
================================================
FILE: simple/s1010/testdata/go1.0/CheckSlicing/slicing.go.golden
================================================
package pkg
func fn() {
var s [5]int
_ = s[:] //@ diag(`omit second index`)
len := func(s [5]int) int { return -1 }
_ = s[:len(s)]
}
================================================
FILE: simple/s1011/s1011.go
================================================
package s1011
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/facts/purity"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1011",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer, purity.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Use a single \'append\' to concatenate two slices`,
Before: `
for _, e := range y {
x = append(x, e)
}
for i := range y {
x = append(x, y[i])
}
for i := range y {
v := y[i]
x = append(x, v)
}`,
After: `
x = append(x, y...)
x = append(x, y...)
x = append(x, y...)`,
Since: "2017.1",
// MergeIfAll because y might not be a slice under all build tags.
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkLoopAppendQ = pattern.MustParse(`
(Or
(RangeStmt
(Ident "_")
val@(Object _)
_
x
[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])])
(RangeStmt
idx@(Object _)
nil
_
x
[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])])
(RangeStmt
idx@(Object _)
nil
_
x
[(AssignStmt val@(Object _) ":=" (IndexExpr x idx))
(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]))`)
func run(pass *analysis.Pass) (any, error) {
pure := pass.ResultOf[purity.Analyzer].(purity.Result)
for node, m := range code.Matches(pass, checkLoopAppendQ) {
if val, ok := m.State["val"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), val) {
continue
}
if m.State["idx"] != nil && code.MayHaveSideEffects(pass, m.State["x"].(ast.Expr), pure) {
// When using an index-based loop, x gets evaluated repeatedly and thus should be pure.
// This doesn't matter for value-based loops, because x only gets evaluated once.
continue
}
if idx, ok := m.State["idx"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), idx) {
// The lhs mustn't refer to the index loop variable.
continue
}
if code.MayHaveSideEffects(pass, m.State["lhs"].(ast.Expr), pure) {
// The lhs may be dynamic and return different values on each iteration. For example:
//
// func bar() map[int][]int { /* return one of several maps */ }
//
// func foo(x []int, y [][]int) {
// for i := range x {
// bar()[0] = append(bar()[0], x[i])
// }
// }
//
// The dynamic nature of the lhs might also affect the value of the index.
continue
}
src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr))
dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr))
if !types.Identical(src, dst) {
continue
}
r := &ast.AssignStmt{
Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)},
Tok: token.ASSIGN,
Rhs: []ast.Expr{
&ast.CallExpr{
Fun: &ast.Ident{Name: "append"},
Args: []ast.Expr{
m.State["lhs"].(ast.Expr),
m.State["x"].(ast.Expr),
},
Ellipsis: 1,
},
},
}
report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)),
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r))))
}
return nil, nil
}
================================================
FILE: simple/s1011/s1011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1011/testdata/go1.0/CheckLoopAppend/loop-append.go
================================================
package pkg
type T struct {
F string
}
func fn1() {
var x []interface{}
var y []int
for _, v := range y {
x = append(x, v)
}
var a, b []int
for _, v := range a { //@ diag(`should replace loop`)
b = append(b, v)
}
var a2, b2 []int
for i := range a2 { //@ diag(`should replace loop`)
b2 = append(b2, a2[i])
}
var a3, b3 []int
for i := range a3 { //@ diag(`should replace loop`)
v := a3[i]
b3 = append(b3, v)
}
var a4 []int
for i := range fn6() {
a4 = append(a4, fn6()[i])
}
var m map[string]int
var c []int
for _, v := range m {
c = append(c, v)
}
var t []T
var m2 map[string][]T
for _, tt := range t {
m2[tt.F] = append(m2[tt.F], tt)
}
var out []T
for _, tt := range t {
out = append(m2[tt.F], tt)
}
_ = out
}
func fn2() {
var v struct {
V int
}
var in []int
var out []int
for _, v.V = range in {
out = append(out, v.V)
}
}
func fn3() {
var t []T
var out [][]T
var m2 map[string][]T
for _, tt := range t {
out = append(out, m2[tt.F])
}
}
func fn4() {
var a, b, c []int
for _, v := range a {
b = append(c, v)
}
_ = b
}
func fn5() {
var t []T
var m2 map[string][]T
var out []T
for _, tt := range t {
out = append(m2[tt.F], tt)
}
_ = out
}
func fn6() []int {
return []int{1, 2, 3}
}
func fn7() {
var x []int
for _, v := range fn6() { //@ diag(`should replace loop`)
// Purity doesn't matter here
x = append(x, v)
}
for i := range fn6() {
// Purity does matter here
x = append(x, fn6()[i])
}
}
func fn8() {
// The lhs isn't allowed to refer to i
var i int
var x []int
var y [][]int
for i = range x {
y[i] = append(y[i], x[i])
}
for i := range x {
y[i] = append(y[i], x[i])
}
}
func fn9() {
// The lhs isn't allowed to have side effects
bar := func() map[int][]int { return nil }
var x []int
for i := range x {
bar()[0] = append(bar()[0], x[i])
}
}
================================================
FILE: simple/s1011/testdata/go1.0/CheckLoopAppend/loop-append.go.golden
================================================
package pkg
type T struct {
F string
}
func fn1() {
var x []interface{}
var y []int
for _, v := range y {
x = append(x, v)
}
var a, b []int
b = append(b, a...)
var a2, b2 []int
b2 = append(b2, a2...)
var a3, b3 []int
b3 = append(b3, a3...)
var a4 []int
for i := range fn6() {
a4 = append(a4, fn6()[i])
}
var m map[string]int
var c []int
for _, v := range m {
c = append(c, v)
}
var t []T
var m2 map[string][]T
for _, tt := range t {
m2[tt.F] = append(m2[tt.F], tt)
}
var out []T
for _, tt := range t {
out = append(m2[tt.F], tt)
}
_ = out
}
func fn2() {
var v struct {
V int
}
var in []int
var out []int
for _, v.V = range in {
out = append(out, v.V)
}
}
func fn3() {
var t []T
var out [][]T
var m2 map[string][]T
for _, tt := range t {
out = append(out, m2[tt.F])
}
}
func fn4() {
var a, b, c []int
for _, v := range a {
b = append(c, v)
}
_ = b
}
func fn5() {
var t []T
var m2 map[string][]T
var out []T
for _, tt := range t {
out = append(m2[tt.F], tt)
}
_ = out
}
func fn6() []int {
return []int{1, 2, 3}
}
func fn7() {
var x []int
x = append(x, fn6()...)
for i := range fn6() {
// Purity does matter here
x = append(x, fn6()[i])
}
}
func fn8() {
// The lhs isn't allowed to refer to i
var i int
var x []int
var y [][]int
for i = range x {
y[i] = append(y[i], x[i])
}
for i := range x {
y[i] = append(y[i], x[i])
}
}
func fn9() {
// The lhs isn't allowed to have side effects
bar := func() map[int][]int { return nil }
var x []int
for i := range x {
bar()[0] = append(bar()[0], x[i])
}
}
================================================
FILE: simple/s1012/s1012.go
================================================
package s1012
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1012",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Replace \'time.Now().Sub(x)\' with \'time.Since(x)\'`,
Text: `The \'time.Since\' helper has the same effect as using \'time.Now().Sub(x)\'
but is easier to read.`,
Before: `time.Now().Sub(x)`,
After: `time.Since(x)`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkTimeSinceQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (Symbol "time.Now") []) (Symbol "(time.Time).Sub")) [arg])`)
checkTimeSinceR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Since")) [arg])`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkTimeSinceQ) {
edits := code.EditMatch(pass, node, m, checkTimeSinceR)
report.Report(pass, node, "should use time.Since instead of time.Now().Sub",
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace with call to time.Since", edits...)))
}
return nil, nil
}
================================================
FILE: simple/s1012/s1012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1012/testdata/go1.0/CheckTimeSince/time-since.go
================================================
package pkg
import "time"
func fn() {
t1 := time.Now()
_ = time.Now().Sub(t1) //@ diag(`time.Since`)
_ = time.Date(0, 0, 0, 0, 0, 0, 0, nil).Sub(t1)
}
================================================
FILE: simple/s1012/testdata/go1.0/CheckTimeSince/time-since.go.golden
================================================
package pkg
import "time"
func fn() {
t1 := time.Now()
_ = time.Since(t1) //@ diag(`time.Since`)
_ = time.Date(0, 0, 0, 0, 0, 0, 0, nil).Sub(t1)
}
================================================
FILE: simple/s1016/s1016.go
================================================
package s1016
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"go/version"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1016",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use a type conversion instead of manually copying struct fields`,
Text: `Two struct types with identical fields can be converted between each
other. In older versions of Go, the fields had to have identical
struct tags. Since Go 1.8, however, struct tags are ignored during
conversions. It is thus not necessary to manually copy every field
individually.`,
Before: `
var x T1
y := T2{
Field1: x.Field1,
Field2: x.Field2,
}`,
After: `
var x T1
y := T2(x)`,
Since: "2017.1",
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): support conversions between type parameters
fn := func(c inspector.Cursor) {
node := c.Node()
if unary, ok := c.Parent().Node().(*ast.UnaryExpr); ok && unary.Op == token.AND {
// Do not suggest type conversion between pointers
return
}
lit := node.(*ast.CompositeLit)
var typ1 types.Type
var named1 *types.Named
switch typ := pass.TypesInfo.TypeOf(lit.Type).(type) {
case *types.Named:
typ1 = typ
named1 = typ
case *types.Alias:
ua := types.Unalias(typ)
if n, ok := ua.(*types.Named); ok {
typ1 = typ
named1 = n
}
}
if typ1 == nil {
return
}
s1, ok := typ1.Underlying().(*types.Struct)
if !ok {
return
}
var typ2 types.Type
var named2 *types.Named
var ident *ast.Ident
getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
sel, ok := expr.(*ast.SelectorExpr)
if !ok {
return nil, nil, false
}
ident, ok := sel.X.(*ast.Ident)
if !ok {
return nil, nil, false
}
typ := pass.TypesInfo.TypeOf(sel.X)
return typ, ident, typ != nil
}
if len(lit.Elts) == 0 {
return
}
if s1.NumFields() != len(lit.Elts) {
return
}
for i, elt := range lit.Elts {
var t types.Type
var id *ast.Ident
var ok bool
switch elt := elt.(type) {
case *ast.SelectorExpr:
t, id, ok = getSelType(elt)
if !ok {
return
}
if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
return
}
case *ast.KeyValueExpr:
var sel *ast.SelectorExpr
sel, ok = elt.Value.(*ast.SelectorExpr)
if !ok {
return
}
if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
return
}
t, id, ok = getSelType(elt.Value)
}
if !ok {
return
}
// All fields must be initialized from the same object
if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) {
return
}
switch t := t.(type) {
case *types.Named:
typ2 = t
named2 = t
case *types.Alias:
if n, ok := types.Unalias(t).(*types.Named); ok {
typ2 = t
named2 = n
}
}
if typ2 == nil {
return
}
ident = id
}
if typ2 == nil {
return
}
if named1.Obj().Pkg() != named2.Obj().Pkg() {
// Do not suggest type conversions between different
// packages. Types in different packages might only match
// by coincidence. Furthermore, if the dependency ever
// adds more fields to its type, it could break the code
// that relies on the type conversion to work.
return
}
s2, ok := typ2.Underlying().(*types.Struct)
if !ok {
return
}
if typ1 == typ2 {
return
}
if version.Compare(code.LanguageVersion(pass, node), "go1.8") >= 0 {
if !types.IdenticalIgnoreTags(s1, s2) {
return
}
} else {
if !types.Identical(s1, s2) {
return
}
}
r := &ast.CallExpr{
Fun: lit.Type,
Args: []ast.Expr{ident},
}
report.Report(pass, node,
fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, types.TypeString(typ2, types.RelativeTo(pass.Pkg)), types.TypeString(typ1, types.RelativeTo(pass.Pkg))),
report.FilterGenerated(),
report.Fixes(edit.Fix("Use type conversion", edit.ReplaceWithNode(pass.Fset, node, r))))
}
for c := range code.Cursor(pass).Preorder((*ast.CompositeLit)(nil)) {
fn(c)
}
return nil, nil
}
================================================
FILE: simple/s1016/s1016_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1016
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1016/testdata/go1.0/CheckSimplerStructConversion/convert.go
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 t1
func fn() {
v1 := t1{1, 2}
v2 := t2{1, 2}
_ = t2{v1.a, v1.b} //@ diag(`should convert v1`)
_ = t2{a: v1.a, b: v1.b} //@ diag(`should convert v1`)
_ = t2{b: v1.b, a: v1.a} //@ diag(`should convert v1`)
_ = t3{v1.a, v1.b} //@ diag(`should convert v1`)
_ = t3{v1.a, v2.b}
_ = t2{v1.b, v1.a}
_ = t2{a: v1.b, b: v1.a}
_ = t2{a: v1.a}
_ = t1{v1.a, v1.b}
v := t1{1, 2}
_ = &t2{v.a, v.b}
}
================================================
FILE: simple/s1016/testdata/go1.0/CheckSimplerStructConversion/convert.go.golden
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 t1
func fn() {
v1 := t1{1, 2}
v2 := t2{1, 2}
_ = t2(v1) //@ diag(`should convert v1`)
_ = t2(v1) //@ diag(`should convert v1`)
_ = t2(v1) //@ diag(`should convert v1`)
_ = t3(v1) //@ diag(`should convert v1`)
_ = t3{v1.a, v2.b}
_ = t2{v1.b, v1.a}
_ = t2{a: v1.b, b: v1.a}
_ = t2{a: v1.a}
_ = t1{v1.a, v1.b}
v := t1{1, 2}
_ = &t2{v.a, v.b}
}
================================================
FILE: simple/s1016/testdata/go1.18/CheckSimplerStructConversion/convert_generics.go
================================================
package pkg
type T1 struct {
A int
B int
}
type T2 struct {
A int
B int
}
type T3[T any] struct {
A T
B T
}
type T4[T any] struct {
A T
B T
}
func _() {
t1 := T1{0, 0}
t3 := T3[int]{0, 0}
_ = T2{t1.A, t1.B} //@ diag(`(type T1) to T2`)
_ = T2{t3.A, t3.B} //@ diag(`(type T3[int]) to T2`)
_ = T4[int]{t1.A, t1.B} //@ diag(`(type T1) to T4[int]`)
_ = T4[int]{t3.A, t3.B} //@ diag(`(type T3[int]) to T4[int]`)
_ = T4[any]{t3.A, t3.B}
}
================================================
FILE: simple/s1016/testdata/go1.18/CheckSimplerStructConversion/convert_generics.go.golden
================================================
package pkg
type T1 struct {
A int
B int
}
type T2 struct {
A int
B int
}
type T3[T any] struct {
A T
B T
}
type T4[T any] struct {
A T
B T
}
func _() {
t1 := T1{0, 0}
t3 := T3[int]{0, 0}
_ = T2(t1) //@ diag(`(type T1) to T2`)
_ = T2(t3) //@ diag(`(type T3[int]) to T2`)
_ = T4[int](t1) //@ diag(`(type T1) to T4[int]`)
_ = T4[int](t3) //@ diag(`(type T3[int]) to T4[int]`)
_ = T4[any]{t3.A, t3.B}
}
================================================
FILE: simple/s1016/testdata/go1.7/CheckSimplerStructConversion/convert.go
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 struct {
a int `tag`
b int `tag`
}
func fn() {
v1 := t1{1, 2}
_ = t2{v1.a, v1.b} //@ diag(`should convert v1`)
_ = t3{v1.a, v1.b}
}
================================================
FILE: simple/s1016/testdata/go1.7/CheckSimplerStructConversion/convert.go.golden
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 struct {
a int `tag`
b int `tag`
}
func fn() {
v1 := t1{1, 2}
_ = t2(v1) //@ diag(`should convert v1`)
_ = t3{v1.a, v1.b}
}
================================================
FILE: simple/s1016/testdata/go1.8/CheckSimplerStructConversion/convert.go
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 struct {
a int `tag`
b int `tag`
}
func fn() {
v1 := t1{1, 2}
_ = t2{v1.a, v1.b} //@ diag(`should convert v1`)
_ = t3{v1.a, v1.b} //@ diag(`should convert v1`)
}
================================================
FILE: simple/s1016/testdata/go1.8/CheckSimplerStructConversion/convert.go.golden
================================================
package pkg
type t1 struct {
a int
b int
}
type t2 struct {
a int
b int
}
type t3 struct {
a int `tag`
b int `tag`
}
func fn() {
v1 := t1{1, 2}
_ = t2(v1) //@ diag(`should convert v1`)
_ = t3(v1) //@ diag(`should convert v1`)
}
================================================
FILE: simple/s1016/testdata/go1.9/CheckSimplerStructConversion/convert_alias.go
================================================
package pkg
type S1 struct {
A int
}
type S2 struct {
A int
}
type Alias = S2
// XXX the diagnostics depend on GODEBUG
func foo() {
v1 := S1{A: 1}
v2 := Alias{A: 1}
_ = Alias{A: v1.A} //@ diag(`should convert v1`)
_ = S1{A: v2.A} //@ diag(`should convert v2`)
}
================================================
FILE: simple/s1016/testdata/go1.9/CheckSimplerStructConversion/convert_alias.go.golden
================================================
package pkg
type S1 struct {
A int
}
type S2 struct {
A int
}
type Alias = S2
// XXX the diagnostics depend on GODEBUG
func foo() {
v1 := S1{A: 1}
v2 := Alias{A: 1}
_ = Alias(v1) //@ diag(`should convert v1`)
_ = S1(v2) //@ diag(`should convert v2`)
}
================================================
FILE: simple/s1017/s1017.go
================================================
package s1017
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1017",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Replace manual trimming with \'strings.TrimPrefix\'`,
Text: `Instead of using \'strings.HasPrefix\' and manual slicing, use the
\'strings.TrimPrefix\' function. If the string doesn't start with the
prefix, the original string will be returned. Using \'strings.TrimPrefix\'
reduces complexity, and avoids common bugs, such as off-by-one
mistakes.`,
Before: `
if strings.HasPrefix(str, prefix) {
str = str[len(prefix):]
}`,
After: `str = strings.TrimPrefix(str, prefix)`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
sameNonDynamic := func(node1, node2 ast.Node) bool {
if reflect.TypeOf(node1) != reflect.TypeOf(node2) {
return false
}
switch node1 := node1.(type) {
case *ast.Ident:
return pass.TypesInfo.ObjectOf(node1) == pass.TypesInfo.ObjectOf(node2.(*ast.Ident))
case *ast.SelectorExpr, *ast.IndexExpr:
return astutil.Equal(node1, node2)
case *ast.BasicLit:
return astutil.Equal(node1, node2)
}
return false
}
isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool {
call, ok := fn.(*ast.CallExpr)
if !ok {
return false
}
if !code.IsCallTo(pass, call, "len") {
return false
}
if len(call.Args) != 1 {
return false
}
return sameNonDynamic(call.Args[knowledge.Arg("len.v")], ident)
}
seen := make(map[ast.Node]struct{})
fn := func(node ast.Node) {
var pkg string
var fun string
ifstmt := node.(*ast.IfStmt)
if ifstmt.Init != nil {
return
}
if ifstmt.Else != nil {
seen[ifstmt.Else] = struct{}{}
return
}
if _, ok := seen[ifstmt]; ok {
return
}
if len(ifstmt.Body.List) != 1 {
return
}
condCall, ok := ifstmt.Cond.(*ast.CallExpr)
if !ok {
return
}
condCallName := code.CallName(pass, condCall)
switch condCallName {
case "strings.HasPrefix":
pkg = "strings"
fun = "HasPrefix"
case "strings.HasSuffix":
pkg = "strings"
fun = "HasSuffix"
case "strings.Contains":
pkg = "strings"
fun = "Contains"
case "bytes.HasPrefix":
pkg = "bytes"
fun = "HasPrefix"
case "bytes.HasSuffix":
pkg = "bytes"
fun = "HasSuffix"
case "bytes.Contains":
pkg = "bytes"
fun = "Contains"
default:
return
}
assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt)
if !ok {
return
}
if assign.Tok != token.ASSIGN {
return
}
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return
}
if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
return
}
switch rhs := assign.Rhs[0].(type) {
case *ast.CallExpr:
if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) {
return
}
rhsName := code.CallName(pass, rhs)
if condCallName == "strings.HasPrefix" && rhsName == "strings.TrimPrefix" ||
condCallName == "strings.HasSuffix" && rhsName == "strings.TrimSuffix" ||
condCallName == "strings.Contains" && rhsName == "strings.Replace" ||
condCallName == "bytes.HasPrefix" && rhsName == "bytes.TrimPrefix" ||
condCallName == "bytes.HasSuffix" && rhsName == "bytes.TrimSuffix" ||
condCallName == "bytes.Contains" && rhsName == "bytes.Replace" {
report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s", rhsName), report.FilterGenerated())
}
case *ast.SliceExpr:
slice := rhs
if !ok {
return
}
if slice.Slice3 {
return
}
if !sameNonDynamic(slice.X, condCall.Args[0]) {
return
}
validateOffset := func(off ast.Expr) bool {
switch off := off.(type) {
case *ast.CallExpr:
return isLenOnIdent(off, condCall.Args[1])
case *ast.BasicLit:
if pkg != "strings" {
return false
}
if _, ok := condCall.Args[1].(*ast.BasicLit); !ok {
// Only allow manual slicing with an integer
// literal if the second argument to HasPrefix
// was a string literal.
return false
}
s, ok1 := code.ExprToString(pass, condCall.Args[1])
n, ok2 := code.ExprToInt(pass, off)
if !ok1 || !ok2 || n != int64(len(s)) {
return false
}
return true
default:
return false
}
}
switch fun {
case "HasPrefix":
// TODO(dh) We could detect a High that is len(s), but another
// rule will already flag that, anyway.
if slice.High != nil {
return
}
if !validateOffset(slice.Low) {
return
}
case "HasSuffix":
if slice.Low != nil {
n, ok := code.ExprToInt(pass, slice.Low)
if !ok || n != 0 {
return
}
}
switch index := slice.High.(type) {
case *ast.BinaryExpr:
if index.Op != token.SUB {
return
}
if !isLenOnIdent(index.X, condCall.Args[0]) {
return
}
if !validateOffset(index.Y) {
return
}
default:
return
}
default:
return
}
var replacement string
switch fun {
case "HasPrefix":
replacement = "TrimPrefix"
case "HasSuffix":
replacement = "TrimSuffix"
}
report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s.%s", pkg, replacement),
report.ShortRange(),
report.FilterGenerated())
}
}
code.Preorder(pass, fn, (*ast.IfStmt)(nil))
return nil, nil
}
================================================
FILE: simple/s1017/s1017_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1017
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1017/testdata/go1.0/CheckTrim/trim.go
================================================
package pkg
import (
"bytes"
"strings"
)
func foo(s string) int { return 0 }
func gen() string {
return ""
}
func fn() {
const s1 = "a string value"
var s2 = "a string value"
const n = 14
var id1 = "a string value"
var id2 string
if strings.HasPrefix(id1, s1) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
id1 = id1[len(s1):]
}
if strings.HasPrefix(id1, s1) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
id1 = strings.TrimPrefix(id1, s1)
}
if strings.HasPrefix(id1, s1) {
id1 = strings.TrimPrefix(id1, s2)
}
if strings.Contains(id1, s1) { //@ diag(re`should replace.*with.*strings\.Replace`)
id1 = strings.Replace(id1, s1, "something", 123)
}
if strings.HasSuffix(id1, s2) { //@ diag(re`should replace.*with.*strings\.TrimSuffix`)
id1 = id1[:len(id1)-len(s2)]
}
var x, y []string
var i int
if strings.HasPrefix(x[i], s1) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
x[i] = x[i][len(s1):]
}
if strings.HasPrefix(x[i], y[i]) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
x[i] = x[i][len(y[i]):]
}
var t struct{ x string }
if strings.HasPrefix(t.x, s1) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
t.x = t.x[len(s1):]
}
if strings.HasPrefix(id1, "test") { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
id1 = id1[len("test"):]
}
if strings.HasPrefix(id1, "test") { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
id1 = id1[4:]
}
if strings.HasPrefix(id1, s1) { // not allowed, 14 and s1 aren't obviously connected
id1 = id1[14:]
}
if strings.HasPrefix(id1, s1) { // not allowed, s1 and n aren't obviously connected
id1 = id1[n:]
}
var b1, b2 []byte
if bytes.HasPrefix(b1, b2) { //@ diag(re`should replace.*with.*bytes\.TrimPrefix`)
b1 = b1[len(b2):]
}
id3 := s2
if strings.HasPrefix(id1, id3) { //@ diag(re`should replace.*with.*strings\.TrimPrefix`)
id1 = id1[len(id3):]
}
if strings.HasSuffix(id1, s2) {
id1 = id1[:len(id1)+len(s2)] // wrong operator
}
if strings.HasSuffix(id1, s2) {
id1 = id1[:len(s2)-len(id1)] // wrong math
}
if strings.HasSuffix(id1, s2) {
id1 = id1[:len(id1)-len(id1)] // wrong string length
}
if strings.HasPrefix(id1, gen()) {
id1 = id1[len(gen()):] // dynamic id3
}
if strings.HasPrefix(id1, s1) {
id1 = id1[foo(s1):] // wrong function
}
if strings.HasPrefix(id1, s1) {
id1 = id1[len(id1):] // len() on wrong value
}
if strings.HasPrefix(id1, "test") {
id1 = id1[5:] // wrong length
}
if strings.HasPrefix(id1, s1) {
id1 = id1[len(s1)+1:] // wrong length due to math
}
if strings.HasPrefix(id1, s1) {
id2 = id1[len(s1):] // assigning to the wrong variable
}
if strings.HasPrefix(id1, s1) {
id1 = id1[len(s1):15] // has a max
}
if strings.HasPrefix(id1, s1) {
id1 = id2[len(s1):] // assigning the wrong value
}
if strings.HasPrefix(id1, s1) {
id1 = id1[len(s1):]
id1 += "" // doing more work in the if
}
if strings.HasPrefix(id1, s1) {
id1 = id1[len(s1):]
} else {
id1 = "game over" // else branch
}
if strings.HasPrefix(id1, s1) {
// the conditional is guarding additional code
id1 = id1[len(s1):]
println(id1)
}
if strings.Contains(id1, s1) {
id1 = id1[:]
}
}
func fn2() {
var s string
const id = ".json"
if strings.HasSuffix(s, ".json") { //@ diag(re`should replace.*with.*strings\.TrimSuffix`)
s = strings.TrimSuffix(s, ".json")
}
if strings.HasSuffix(s, ".json") { //@ diag(re`should replace.*with.*strings\.TrimSuffix`)
s = s[:len(s)-len(".json")]
}
if strings.HasSuffix(s, ".json") { //@ diag(re`should replace.*with.*strings\.TrimSuffix`)
s = s[:len(s)-5]
}
if strings.HasSuffix(s, id) {
s = s[:len(s)-5] // second argument of HasSuffix it not a string literal
}
if strings.HasSuffix(s, ".json") {
s = s[:len(s)-4] // wrong length
}
// Don't check with else if branch; see https://staticcheck.dev/issues/1447
if strings.HasPrefix(s, "\xff\xfe") {
s = s[2:]
} else if strings.HasPrefix(s, "\xef\xbb\xbf") {
s = s[3:]
}
}
func fn3() {
const s1 = "a string value"
var id1 = "a string value"
len := func(string) int { return 0 } // don't accept non-builtin definition of len
if strings.HasPrefix(id1, s1) {
id1 = id1[len(s1):]
}
if strings.HasSuffix(id1, s1) {
id1 = id1[:len(id1)-len(s1)]
}
}
================================================
FILE: simple/s1018/s1018.go
================================================
package s1018
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1018",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Use \"copy\" for sliding elements`,
Text: `\'copy()\' permits using the same source and destination slice, even with
overlapping ranges. This makes it ideal for sliding elements in a
slice.`,
Before: `
for i := 0; i < n; i++ {
bs[i] = bs[offset+i]
}`,
After: `copy(bs[:n], bs[offset:])`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkLoopSlideQ = pattern.MustParse(`
(ForStmt
(AssignStmt initvar@(Ident _) _ (IntegerLiteral "0"))
(BinaryExpr initvar "<" limit@(Ident _))
(IncDecStmt initvar "++")
[(AssignStmt
(IndexExpr slice@(Ident _) initvar)
"="
(IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`)
checkLoopSlideR = pattern.MustParse(`
(CallExpr
(Ident "copy")
[(SliceExpr slice nil limit nil)
(SliceExpr slice offset nil nil)])`)
)
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): detect bs[i+offset] in addition to bs[offset+i]
// TODO(dh): consider merging this function with LintLoopCopy
// TODO(dh): detect length that is an expression, not a variable name
// TODO(dh): support sliding to a different offset than the beginning of the slice
for node, m := range code.Matches(pass, checkLoopSlideQ) {
typ := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident))
// The pattern probably needs a core type, but All is fine, too. Either way we only accept slices.
if !typeutil.All(typ, typeutil.IsSlice) {
continue
}
edits := code.EditMatch(pass, node, m, checkLoopSlideR)
report.Report(pass, node, "should use copy() instead of loop for sliding slice elements",
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Use copy() instead of loop", edits...)))
}
return nil, nil
}
================================================
FILE: simple/s1018/s1018_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1018
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1018/testdata/go1.0/CheckLoopSlide/LintLoopSlide.go
================================================
package pkg
func fn() {
var n int
var bs []int
var offset int
for i := 0; i < n; i++ { //@ diag(`should use copy() instead of loop for sliding slice elements`)
bs[i] = bs[offset+i]
}
for i := 1; i < n; i++ { // not currently supported
bs[i] = bs[offset+i]
}
for i := 1; i < n; i++ { // not currently supported
bs[i] = bs[i+offset]
}
for i := 0; i <= n; i++ {
bs[i] = bs[offset+i]
}
}
================================================
FILE: simple/s1018/testdata/go1.0/CheckLoopSlide/LintLoopSlide.go.golden
================================================
package pkg
func fn() {
var n int
var bs []int
var offset int
copy(bs[:n], bs[offset:])
for i := 1; i < n; i++ { // not currently supported
bs[i] = bs[offset+i]
}
for i := 1; i < n; i++ { // not currently supported
bs[i] = bs[i+offset]
}
for i := 0; i <= n; i++ {
bs[i] = bs[offset+i]
}
}
================================================
FILE: simple/s1018/testdata/go1.18/CheckLoopSlide/generics.go
================================================
package pkg
func tpfn[T []int]() {
var n int
var bs T
var offset int
for i := 0; i < n; i++ { //@ diag(`should use copy() instead of loop for sliding slice elements`)
bs[i] = bs[offset+i]
}
}
================================================
FILE: simple/s1018/testdata/go1.18/CheckLoopSlide/generics.go.golden
================================================
package pkg
func tpfn[T []int]() {
var n int
var bs T
var offset int
copy(bs[:n], bs[offset:])
}
================================================
FILE: simple/s1019/s1019.go
================================================
package s1019
import (
"fmt"
"go/ast"
"go/types"
"path/filepath"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1019",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Simplify \"make\" call by omitting redundant arguments`,
Text: `The \"make\" function has default values for the length and capacity
arguments. For channels, the length defaults to zero, and for slices,
the capacity defaults to the length.`,
Since: "2017.1",
// MergeIfAll because the type might be different under different build tags.
// You shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkMakeLenCapQ1 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size@(IntegerLiteral "0")])`)
checkMakeLenCapQ2 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size size])`)
)
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
if pass.Pkg.Path() == "runtime_test" && filepath.Base(pass.Fset.Position(node.Pos()).Filename) == "map_test.go" {
// special case of runtime tests testing map creation
return
}
if m, ok := code.Match(pass, checkMakeLenCapQ1, node); ok {
T := m.State["typ"].(ast.Expr)
size := m.State["size"].(ast.Node)
if _, ok := typeutil.CoreType(pass.TypesInfo.TypeOf(T)).Underlying().(*types.Chan); ok {
report.Report(pass, size, fmt.Sprintf("should use make(%s) instead", report.Render(pass, T)), report.FilterGenerated())
}
} else if m, ok := code.Match(pass, checkMakeLenCapQ2, node); ok {
// TODO(dh): don't consider sizes identical if they're
// dynamic. for example: make(T, <-ch, <-ch).
T := m.State["typ"].(ast.Expr)
size := m.State["size"].(ast.Node)
report.Report(pass, size,
fmt.Sprintf("should use make(%s, %s) instead", report.Render(pass, T), report.Render(pass, size)),
report.FilterGenerated())
}
}
code.Preorder(pass, fn, (*ast.CallExpr)(nil))
return nil, nil
}
================================================
FILE: simple/s1019/s1019_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1019
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1019/testdata/go1.0/CheckMakeLenCap/LintMakeLenCap.go
================================================
package pkg
func fn() {
const c = 0
var x, y int
type s []int
type ch chan int
_ = make([]int, 1)
_ = make([]int, 0) // length is mandatory for slices, don't suggest removal
_ = make(s, 0) // length is mandatory for slices, don't suggest removal
_ = make(chan int, c) // constant of 0 may be due to debugging, math or platform-specific code
_ = make(chan int, 0) //@ diag(`should use make(chan int) instead`)
_ = make(ch, 0) //@ diag(`should use make(ch) instead`)
_ = make(map[int]int, 0)
_ = make([]int, 1, 1) //@ diag(`should use make([]int, 1) instead`)
_ = make([]int, x, x) //@ diag(`should use make([]int, x) instead`)
_ = make([]int, 1, 2)
_ = make([]int, x, y)
}
================================================
FILE: simple/s1019/testdata/go1.18/CheckMakeLenCap/CheckMakeLenCap.go
================================================
package pkg
func fn1() {
_ = make(chan int, 0) //@ diag(`should use make(chan int) instead`)
}
func fn2[T chan int]() {
_ = make(T, 0) //@ diag(`should use make(T) instead`)
}
func fn3[T chan T]() {
_ = make(T, 0) //@ diag(`should use make(T) instead`)
}
func fn4[T any, C chan T]() {
_ = make(chan T, 0) //@ diag(`should use make(chan T) instead`)
_ = make(C, 0) //@ diag(`should use make(C) instead`)
}
func fn5[T []int]() {
_ = make(T, 0) // don't flag this, T isn't a channel
}
type I interface {
chan int
}
func fn6[T I]() {
_ = make(T, 0) //@ diag(`should use make(T) instead`)
}
================================================
FILE: simple/s1020/s1020.go
================================================
package s1020
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1020",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Omit redundant nil check in type assertion`,
Before: `if _, ok := i.(T); ok && i != nil {}`,
After: `if _, ok := i.(T); ok {}`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkAssertNotNilFn1Q = pattern.MustParse(`
(IfStmt
(AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr assert@(Object _) _)])
(Or
(BinaryExpr ok "&&" (BinaryExpr assert "!=" (Builtin "nil")))
(BinaryExpr (BinaryExpr assert "!=" (Builtin "nil")) "&&" ok))
_
_)`)
checkAssertNotNilFn2Q = pattern.MustParse(`
(IfStmt
nil
(BinaryExpr lhs@(Object _) "!=" (Builtin "nil"))
[
ifstmt@(IfStmt
(AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr lhs _)])
ok
_
nil)
]
nil)`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkAssertNotNilFn1Q) {
assert := m.State["assert"].(types.Object)
assign := m.State["ok"].(types.Object)
report.Report(pass, node, fmt.Sprintf("when %s is true, %s can't be nil", assign.Name(), assert.Name()),
report.ShortRange(),
report.FilterGenerated())
}
for _, m := range code.Matches(pass, checkAssertNotNilFn2Q) {
ifstmt := m.State["ifstmt"].(*ast.IfStmt)
lhs := m.State["lhs"].(types.Object)
assignIdent := m.State["ok"].(types.Object)
report.Report(pass, ifstmt, fmt.Sprintf("when %s is true, %s can't be nil", assignIdent.Name(), lhs.Name()),
report.ShortRange(),
report.FilterGenerated())
}
return nil, nil
}
================================================
FILE: simple/s1020/s1020_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1020
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1020/testdata/go1.0/CheckAssertNotNil/LintAssertNotNil.go
================================================
package pkg
func fn(i interface{}, x interface{}) {
if _, ok := i.(string); ok && i != nil { //@ diag(`when ok is true, i can't be nil`)
}
if _, ok := i.(string); i != nil && ok { //@ diag(`when ok is true, i can't be nil`)
}
if _, ok := i.(string); i != nil || ok {
}
if _, ok := i.(string); i != nil && !ok {
}
if _, ok := i.(string); i == nil && ok {
}
if i != nil {
if _, ok := i.(string); ok { //@ diag(`when ok is true, i can't be nil`)
}
}
if i != nil {
// This gets flagged because of https://github.com/dominikh/go-tools/issues/1047
if _, ok := i.(string); ok { //@ diag(`when ok is true, i can't be nil`)
} else {
//
}
}
if i != nil {
if _, ok := i.(string); ok {
} else {
println()
}
}
if i != nil {
if _, ok := i.(string); ok {
}
println(i)
}
if i == nil {
if _, ok := i.(string); ok {
}
}
if i != nil {
if _, ok := i.(string); !ok {
}
}
if x != nil {
if _, ok := i.(string); ok {
}
}
if i != nil {
if _, ok := x.(string); ok {
}
}
}
================================================
FILE: simple/s1021/s1021.go
================================================
package s1021
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1021",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Merge variable declaration and assignment`,
Before: `
var x uint
x = 1`,
After: `var x uint = 1`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
hasMultipleAssignments := func(root ast.Node, ident *ast.Ident) bool {
num := 0
ast.Inspect(root, func(node ast.Node) bool {
if num >= 2 {
return false
}
assign, ok := node.(*ast.AssignStmt)
if !ok {
return true
}
for _, lhs := range assign.Lhs {
if oident, ok := lhs.(*ast.Ident); ok {
if pass.TypesInfo.ObjectOf(oident) == pass.TypesInfo.ObjectOf(ident) {
num++
}
}
}
return true
})
return num >= 2
}
fn := func(node ast.Node) {
block := node.(*ast.BlockStmt)
if len(block.List) < 2 {
return
}
for i, stmt := range block.List[:len(block.List)-1] {
_ = i
decl, ok := stmt.(*ast.DeclStmt)
if !ok {
continue
}
gdecl, ok := decl.Decl.(*ast.GenDecl)
if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 {
continue
}
vspec, ok := gdecl.Specs[0].(*ast.ValueSpec)
if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 {
continue
}
assign, ok := block.List[i+1].(*ast.AssignStmt)
if !ok || assign.Tok != token.ASSIGN {
continue
}
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
continue
}
ident, ok := assign.Lhs[0].(*ast.Ident)
if !ok {
continue
}
if pass.TypesInfo.ObjectOf(vspec.Names[0]) != pass.TypesInfo.ObjectOf(ident) {
continue
}
if code.RefersTo(pass, assign.Rhs[0], pass.TypesInfo.ObjectOf(ident)) {
continue
}
if hasMultipleAssignments(block, ident) {
continue
}
r := &ast.GenDecl{
Specs: []ast.Spec{
&ast.ValueSpec{
Names: vspec.Names,
Values: []ast.Expr{assign.Rhs[0]},
Type: vspec.Type,
},
},
Tok: gdecl.Tok,
}
report.Report(pass, decl, "should merge variable declaration with assignment on next line",
report.FilterGenerated(),
report.Fixes(edit.Fix("Merge declaration with assignment", edit.ReplaceWithNode(pass.Fset, edit.Range{decl.Pos(), assign.End()}, r))))
}
}
code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
return nil, nil
}
================================================
FILE: simple/s1021/s1021_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1021
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1021/testdata/go1.0/CheckDeclareAssign/LintDeclareAssign.go
================================================
package pkg
func fn() {
var x int //@ diag(`should merge variable declaration with assignment on next line`)
x = 1
_ = x
var y interface{} //@ diag(`should merge variable declaration with assignment on next line`)
y = 1
_ = y
if true {
var x string //@ diag(`should merge variable declaration with assignment on next line`)
x = ""
_ = x
}
var z []string
z = append(z, "")
_ = z
var f func()
f = func() { f() }
_ = f
var a int
a = 1
a = 2
_ = a
var b int
b = 1
// do stuff
b = 2
_ = b
var c int
unrelated = 1
_ = c
}
var unrelated int
================================================
FILE: simple/s1021/testdata/go1.0/CheckDeclareAssign/LintDeclareAssign.go.golden
================================================
package pkg
func fn() {
var x int = 1
_ = x
var y interface{} = 1
_ = y
if true {
var x string = ""
_ = x
}
var z []string
z = append(z, "")
_ = z
var f func()
f = func() { f() }
_ = f
var a int
a = 1
a = 2
_ = a
var b int
b = 1
// do stuff
b = 2
_ = b
var c int
unrelated = 1
_ = c
}
var unrelated int
================================================
FILE: simple/s1023/s1023.go
================================================
package s1023
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1023",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Omit redundant control flow`,
Text: `Functions that have no return value do not need a return statement as
the final statement of the function.
Switches in Go do not have automatic fallthrough, unlike languages
like C. It is not necessary to have a break statement as the final
statement in a case block.`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn1 := func(node ast.Node) {
clause := node.(*ast.CaseClause)
if len(clause.Body) < 2 {
return
}
branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt)
if !ok || branch.Tok != token.BREAK || branch.Label != nil {
return
}
report.Report(pass, branch, "redundant break statement", report.FilterGenerated())
}
fn2 := func(node ast.Node) {
var ret *ast.FieldList
var body *ast.BlockStmt
switch x := node.(type) {
case *ast.FuncDecl:
ret = x.Type.Results
body = x.Body
case *ast.FuncLit:
ret = x.Type.Results
body = x.Body
default:
lint.ExhaustiveTypeSwitch(node)
}
// if the func has results, a return can't be redundant.
// similarly, if there are no statements, there can be
// no return.
if ret != nil || body == nil || len(body.List) < 1 {
return
}
rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt)
if !ok {
return
}
// we don't need to check rst.Results as we already
// checked x.Type.Results to be nil.
report.Report(pass, rst, "redundant return statement", report.FilterGenerated())
}
code.Preorder(pass, fn1, (*ast.CaseClause)(nil))
code.Preorder(pass, fn2, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
return nil, nil
}
================================================
FILE: simple/s1023/s1023_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1023
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1023/testdata/go1.0/CheckRedundantBreak/LintRedundantBreak.go
================================================
package pkg
func fn(x int) {
switch x {
case 1:
println()
break //@ diag(`redundant break`)
case 2:
println()
case 3:
break // don't flag cases only consisting of break
case 4:
println()
break
println()
case 5:
println()
if true {
break // we don't currently detect this
}
case 6:
println()
if true {
break
}
println()
}
label:
for {
switch x {
case 1:
println()
break label
}
}
}
================================================
FILE: simple/s1023/testdata/go1.0/CheckRedundantReturn/LintRedundantReturn.go
================================================
package pkg
func fn1() {
return //@ diag(`redundant return`)
}
func fn2(a int) {
return //@ diag(`redundant return`)
}
func fn3() int {
return 3
}
func fn4() (n int) {
return
}
func fn5(b bool) {
if b {
return
}
}
func fn6() {
return
println("foo")
}
func fn7() {
return
println("foo")
return //@ diag(`redundant return`)
}
func fn8() {
_ = func() {
return //@ diag(`redundant return`)
}
}
================================================
FILE: simple/s1024/s1024.go
================================================
package s1024
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1024",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Replace \'x.Sub(time.Now())\' with \'time.Until(x)\'`,
Text: `The \'time.Until\' helper has the same effect as using \'x.Sub(time.Now())\'
but is easier to read.`,
Before: `x.Sub(time.Now())`,
After: `time.Until(x)`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkTimeUntilQ = pattern.MustParse(`(CallExpr (Symbol "(time.Time).Sub") [(CallExpr (Symbol "time.Now") [])])`)
checkTimeUntilR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Until")) [arg])`)
)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, checkTimeUntilQ) {
if sel, ok := node.(*ast.CallExpr).Fun.(*ast.SelectorExpr); ok {
r := pattern.NodeToAST(checkTimeUntilR.Root, map[string]any{"arg": sel.X}).(ast.Node)
report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())",
report.FilterGenerated(),
report.MinimumStdlibVersion("go1.8"),
report.Fixes(edit.Fix("Replace with call to time.Until", edit.ReplaceWithNode(pass.Fset, node, r))))
} else {
report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())",
report.MinimumStdlibVersion("go1.8"),
report.FilterGenerated())
}
}
return nil, nil
}
================================================
FILE: simple/s1024/s1024_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1024
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1024/testdata/go1.7/CheckTimeUntil/LimeTimeUntil.go
================================================
package pkg
import "time"
func fn(t time.Time) {
t.Sub(time.Now())
}
================================================
FILE: simple/s1024/testdata/go1.8/CheckTimeUntil/LimeTimeUntil.go
================================================
package pkg
import "time"
func fn(t time.Time) {
t.Sub(time.Now()) //@ diag(`time.Until`)
t.Sub(t)
t2 := time.Now()
t.Sub(t2)
}
================================================
FILE: simple/s1024/testdata/go1.8/CheckTimeUntil/LimeTimeUntil.go.golden
================================================
package pkg
import "time"
func fn(t time.Time) {
time.Until(t) //@ diag(`time.Until`)
t.Sub(t)
t2 := time.Now()
t.Sub(t2)
}
================================================
FILE: simple/s1025/s1025.go
================================================
package s1025
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/exp/typeparams"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1025",
Run: run,
Requires: append([]*analysis.Analyzer{
buildir.Analyzer,
generated.Analyzer,
}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Don't use \'fmt.Sprintf("%s", x)\' unnecessarily`,
Text: `In many instances, there are easier and more efficient ways of getting
a value's string representation. Whenever a value's underlying type is
a string already, or the type has a String method, they should be used
directly.
Given the following shared definitions
type T1 string
type T2 int
func (T2) String() string { return "Hello, world" }
var x string
var y T1
var z T2
we can simplify
fmt.Sprintf("%s", x)
fmt.Sprintf("%s", y)
fmt.Sprintf("%s", z)
to
x
string(y)
z.String()
`,
Since: "2017.1",
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkRedundantSprintfQ = pattern.MustParse(`(CallExpr (Symbol "fmt.Sprintf") [format arg])`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkRedundantSprintfQ) {
format := m.State["format"].(ast.Expr)
arg := m.State["arg"].(ast.Expr)
// TODO(dh): should we really support named constants here?
// shouldn't we only look for string literals? to avoid false
// positives via build tags?
if s, ok := code.ExprToString(pass, format); !ok || s != "%s" {
continue
}
typ := pass.TypesInfo.TypeOf(arg)
if typeparams.IsTypeParam(typ) {
continue
}
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
if typeutil.IsTypeWithName(typ, "reflect.Value") {
// printing with %s produces output different from using
// the String method
continue
}
if isFormatter(typ, &irpkg.Prog.MethodSets) {
// the type may choose to handle %s in arbitrary ways
continue
}
if types.Implements(typ, knowledge.Interfaces["fmt.Stringer"]) {
replacement := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: arg,
Sel: &ast.Ident{Name: "String"},
},
}
report.Report(pass, node, "should use String() instead of fmt.Sprintf",
report.Fixes(edit.Fix("Replace with call to String method", edit.ReplaceWithNode(pass.Fset, node, replacement))))
} else if types.Unalias(typ) == types.Universe.Lookup("string").Type() {
report.Report(pass, node, "the argument is already a string, there's no need to use fmt.Sprintf",
report.FilterGenerated(),
report.Fixes(edit.Fix("Remove unnecessary call to fmt.Sprintf", edit.ReplaceWithNode(pass.Fset, node, arg))))
} else if typ.Underlying() == types.Universe.Lookup("string").Type() {
replacement := &ast.CallExpr{
Fun: &ast.Ident{Name: "string"},
Args: []ast.Expr{arg},
}
report.Report(pass, node, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf",
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement))))
} else if code.IsOfStringConvertibleByteSlice(pass, arg) {
replacement := &ast.CallExpr{
Fun: &ast.Ident{Name: "string"},
Args: []ast.Expr{arg},
}
report.Report(pass, node, "the argument's underlying type is a slice of bytes, should use a simple conversion instead of fmt.Sprintf",
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement))))
}
}
return nil, nil
}
func isFormatter(T types.Type, msCache *typeutil.MethodSetCache) bool {
// TODO(dh): this function also exists in staticcheck/lint.go – deduplicate.
ms := msCache.MethodSet(T)
sel := ms.Lookup(nil, "Format")
if sel == nil {
return false
}
fn, ok := sel.Obj().(*types.Func)
if !ok {
// should be unreachable
return false
}
sig := fn.Type().(*types.Signature)
if sig.Params().Len() != 2 {
return false
}
// TODO(dh): check the types of the arguments for more
// precision
if sig.Results().Len() != 0 {
return false
}
return true
}
================================================
FILE: simple/s1025/s1025_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1025
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1025/testdata/go1.0/CheckRedundantSprintf/LintRedundantSprintf.go
================================================
package pkg
import "fmt"
type T1 string
type T2 T1
type T3 int
type T4 int
type T5 int
type T6 string
type T7 []byte
type T9 string
type T10 []byte
type T11 int
type MyByte byte
func (T3) String() string { return "" }
func (T6) String() string { return "" }
func (T4) String(arg int) string { return "" }
func (T5) String() {}
func (T9) Format(f fmt.State, c rune) {}
func (T10) Format(f fmt.State, c rune) {}
func (T11) Format(f fmt.State, c rune) {}
func (T11) String() string { return "" }
func fn() {
var t1 T1
var t2 T2
var t3 T3
var t4 T4
var t5 T5
var t6 T6
var t7 T7
var t9 T9
var t10 T10
var t11 T11
_ = fmt.Sprintf("%s", "test") //@ diag(`is already a string`)
_ = fmt.Sprintf("%s", t1) //@ diag(`is a string`)
_ = fmt.Sprintf("%s", t2) //@ diag(`is a string`)
_ = fmt.Sprintf("%s", t3) //@ diag(`should use String() instead of fmt.Sprintf`)
_ = fmt.Sprintf("%s", t3.String()) //@ diag(`is already a string`)
_ = fmt.Sprintf("%s", t4)
_ = fmt.Sprintf("%s", t5)
_ = fmt.Sprintf("%s %s", t1, t2)
_ = fmt.Sprintf("%v", t1)
_ = fmt.Sprintf("%s", t6) //@ diag(`should use String() instead of fmt.Sprintf`)
_ = fmt.Sprintf("%s", t7) //@ diag(`underlying type is a slice of bytes`)
// don't simplify types that implement fmt.Formatter
_ = fmt.Sprintf("%s", t9)
_ = fmt.Sprintf("%s", t10)
_ = fmt.Sprintf("%s", t11)
}
================================================
FILE: simple/s1025/testdata/go1.0/CheckRedundantSprintf/LintRedundantSprintf.go.golden
================================================
package pkg
import "fmt"
type T1 string
type T2 T1
type T3 int
type T4 int
type T5 int
type T6 string
type T7 []byte
type T9 string
type T10 []byte
type T11 int
type MyByte byte
func (T3) String() string { return "" }
func (T6) String() string { return "" }
func (T4) String(arg int) string { return "" }
func (T5) String() {}
func (T9) Format(f fmt.State, c rune) {}
func (T10) Format(f fmt.State, c rune) {}
func (T11) Format(f fmt.State, c rune) {}
func (T11) String() string { return "" }
func fn() {
var t1 T1
var t2 T2
var t3 T3
var t4 T4
var t5 T5
var t6 T6
var t7 T7
var t9 T9
var t10 T10
var t11 T11
_ = "test" //@ diag(`is already a string`)
_ = string(t1) //@ diag(`is a string`)
_ = string(t2) //@ diag(`is a string`)
_ = t3.String() //@ diag(`should use String() instead of fmt.Sprintf`)
_ = t3.String() //@ diag(`is already a string`)
_ = fmt.Sprintf("%s", t4)
_ = fmt.Sprintf("%s", t5)
_ = fmt.Sprintf("%s %s", t1, t2)
_ = fmt.Sprintf("%v", t1)
_ = t6.String() //@ diag(`should use String() instead of fmt.Sprintf`)
_ = string(t7) //@ diag(`underlying type is a slice of bytes`)
// don't simplify types that implement fmt.Formatter
_ = fmt.Sprintf("%s", t9)
_ = fmt.Sprintf("%s", t10)
_ = fmt.Sprintf("%s", t11)
}
================================================
FILE: simple/s1025/testdata/go1.17/CheckRedundantSprintf/LintRedundantSprintf.go
================================================
package pkg
import "fmt"
type MyByte byte
type T1 []MyByte
func fn() {
var t1 T1
_ = fmt.Sprintf("%s", t1)
}
================================================
FILE: simple/s1025/testdata/go1.17/CheckRedundantSprintf/LintRedundantSprintf.go.golden
================================================
package pkg
import "fmt"
type MyByte byte
type T1 []MyByte
func fn() {
var t1 T1
_ = fmt.Sprintf("%s", t1)
}
================================================
FILE: simple/s1025/testdata/go1.18/CheckRedundantSprintf/LintRedundantSprintf.go
================================================
package pkg
import "fmt"
type MyByte byte
type T1 []MyByte
func fn() {
var t1 T1
_ = fmt.Sprintf("%s", t1) //@ diag(`underlying type is a slice of bytes`)
}
================================================
FILE: simple/s1025/testdata/go1.18/CheckRedundantSprintf/LintRedundantSprintf.go.golden
================================================
package pkg
import "fmt"
type MyByte byte
type T1 []MyByte
func fn() {
var t1 T1
_ = string(t1) //@ diag(`underlying type is a slice of bytes`)
}
================================================
FILE: simple/s1025/testdata/go1.9/CheckRedundantSprintf/LintRedundantSprintf.go
================================================
package pkg
import "fmt"
type T6 string
type T9 string
type MyByte byte
type Alias = byte
type T13 = []byte
type T14 = []Alias
type T15 = string
type T16 = T9
type T17 = T6
func (T6) String() string { return "" }
func (T9) Format(f fmt.State, c rune) {}
func fn() {
var t12 []Alias
var t13 T13
var t14 T14
var t15 T15
var t16 T16
var t17 T17
_ = fmt.Sprintf("%s", t12) //@ diag(`underlying type is a slice of bytes`)
_ = fmt.Sprintf("%s", t13) //@ diag(`underlying type is a slice of bytes`)
_ = fmt.Sprintf("%s", t14) //@ diag(`underlying type is a slice of bytes`)
_ = fmt.Sprintf("%s", t15) //@ diag(`is already a string`)
_ = fmt.Sprintf("%s", t17) //@ diag(`should use String() instead of fmt.Sprintf`)
// don't simplify types that implement fmt.Formatter
_ = fmt.Sprintf("%s", t16)
}
================================================
FILE: simple/s1025/testdata/go1.9/CheckRedundantSprintf/LintRedundantSprintf.go.golden
================================================
package pkg
import "fmt"
type T6 string
type T9 string
type MyByte byte
type Alias = byte
type T13 = []byte
type T14 = []Alias
type T15 = string
type T16 = T9
type T17 = T6
func (T6) String() string { return "" }
func (T9) Format(f fmt.State, c rune) {}
func fn() {
var t12 []Alias
var t13 T13
var t14 T14
var t15 T15
var t16 T16
var t17 T17
_ = string(t12) //@ diag(`underlying type is a slice of bytes`)
_ = string(t13) //@ diag(`underlying type is a slice of bytes`)
_ = string(t14) //@ diag(`underlying type is a slice of bytes`)
_ = t15 //@ diag(`is already a string`)
_ = t17.String() //@ diag(`should use String() instead of fmt.Sprintf`)
// don't simplify types that implement fmt.Formatter
_ = fmt.Sprintf("%s", t16)
}
================================================
FILE: simple/s1028/s1028.go
================================================
package s1028
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1028",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Simplify error construction with \'fmt.Errorf\'`,
Before: `errors.New(fmt.Sprintf(...))`,
After: `fmt.Errorf(...)`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkErrorsNewSprintfQ = pattern.MustParse(`(CallExpr (Symbol "errors.New") [(CallExpr (Symbol "fmt.Sprintf") args)])`)
checkErrorsNewSprintfR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "fmt") (Ident "Errorf")) args)`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkErrorsNewSprintfQ) {
edits := code.EditMatch(pass, node, m, checkErrorsNewSprintfR)
// TODO(dh): the suggested fix may leave an unused import behind
report.Report(pass, node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))",
report.FilterGenerated(),
report.Fixes(edit.Fix("Use fmt.Errorf", edits...)))
}
return nil, nil
}
================================================
FILE: simple/s1028/s1028_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1028
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1028/testdata/go1.0/CheckErrorsNewSprintf/LintErrorsNewSprintf.go
================================================
package pkg
import (
"errors"
"fmt"
)
func fn() {
_ = fmt.Errorf("%d", 0)
_ = errors.New("")
_ = errors.New(fmt.Sprintf("%d", 0)) //@ diag(`should use fmt.Errorf`)
}
================================================
FILE: simple/s1028/testdata/go1.0/CheckErrorsNewSprintf/LintErrorsNewSprintf.go.golden
================================================
package pkg
import (
"errors"
"fmt"
)
func fn() {
_ = fmt.Errorf("%d", 0)
_ = errors.New("")
_ = fmt.Errorf("%d", 0) //@ diag(`should use fmt.Errorf`)
}
================================================
FILE: simple/s1029/s1029.go
================================================
package s1029
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/internal/sharedcheck"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1029",
Run: sharedcheck.CheckRangeStringRunes,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Range over the string directly`,
Text: `Ranging over a string will yield byte offsets and runes. If the offset
isn't used, this is functionally equivalent to converting the string
to a slice of runes and ranging over that. Ranging directly over the
string will be more performant, however, as it avoids allocating a new
slice, the size of which depends on the length of the string.`,
Before: `for _, r := range []rune(s) {}`,
After: `for _, r := range s {}`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
================================================
FILE: simple/s1029/s1029_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1029
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1029/testdata/go1.0/CheckRangeStringRunes/LintRangeStringRunes.go
================================================
package pkg
type String string
func fn(s string, s2 String) {
for _, r := range s {
println(r)
}
for _, r := range []rune(s) { //@ diag(`should range over string`)
println(r)
}
for i, r := range []rune(s) {
println(i)
println(r)
}
x := []rune(s)
for _, r := range x { //@ diag(`should range over string`)
println(r)
}
y := []rune(s)
for _, r := range y {
println(r)
}
println(y[0])
for _, r := range []rune(s2) { //@ diag(`should range over string`)
println(r)
}
}
================================================
FILE: simple/s1029/testdata/go1.18/CheckRangeStringRunes/generics.go
================================================
package pkg
func tpfn1[T string](x T) {
for _, c := range []rune(x) { //@ diag(`should range over string`)
println(c)
}
}
func tpfn2[T1 string, T2 []rune](x T1) {
for _, c := range T2(x) { //@ diag(`should range over string`)
println(c)
}
}
================================================
FILE: simple/s1030/s1030.go
================================================
package s1030
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1030",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use \'bytes.Buffer.String\' or \'bytes.Buffer.Bytes\'`,
Text: `\'bytes.Buffer\' has both a \'String\' and a \'Bytes\' method. It is almost never
necessary to use \'string(buf.Bytes())\' or \'[]byte(buf.String())\' – simply
use the other method.
The only exception to this are map lookups. Due to a compiler optimization,
\'m[string(buf.Bytes())]\' is more efficient than \'m[buf.String()]\'.
`,
Since: "2017.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkBytesBufferConversionsQ = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`)
checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`)
checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`)
)
func run(pass *analysis.Pass) (any, error) {
if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
// The bytes package can use itself however it wants
return nil, nil
}
fn := func(c inspector.Cursor) {
node := c.Node()
m, ok := code.Match(pass, checkBytesBufferConversionsQ, node)
if !ok {
return
}
call := node.(*ast.CallExpr)
sel := m.State["sel"].(*ast.SelectorExpr)
typ := pass.TypesInfo.TypeOf(call.Fun)
if types.Unalias(typ) == types.Universe.Lookup("string").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).Bytes") {
if _, ok := c.Parent().Node().(*ast.IndexExpr); ok {
// Don't flag m[string(buf.Bytes())] – thanks to a
// compiler optimization, this is actually faster than
// m[buf.String()]
return
}
report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
report.FilterGenerated(),
report.Fixes(edit.Fix("Simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRs, m.State))))
} else if typ, ok := types.Unalias(typ).(*types.Slice); ok &&
types.Unalias(typ.Elem()) == types.Universe.Lookup("byte").Type() &&
code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") {
report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)),
report.FilterGenerated(),
report.Fixes(edit.Fix("Simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRb, m.State))))
}
}
for c := range code.Cursor(pass).Preorder((*ast.CallExpr)(nil)) {
fn(c)
}
return nil, nil
}
================================================
FILE: simple/s1030/s1030_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1030
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1030/testdata/go1.0/CheckBytesBufferConversions/LintBytesBufferConversions.go
================================================
package pkg
import (
"bytes"
)
func fn() {
buf := bytes.NewBufferString("str")
_ = string(buf.Bytes()) //@ diag(`should use buf.String() instead of string(buf.Bytes())`)
_ = []byte(buf.String()) //@ diag(`should use buf.Bytes() instead of []byte(buf.String())`)
m := map[string]*bytes.Buffer{"key": buf}
_ = string(m["key"].Bytes()) //@ diag(`should use m["key"].String() instead of string(m["key"].Bytes())`)
_ = []byte(m["key"].String()) //@ diag(`should use m["key"].Bytes() instead of []byte(m["key"].String())`)
var m2 map[string]int
_ = m2[string(buf.Bytes())] // no warning, this is more efficient than buf.String()
string := func(_ interface{}) interface{} {
return nil
}
_ = string(m["key"].Bytes())
}
================================================
FILE: simple/s1030/testdata/go1.0/CheckBytesBufferConversions/LintBytesBufferConversions.go.golden
================================================
package pkg
import (
"bytes"
)
func fn() {
buf := bytes.NewBufferString("str")
_ = buf.String() //@ diag(`should use buf.String() instead of string(buf.Bytes())`)
_ = buf.Bytes() //@ diag(`should use buf.Bytes() instead of []byte(buf.String())`)
m := map[string]*bytes.Buffer{"key": buf}
_ = m["key"].String() //@ diag(`should use m["key"].String() instead of string(m["key"].Bytes())`)
_ = m["key"].Bytes() //@ diag(`should use m["key"].Bytes() instead of []byte(m["key"].String())`)
var m2 map[string]int
_ = m2[string(buf.Bytes())] // no warning, this is more efficient than buf.String()
string := func(_ interface{}) interface{} {
return nil
}
_ = string(m["key"].Bytes())
}
================================================
FILE: simple/s1030/testdata/go1.9/CheckBytesBufferConversions/LintBytesBufferConversions.go
================================================
package pkg
import (
"bytes"
)
type AliasByte = byte
type AliasSlice = []byte
type AliasSlice2 = []AliasByte
func fn() {
buf := bytes.NewBufferString("str")
m := map[string]*bytes.Buffer{"key": buf}
_ = []AliasByte(m["key"].String()) //@ diag(`should use m["key"].Bytes() instead of []AliasByte(m["key"].String())`)
_ = AliasSlice(m["key"].String()) //@ diag(`should use m["key"].Bytes() instead of AliasSlice(m["key"].String())`)
_ = AliasSlice2(m["key"].String()) //@ diag(`should use m["key"].Bytes() instead of AliasSlice2(m["key"].String())`)
}
================================================
FILE: simple/s1030/testdata/go1.9/CheckBytesBufferConversions/LintBytesBufferConversions.go.golden
================================================
package pkg
import (
"bytes"
)
type AliasByte = byte
type AliasSlice = []byte
type AliasSlice2 = []AliasByte
func fn() {
buf := bytes.NewBufferString("str")
m := map[string]*bytes.Buffer{"key": buf}
_ = m["key"].Bytes() //@ diag(`should use m["key"].Bytes() instead of []AliasByte(m["key"].String())`)
_ = m["key"].Bytes() //@ diag(`should use m["key"].Bytes() instead of AliasSlice(m["key"].String())`)
_ = m["key"].Bytes() //@ diag(`should use m["key"].Bytes() instead of AliasSlice2(m["key"].String())`)
}
================================================
FILE: simple/s1031/s1031.go
================================================
package s1031
import (
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1031",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Omit redundant nil check around loop`,
Text: `You can use range on nil slices and maps, the loop will simply never
execute. This makes an additional nil check around the loop
unnecessary.`,
Before: `
if s != nil {
for _, x := range s {
...
}
}`,
After: `
for _, x := range s {
...
}`,
Since: "2017.1",
// MergeIfAll because x might be a channel under some build tags.
// you shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkNilCheckAroundRangeQ = pattern.MustParse(`
(IfStmt
nil
(BinaryExpr x@(Object _) "!=" (Builtin "nil"))
[(RangeStmt _ _ _ x _)]
nil)`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkNilCheckAroundRangeQ) {
ok := typeutil.All(m.State["x"].(types.Object).Type(), func(term *types.Term) bool {
switch term.Type().Underlying().(type) {
case *types.Slice, *types.Map:
return true
case *types.TypeParam, *types.Chan, *types.Pointer, *types.Signature:
return false
default:
lint.ExhaustiveTypeSwitch(term.Type().Underlying())
return false
}
})
if ok {
report.Report(pass, node, "unnecessary nil check around range", report.ShortRange(), report.FilterGenerated())
}
}
return nil, nil
}
================================================
FILE: simple/s1031/s1031_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1031
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1031/testdata/go1.0/CheckNilCheckAroundRange/LintNilCheckAroundRange.go
================================================
package pkg
import "fmt"
func main() {
str := []string{}
// range outside nil check should not match
for _, s := range str {
s = s + "B"
}
// body with multiple statements should not match
if str != nil {
str = append(str, "C")
for _, s := range str {
s = s + "D"
}
}
if str != nil { //@ diag(`unnecessary nil check around range`)
for _, s := range str {
s = s + "A"
}
}
var nilMap map[string]int
if nilMap != nil { //@ diag(`unnecessary nil check around range`)
for key, value := range nilMap {
nilMap[key] = value + 1
}
}
// range over channel can have nil check, as it is required to avoid blocking
var nilChan chan int
if nilChan != nil {
for v := range nilChan {
fmt.Println(v)
}
}
}
================================================
FILE: simple/s1031/testdata/go1.18/CheckNilCheckAroundRange/CheckNilCheckAroundRange.go
================================================
package pkg
func _[T int | string](x []T) {
if x != nil { //@ diag(`unnecessary nil check around range`)
for range x {
}
}
}
func _[T int | string, S []T](x S) {
if x != nil { //@ diag(`unnecessary nil check around range`)
for range x {
}
}
}
func _[T []string](x T) {
if x != nil { //@ diag(`unnecessary nil check around range`)
for range x {
}
}
}
func _[T chan int](x T) {
if x != nil {
for range x {
}
}
}
func _[T any, S chan T](x S) {
if x != nil {
for range x {
}
}
}
================================================
FILE: simple/s1032/s1032.go
================================================
package s1032
import (
"go/ast"
"go/token"
"sort"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1032",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`,
Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to
read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\'
and \'sort.Sort(sort.StringSlice(x))\'.`,
Before: `sort.Sort(sort.StringSlice(x))`,
After: `sort.Strings(x)`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool {
call := node.(*ast.CallExpr)
typeconv, ok := call.Args[0].(*ast.CallExpr)
if !ok {
return true
}
sel, ok := typeconv.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
name := code.SelectorName(pass, sel)
switch name {
case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice":
default:
return true
}
return false
}
func run(pass *analysis.Pass) (any, error) {
type Error struct {
node ast.Node
msg string
}
var allErrors []Error
fn := func(node ast.Node) {
var body *ast.BlockStmt
switch node := node.(type) {
case *ast.FuncLit:
body = node.Body
case *ast.FuncDecl:
body = node.Body
default:
lint.ExhaustiveTypeSwitch(node)
}
if body == nil {
return
}
var errors []Error
permissible := false
fnSorts := func(node ast.Node) bool {
if permissible {
return false
}
if !code.IsCallTo(pass, node, "sort.Sort") {
return true
}
if isPermissibleSort(pass, node) {
permissible = true
return false
}
call := node.(*ast.CallExpr)
// isPermissibleSort guarantees that this type assertion will succeed
typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr)
sel := typeconv.Fun.(*ast.SelectorExpr)
name := code.SelectorName(pass, sel)
switch name {
case "sort.IntSlice":
errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"})
case "sort.Float64Slice":
errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"})
case "sort.StringSlice":
errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"})
}
return true
}
ast.Inspect(body, fnSorts)
if permissible {
return
}
allErrors = append(allErrors, errors...)
}
code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil))
sort.Slice(allErrors, func(i, j int) bool {
return allErrors[i].node.Pos() < allErrors[j].node.Pos()
})
var prev token.Pos
for _, err := range allErrors {
if err.node.Pos() == prev {
continue
}
prev = err.node.Pos()
report.Report(pass, err.node, err.msg, report.FilterGenerated())
}
return nil, nil
}
================================================
FILE: simple/s1032/s1032_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1032
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1032/testdata/go1.0/CheckSortHelpers/LintSortHelpers.go
================================================
package pkg
import "sort"
type MyIntSlice []int
func (s MyIntSlice) Len() int { return 0 }
func (s MyIntSlice) Less(i, j int) bool { return true }
func (s MyIntSlice) Swap(i, j int) {}
func fn1() {
var a []int
sort.Sort(sort.IntSlice(a)) //@ diag(`sort.Ints`)
}
func fn2() {
var b []float64
sort.Sort(sort.Float64Slice(b)) //@ diag(`sort.Float64s`)
}
func fn3() {
var c []string
sort.Sort(sort.StringSlice(c)) //@ diag(`sort.Strings`)
}
func fn4() {
var a []int
sort.Sort(MyIntSlice(a))
}
func fn5() {
var d MyIntSlice
sort.Sort(d)
}
func fn6() {
var e sort.Interface
sort.Sort(e)
}
func fn7() {
// Don't recommend sort.Ints when there was another legitimate
// sort.Sort call already
var a []int
var e sort.Interface
sort.Sort(e)
sort.Sort(sort.IntSlice(a))
}
func fn8() {
var a []int
sort.Sort(sort.IntSlice(a)) //@ diag(`sort.Ints`)
sort.Sort(sort.IntSlice(a)) //@ diag(`sort.Ints`)
}
func fn9() {
func() {
var a []int
sort.Sort(sort.IntSlice(a)) //@ diag(`sort.Ints`)
}()
}
func fn10() {
var a MyIntSlice
sort.Sort(sort.IntSlice(a)) //@ diag(`sort.Ints`)
}
================================================
FILE: simple/s1033/s1033.go
================================================
package s1033
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1033",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Unnecessary guard around call to \"delete\"`,
Text: `Calling \'delete\' on a nil map is a no-op.`,
Since: "2019.2",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkGuardedDeleteQ = pattern.MustParse(`
(IfStmt
(AssignStmt
[(Ident "_") ok@(Ident _)]
":="
(IndexExpr m key))
ok
[call@(CallExpr (Builtin "delete") [m key])]
nil)`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkGuardedDeleteQ) {
report.Report(pass, node, "unnecessary guard around call to delete",
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Remove guard", edit.ReplaceWithNode(pass.Fset, node, m.State["call"].(ast.Node)))))
}
return nil, nil
}
================================================
FILE: simple/s1033/s1033_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1033
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1033/testdata/go1.0/CheckGuardedDelete/LintGuardedDelete.go
================================================
// Package pkg ...
package pkg
func fn(m map[int]int) {
if _, ok := m[0]; ok { //@ diag(`unnecessary guard`)
delete(m, 0)
}
if _, ok := m[0]; !ok {
delete(m, 0)
}
if _, ok := m[0]; ok {
println("deleting")
delete(m, 0)
}
if v, ok := m[0]; ok && v > 0 {
delete(m, 0)
}
var key int
if _, ok := m[key]; ok { //@ diag(`unnecessary guard`)
delete(m, key)
}
if _, ok := m[key]; ok {
delete(m, 0)
}
if _, ok := m[key]; ok {
delete(m, key)
} else {
println("not deleted")
}
var ok bool
if _, ok = m[key]; ok {
delete(m, 0)
}
if ok {
println("deleted")
}
delete := func(a, b interface{}) {}
if _, ok := m[0]; ok {
delete(m, 0)
}
}
================================================
FILE: simple/s1033/testdata/go1.0/CheckGuardedDelete/LintGuardedDelete.go.golden
================================================
// Package pkg ...
package pkg
func fn(m map[int]int) {
delete(m, 0)
if _, ok := m[0]; !ok {
delete(m, 0)
}
if _, ok := m[0]; ok {
println("deleting")
delete(m, 0)
}
if v, ok := m[0]; ok && v > 0 {
delete(m, 0)
}
var key int
delete(m, key)
if _, ok := m[key]; ok {
delete(m, 0)
}
if _, ok := m[key]; ok {
delete(m, key)
} else {
println("not deleted")
}
var ok bool
if _, ok = m[key]; ok {
delete(m, 0)
}
if ok {
println("deleted")
}
delete := func(a, b interface{}) {}
if _, ok := m[0]; ok {
delete(m, 0)
}
}
================================================
FILE: simple/s1034/s1034.go
================================================
package s1034
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1034",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Use result of type assertion to simplify cases`,
Since: "2019.2",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkSimplifyTypeSwitchQ = pattern.MustParse(`
(TypeSwitchStmt
nil
expr@(TypeAssertExpr ident@(Ident _) _)
body)`)
checkSimplifyTypeSwitchR = pattern.MustParse(`(AssignStmt ident ":=" expr)`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkSimplifyTypeSwitchQ) {
stmt := node.(*ast.TypeSwitchStmt)
expr := m.State["expr"].(ast.Node)
ident := m.State["ident"].(*ast.Ident)
x := pass.TypesInfo.ObjectOf(ident)
var allOffenders []*ast.TypeAssertExpr
canSuggestFix := true
for _, clause := range stmt.Body.List {
clause := clause.(*ast.CaseClause)
if len(clause.List) != 1 {
continue
}
hasUnrelatedAssertion := false
var offenders []*ast.TypeAssertExpr
ast.Inspect(clause, func(node ast.Node) bool {
assert2, ok := node.(*ast.TypeAssertExpr)
if !ok {
return true
}
ident, ok := assert2.X.(*ast.Ident)
if !ok {
hasUnrelatedAssertion = true
return false
}
if pass.TypesInfo.ObjectOf(ident) != x {
hasUnrelatedAssertion = true
return false
}
if !types.Identical(pass.TypesInfo.TypeOf(clause.List[0]), pass.TypesInfo.TypeOf(assert2.Type)) {
hasUnrelatedAssertion = true
return false
}
offenders = append(offenders, assert2)
return true
})
if !hasUnrelatedAssertion {
// don't flag cases that have other type assertions
// unrelated to the one in the case clause. often
// times, this is done for symmetry, when two
// different values have to be asserted to the same
// type.
allOffenders = append(allOffenders, offenders...)
}
canSuggestFix = canSuggestFix && !hasUnrelatedAssertion
}
if len(allOffenders) != 0 {
var opts []report.Option
for _, offender := range allOffenders {
opts = append(opts, report.Related(offender, "could eliminate this type assertion"))
}
opts = append(opts, report.FilterGenerated())
msg := fmt.Sprintf("assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate type assertions in switch cases",
report.Render(pass, ident), report.Render(pass, ident))
if canSuggestFix {
var edits []analysis.TextEdit
edits = append(edits, edit.ReplaceWithPattern(pass.Fset, expr, checkSimplifyTypeSwitchR, m.State))
for _, offender := range allOffenders {
edits = append(edits, edit.ReplaceWithNode(pass.Fset, offender, offender.X))
}
opts = append(opts, report.Fixes(edit.Fix("Simplify type switch", edits...)))
report.Report(pass, expr, msg, opts...)
} else {
report.Report(pass, expr, msg, opts...)
}
}
}
return nil, nil
}
================================================
FILE: simple/s1034/s1034_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1034
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1034/testdata/go1.0/CheckSimplifyTypeSwitch/LintSimplifyTypeSwitch.go
================================================
package pkg
import "fmt"
func gen() interface{} { return nil }
func fn(x, y interface{}) {
switch z := x.(type) {
case int:
_ = z
fmt.Println(x.(int))
}
switch x.(type) {
case int:
fmt.Println(x.(int), y.(int))
}
switch x.(type) { //@ diag(`assigning the result of this type assertion`)
case int:
fmt.Println(x.(int))
}
switch x.(type) {
case int:
fmt.Println(x.(string))
}
switch x.(type) {
case int:
fmt.Println(y.(int))
}
switch (gen()).(type) {
case int:
fmt.Println((gen()).(int))
}
}
================================================
FILE: simple/s1034/testdata/go1.0/CheckSimplifyTypeSwitch/LintSimplifyTypeSwitch.go.golden
================================================
package pkg
import "fmt"
func gen() interface{} { return nil }
func fn(x, y interface{}) {
switch z := x.(type) {
case int:
_ = z
fmt.Println(x.(int))
}
switch x.(type) {
case int:
fmt.Println(x.(int), y.(int))
}
switch x := x.(type) { //@ diag(`assigning the result of this type assertion`)
case int:
fmt.Println(x)
}
switch x.(type) {
case int:
fmt.Println(x.(string))
}
switch x.(type) {
case int:
fmt.Println(y.(int))
}
switch (gen()).(type) {
case int:
fmt.Println((gen()).(int))
}
}
================================================
FILE: simple/s1035/s1035.go
================================================
package s1035
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1035",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Redundant call to \'net/http.CanonicalHeaderKey\' in method call on \'net/http.Header\'`,
Text: `
The methods on \'net/http.Header\', namely \'Add\', \'Del\', \'Get\'
and \'Set\', already canonicalize the given header name.`,
Since: "2020.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`
(CallExpr
(Symbol
callName@(Or
"(net/http.Header).Add"
"(net/http.Header).Del"
"(net/http.Header).Get"
"(net/http.Header).Set"))
arg@(CallExpr (Symbol "net/http.CanonicalHeaderKey") _):_)`)
func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, query) {
arg := m.State["arg"].(*ast.CallExpr)
report.Report(pass, m.State["arg"].(ast.Expr),
fmt.Sprintf("calling net/http.CanonicalHeaderKey on the 'key' argument of %s is redundant", m.State["callName"].(string)),
report.FilterGenerated(),
report.Fixes(edit.Fix("Remove call to CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, arg, arg.Args[0]))))
}
return nil, nil
}
================================================
FILE: simple/s1035/s1035_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1035
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1035/testdata/go1.0/CheckRedundantCanonicalHeaderKey/LintRedundantCanonicalHeaderKey.go
================================================
package pkg
import (
"net/http"
"strings"
)
func fn1() {
var headers http.Header
// Matches
headers.Add(http.CanonicalHeaderKey("test"), "test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Del(http.CanonicalHeaderKey("test")) //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Get(http.CanonicalHeaderKey("test")) //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Set(http.CanonicalHeaderKey("test"), "test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
// Non-matches
headers.Add("test", "test")
headers.Del("test")
headers.Get("test")
headers.Set("test", "test")
headers.Add("test", http.CanonicalHeaderKey("test"))
headers.Set("test", http.CanonicalHeaderKey("test"))
headers.Add(http.CanonicalHeaderKey("test")+"1", "test")
headers.Del(http.CanonicalHeaderKey("test") + "1")
headers.Get(http.CanonicalHeaderKey("test") + "1")
headers.Set(http.CanonicalHeaderKey("test")+"1", "test")
headers.Add(strings.ToUpper(http.CanonicalHeaderKey("test")), "test")
headers.Del(strings.ToUpper(http.CanonicalHeaderKey("test")))
headers.Get(strings.ToUpper(http.CanonicalHeaderKey("test")))
headers.Set(strings.ToUpper(http.CanonicalHeaderKey("test")), "test")
}
================================================
FILE: simple/s1035/testdata/go1.0/CheckRedundantCanonicalHeaderKey/LintRedundantCanonicalHeaderKey.go.golden
================================================
package pkg
import (
"net/http"
"strings"
)
func fn1() {
var headers http.Header
// Matches
headers.Add("test", "test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Del("test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Get("test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
headers.Set("test", "test") //@ diag(`calling net/http.CanonicalHeaderKey on the 'key' argument of`)
// Non-matches
headers.Add("test", "test")
headers.Del("test")
headers.Get("test")
headers.Set("test", "test")
headers.Add("test", http.CanonicalHeaderKey("test"))
headers.Set("test", http.CanonicalHeaderKey("test"))
headers.Add(http.CanonicalHeaderKey("test")+"1", "test")
headers.Del(http.CanonicalHeaderKey("test") + "1")
headers.Get(http.CanonicalHeaderKey("test") + "1")
headers.Set(http.CanonicalHeaderKey("test")+"1", "test")
headers.Add(strings.ToUpper(http.CanonicalHeaderKey("test")), "test")
headers.Del(strings.ToUpper(http.CanonicalHeaderKey("test")))
headers.Get(strings.ToUpper(http.CanonicalHeaderKey("test")))
headers.Set(strings.ToUpper(http.CanonicalHeaderKey("test")), "test")
}
================================================
FILE: simple/s1036/s1036.go
================================================
package s1036
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1036",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Unnecessary guard around map access`,
Text: `
When accessing a map key that doesn't exist yet, one receives a zero
value. Often, the zero value is a suitable value, for example when
using append or doing integer math.
The following
if _, ok := m["foo"]; ok {
m["foo"] = append(m["foo"], "bar")
} else {
m["foo"] = []string{"bar"}
}
can be simplified to
m["foo"] = append(m["foo"], "bar")
and
if _, ok := m2["k"]; ok {
m2["k"] += 4
} else {
m2["k"] = 4
}
can be simplified to
m["k"] += 4
`,
Since: "2020.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkUnnecessaryGuardQ = pattern.MustParse(`
(Or
(IfStmt
(AssignStmt [(Ident "_") ok@(Ident _)] ":=" indexexpr@(IndexExpr _ _))
ok
set@(AssignStmt indexexpr "=" (CallExpr (Builtin "append") indexexpr:values))
(AssignStmt indexexpr "=" (CompositeLit _ values)))
(IfStmt
(AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
ok
set@(AssignStmt indexexpr "+=" value)
(AssignStmt indexexpr "=" value))
(IfStmt
(AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _))
ok
set@(IncDecStmt indexexpr "++")
(AssignStmt indexexpr "=" (IntegerLiteral "1"))))`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkUnnecessaryGuardQ) {
if code.MayHaveSideEffects(pass, m.State["indexexpr"].(ast.Expr), nil) {
continue
}
report.Report(pass, node, "unnecessary guard around map access",
report.ShortRange(),
report.Fixes(edit.Fix("Simplify map access", edit.ReplaceWithNode(pass.Fset, node, m.State["set"].(ast.Node)))))
}
return nil, nil
}
================================================
FILE: simple/s1036/s1036_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1036
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1036/testdata/go1.0/CheckUnnecessaryGuard/LintUnnecessaryGuard.go
================================================
package pkg
func fn() {
var m = map[string][]string{}
if _, ok := m["k1"]; ok { //@ diag(`unnecessary guard around map access`)
m["k1"] = append(m["k1"], "v1", "v2")
} else {
m["k1"] = []string{"v1", "v2"}
}
if _, ok := m["k1"]; ok {
m["k1"] = append(m["k1"], "v1", "v2")
} else {
m["k1"] = []string{"v1"}
}
if _, ok := m["k1"]; ok {
m["k2"] = append(m["k2"], "v1")
} else {
m["k1"] = []string{"v1"}
}
k1 := "key"
if _, ok := m[k1]; ok { //@ diag(`unnecessary guard around map access`)
m[k1] = append(m[k1], "v1", "v2")
} else {
m[k1] = []string{"v1", "v2"}
}
// ellipsis is not currently supported
v := []string{"v1", "v2"}
if _, ok := m["k1"]; ok {
m["k1"] = append(m["k1"], v...)
} else {
m["k1"] = v
}
var m2 map[string]int
if _, ok := m2["k"]; ok { //@ diag(`unnecessary guard around map access`)
m2["k"] += 4
} else {
m2["k"] = 4
}
if _, ok := m2["k"]; ok {
m2["k"] += 4
} else {
m2["k"] = 3
}
if _, ok := m2["k"]; ok { //@ diag(`unnecessary guard around map access`)
m2["k"]++
} else {
m2["k"] = 1
}
if _, ok := m2["k"]; ok {
m2["k"] -= 1
} else {
m2["k"] = 1
}
}
// this used to cause a panic in the pattern package
func fn2() {
var obj interface{}
if _, ok := obj.(map[string]interface{})["items"]; ok {
obj.(map[string]interface{})["version"] = 1
}
}
================================================
FILE: simple/s1036/testdata/go1.0/CheckUnnecessaryGuard/LintUnnecessaryGuard.go.golden
================================================
package pkg
func fn() {
var m = map[string][]string{}
m["k1"] = append(m["k1"], "v1", "v2")
if _, ok := m["k1"]; ok {
m["k1"] = append(m["k1"], "v1", "v2")
} else {
m["k1"] = []string{"v1"}
}
if _, ok := m["k1"]; ok {
m["k2"] = append(m["k2"], "v1")
} else {
m["k1"] = []string{"v1"}
}
k1 := "key"
m[k1] = append(m[k1], "v1", "v2")
// ellipsis is not currently supported
v := []string{"v1", "v2"}
if _, ok := m["k1"]; ok {
m["k1"] = append(m["k1"], v...)
} else {
m["k1"] = v
}
var m2 map[string]int
m2["k"] += 4
if _, ok := m2["k"]; ok {
m2["k"] += 4
} else {
m2["k"] = 3
}
m2["k"]++
if _, ok := m2["k"]; ok {
m2["k"] -= 1
} else {
m2["k"] = 1
}
}
// this used to cause a panic in the pattern package
func fn2() {
var obj interface{}
if _, ok := obj.(map[string]interface{})["items"]; ok {
obj.(map[string]interface{})["version"] = 1
}
}
================================================
FILE: simple/s1037/s1037.go
================================================
package s1037
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1037",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Elaborate way of sleeping`,
Text: `Using a select statement with a single case receiving
from the result of \'time.After\' is a very elaborate way of sleeping that
can much simpler be expressed with a simple call to time.Sleep.`,
Since: "2020.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkElaborateSleepQ = pattern.MustParse(`(SelectStmt (CommClause (UnaryExpr "<-" (CallExpr (Symbol "time.After") [arg])) body))`)
checkElaborateSleepR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Sleep")) [arg])`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkElaborateSleepQ) {
if body, ok := m.State["body"].([]ast.Stmt); ok && len(body) == 0 {
report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Use time.Sleep", edit.ReplaceWithPattern(pass.Fset, node, checkElaborateSleepR, m.State))))
} else {
// TODO(dh): we could make a suggested fix if the body
// doesn't declare or shadow any identifiers
report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping",
report.ShortRange(),
report.FilterGenerated())
}
}
return nil, nil
}
================================================
FILE: simple/s1037/s1037_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1037
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1037/testdata/go1.0/CheckElaborateSleep/LintElaborateSleep.go
================================================
package pkg
import (
"fmt"
"time"
)
func fn() {
time.Sleep(0)
select { //@ diag(`should use time.Sleep`)
case <-time.After(0):
}
select { //@ diag(`should use time.Sleep`)
case <-time.After(0):
fmt.Println("yay")
}
const d = 0
select { //@ diag(`should use time.Sleep`)
case <-time.After(d):
}
var ch chan int
select {
case <-time.After(0):
case <-ch:
}
}
================================================
FILE: simple/s1037/testdata/go1.0/CheckElaborateSleep/LintElaborateSleep.go.golden
================================================
package pkg
import (
"fmt"
"time"
)
func fn() {
time.Sleep(0)
time.Sleep(0)
select { //@ diag(`should use time.Sleep`)
case <-time.After(0):
fmt.Println("yay")
}
const d = 0
time.Sleep(d)
var ch chan int
select {
case <-time.After(0):
case <-ch:
}
}
================================================
FILE: simple/s1038/s1038.go
================================================
package s1038
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1038",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: "Unnecessarily complex way of printing formatted string",
Text: `Instead of using \'fmt.Print(fmt.Sprintf(...))\', one can use \'fmt.Printf(...)\'.`,
Since: "2020.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkPrintSprintQ = pattern.MustParse(`
(Or
(CallExpr
fn@(Or
(Symbol "fmt.Print")
(Symbol "fmt.Sprint")
(Symbol "fmt.Println")
(Symbol "fmt.Sprintln"))
[(CallExpr (Symbol "fmt.Sprintf") f:_)])
(CallExpr
fn@(Or
(Symbol "fmt.Fprint")
(Symbol "fmt.Fprintln"))
[_ (CallExpr (Symbol "fmt.Sprintf") f:_)]))`)
checkTestingErrorSprintfQ = pattern.MustParse(`
(CallExpr
sel@(SelectorExpr
recv
(Ident
name@(Or
"Error"
"Fatal"
"Fatalln"
"Log"
"Panic"
"Panicln"
"Print"
"Println"
"Skip")))
[(CallExpr (Symbol "fmt.Sprintf") args)])`)
checkLogSprintfQ = pattern.MustParse(`
(CallExpr
(Symbol
(Or
"log.Fatal"
"log.Fatalln"
"log.Panic"
"log.Panicln"
"log.Print"
"log.Println"))
[(CallExpr (Symbol "fmt.Sprintf") args)])`)
checkSprintfMapping = map[string]struct {
recv string
alternative string
}{
"(*testing.common).Error": {"(*testing.common)", "Errorf"},
"(testing.TB).Error": {"(testing.TB)", "Errorf"},
"(*testing.common).Fatal": {"(*testing.common)", "Fatalf"},
"(testing.TB).Fatal": {"(testing.TB)", "Fatalf"},
"(*testing.common).Log": {"(*testing.common)", "Logf"},
"(testing.TB).Log": {"(testing.TB)", "Logf"},
"(*testing.common).Skip": {"(*testing.common)", "Skipf"},
"(testing.TB).Skip": {"(testing.TB)", "Skipf"},
"(*log.Logger).Fatal": {"(*log.Logger)", "Fatalf"},
"(*log.Logger).Fatalln": {"(*log.Logger)", "Fatalf"},
"(*log.Logger).Panic": {"(*log.Logger)", "Panicf"},
"(*log.Logger).Panicln": {"(*log.Logger)", "Panicf"},
"(*log.Logger).Print": {"(*log.Logger)", "Printf"},
"(*log.Logger).Println": {"(*log.Logger)", "Printf"},
"log.Fatal": {"", "log.Fatalf"},
"log.Fatalln": {"", "log.Fatalf"},
"log.Panic": {"", "log.Panicf"},
"log.Panicln": {"", "log.Panicf"},
"log.Print": {"", "log.Printf"},
"log.Println": {"", "log.Printf"},
}
)
func run(pass *analysis.Pass) (any, error) {
fmtPrintf := func(node ast.Node) {
m, ok := code.Match(pass, checkPrintSprintQ, node)
if !ok {
return
}
name := m.State["fn"].(*types.Func).Name()
var msg string
switch name {
case "Print", "Fprint", "Sprint":
newname := name + "f"
msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...))", newname, name)
case "Println", "Fprintln", "Sprintln":
if _, ok := m.State["f"].(*ast.BasicLit); !ok {
// This may be an instance of
// fmt.Println(fmt.Sprintf(arg, ...)) where arg is an
// externally provided format string and the caller
// cannot guarantee that the format string ends with a
// newline.
return
}
newname := name[:len(name)-2] + "f"
msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...)) (but don't forget the newline)", newname, name)
}
report.Report(pass, node, msg,
report.FilterGenerated())
}
methSprintf := func(node ast.Node) {
m, ok := code.Match(pass, checkTestingErrorSprintfQ, node)
if !ok {
return
}
mapped, ok := checkSprintfMapping[code.CallName(pass, node.(*ast.CallExpr))]
if !ok {
return
}
// Ensure that Errorf/Fatalf refer to the right method
recvTV, ok := pass.TypesInfo.Types[m.State["recv"].(ast.Expr)]
if !ok {
return
}
obj, _, _ := types.LookupFieldOrMethod(recvTV.Type, recvTV.Addressable(), nil, mapped.alternative)
f, ok := obj.(*types.Func)
if !ok {
return
}
if typeutil.FuncName(f) != mapped.recv+"."+mapped.alternative {
return
}
alt := &ast.SelectorExpr{
X: m.State["recv"].(ast.Expr),
Sel: &ast.Ident{Name: mapped.alternative},
}
report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", report.Render(pass, alt), report.Render(pass, m.State["sel"].(*ast.SelectorExpr))))
}
pkgSprintf := func(node ast.Node) {
_, ok := code.Match(pass, checkLogSprintfQ, node)
if !ok {
return
}
callName := code.CallName(pass, node.(*ast.CallExpr))
mapped, ok := checkSprintfMapping[callName]
if !ok {
return
}
report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", mapped.alternative, callName))
}
fn := func(node ast.Node) {
fmtPrintf(node)
// TODO(dh): add suggested fixes
methSprintf(node)
pkgSprintf(node)
}
if !code.CouldMatchAny(pass, checkLogSprintfQ, checkPrintSprintQ, checkTestingErrorSprintfQ) {
return nil, nil
}
code.Preorder(pass, fn, (*ast.CallExpr)(nil))
return nil, nil
}
================================================
FILE: simple/s1038/s1038_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1038
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1038/testdata/go1.0/CheckPrintSprintf/CheckPrintSprintf.go
================================================
package pkg
import (
"fmt"
"log"
"testing"
)
func fn() {
fmt.Print(fmt.Sprintf("%d", 1)) //@ diag(`should use fmt.Printf`)
fmt.Println(fmt.Sprintf("%d", 1)) //@ diag(`don't forget the newline`)
fmt.Fprint(nil, fmt.Sprintf("%d", 1)) //@ diag(`should use fmt.Fprintf`)
fmt.Fprintln(nil, fmt.Sprintf("%d", 1)) //@ diag(`don't forget the newline`)
fmt.Sprint(fmt.Sprintf("%d", 1)) //@ diag(`should use fmt.Sprintf`)
fmt.Sprintln(fmt.Sprintf("%d", 1)) //@ diag(`don't forget the newline`)
arg := "%d"
fmt.Println(fmt.Sprintf(arg, 1))
}
func Sprintf(string) string { return "" }
type Embedding1 struct {
*testing.T
}
type Embedding2 struct {
*testing.T
}
func (e Embedding2) Errorf() {}
type Embedding3 struct {
*testing.T
}
func (e Embedding3) Error(string, ...interface{}) {}
type Embedding4 struct {
testing.TB
}
func fn2() {
var t *testing.T
var b *testing.B
var tb testing.TB
// All of these are the basic cases that should be flagged
t.Error(fmt.Sprintf("")) //@ diag(`use t.Errorf(...) instead of t.Error(fmt.Sprintf(...))`)
b.Error(fmt.Sprintf("")) //@ diag(`use b.Errorf`)
tb.Error(fmt.Sprintf("")) //@ diag(`use tb.Errorf`)
t.Fatal(fmt.Sprintf("")) //@ diag(`use t.Fatalf`)
b.Fatal(fmt.Sprintf("")) //@ diag(`use b.Fatalf`)
tb.Fatal(fmt.Sprintf("")) //@ diag(`use tb.Fatalf`)
t.Log(fmt.Sprintf("")) //@ diag(`use t.Logf`)
b.Log(fmt.Sprintf("")) //@ diag(`use b.Logf`)
tb.Log(fmt.Sprintf("")) //@ diag(`use tb.Logf`)
t.Skip(fmt.Sprintf("")) //@ diag(`use t.Skipf`)
b.Skip(fmt.Sprintf("")) //@ diag(`use b.Skipf`)
tb.Skip(fmt.Sprintf("")) //@ diag(`use tb.Skipf`)
var e1 Embedding1
var e2 Embedding2
var e3 Embedding3
var e4 Embedding4
// Error and Errorf are both of *testing.common -> flag
e1.Error(fmt.Sprintf("")) //@ diag(`use e1.Errorf`)
// Fatal and Fatalf are both of *testing.common -> flag
e1.Fatal(fmt.Sprintf("")) //@ diag(`use e1.Fatalf`)
// Error is of *testing.common, but Errorf is Embedding2.Errorf -> don't flag
e2.Error(fmt.Sprintf(""))
// Fatal and Fatalf are both of *testing.common -> flag
e2.Fatal(fmt.Sprintf("")) //@ diag(`use e2.Fatalf`)
// Error is Embedding3.Error and Errorf is of *testing.common -> don't flag
e3.Error(fmt.Sprintf(""))
// Fatal and Fatalf are both of *testing.common -> flag
e3.Fatal(fmt.Sprintf("")) //@ diag(`use e3.Fatalf`)
// Error and Errorf are both of testing.TB -> flag
e4.Error(fmt.Sprintf("")) //@ diag(`use e4.Errorf`)
// Fatal and Fatalf are both of testing.TB -> flag
e4.Fatal(fmt.Sprintf("")) //@ diag(`use e4.Fatalf`)
// Basic cases
log.Fatal(fmt.Sprintf("")) //@ diag(`use log.Fatalf`)
log.Fatalln(fmt.Sprintf("")) //@ diag(`use log.Fatalf`)
log.Panic(fmt.Sprintf("")) //@ diag(`use log.Panicf`)
log.Panicln(fmt.Sprintf("")) //@ diag(`use log.Panicf`)
log.Print(fmt.Sprintf("")) //@ diag(`use log.Printf`)
log.Println(fmt.Sprintf("")) //@ diag(`use log.Printf`)
var l *log.Logger
l.Fatal(fmt.Sprintf("")) //@ diag(`use l.Fatalf(...) instead of l.Fatal(fmt.Sprintf(...)`)
l.Fatalln(fmt.Sprintf("")) //@ diag(`use l.Fatalf`)
l.Panic(fmt.Sprintf("")) //@ diag(`use l.Panicf`)
l.Panicln(fmt.Sprintf("")) //@ diag(`use l.Panicf`)
l.Print(fmt.Sprintf("")) //@ diag(`use l.Printf`)
l.Println(fmt.Sprintf("")) //@ diag(`use l.Printf`)
// log.Logger and testing.T share a code path, no need to check embedding again
}
================================================
FILE: simple/s1039/s1039.go
================================================
package s1039
import (
"fmt"
"go/ast"
"go/types"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1039",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Unnecessary use of \'fmt.Sprint\'`,
Text: `
Calling \'fmt.Sprint\' with a single string argument is unnecessary
and identical to using the string directly.`,
Since: "2020.1",
// MergeIfAll because s might not be a string under all build tags.
// you shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkSprintLiteralQ = pattern.MustParse(`
(CallExpr
fn@(Or
(Symbol "fmt.Sprint")
(Symbol "fmt.Sprintf"))
[lit@(BasicLit "STRING" _)])`)
func run(pass *analysis.Pass) (any, error) {
// We only flag calls with string literals, not expressions of
// type string, because some people use fmt.Sprint(s) as a pattern
// for copying strings, which may be useful when extracting a small
// substring from a large string.
for node, m := range code.Matches(pass, checkSprintLiteralQ) {
callee := m.State["fn"].(*types.Func)
lit := m.State["lit"].(*ast.BasicLit)
if callee.Name() == "Sprintf" {
if strings.ContainsRune(lit.Value, '%') {
// This might be a format string
continue
}
}
report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()),
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit))))
}
return nil, nil
}
================================================
FILE: simple/s1039/s1039_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1039
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1039/testdata/go1.0/CheckSprintLiteral/CheckSprintLiteral.go
================================================
package pkg
import "fmt"
func fn() {
_ = fmt.Sprint("foo") //@ diag(`unnecessary use of fmt.Sprint`)
_ = fmt.Sprintf("foo") //@ diag(`unnecessary use of fmt.Sprintf`)
_ = fmt.Sprintf("foo %d")
_ = fmt.Sprintf("foo %d", 1)
var x string
_ = fmt.Sprint(x)
}
================================================
FILE: simple/s1039/testdata/go1.0/CheckSprintLiteral/CheckSprintLiteral.go.golden
================================================
package pkg
import "fmt"
func fn() {
_ = "foo" //@ diag(`unnecessary use of fmt.Sprint`)
_ = "foo" //@ diag(`unnecessary use of fmt.Sprintf`)
_ = fmt.Sprintf("foo %d")
_ = fmt.Sprintf("foo %d", 1)
var x string
_ = fmt.Sprint(x)
}
================================================
FILE: simple/s1040/s1040.go
================================================
package s1040
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1040",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Type assertion to current type",
Text: `The type assertion \'x.(SomeInterface)\', when \'x\' already has type
\'SomeInterface\', can only fail if \'x\' is nil. Usually, this is
left-over code from when \'x\' had a different type and you can safely
delete the type assertion. If you want to check that \'x\' is not nil,
consider being explicit and using an actual \'if x == nil\' comparison
instead of relying on the type assertion panicking.`,
Since: "2021.1",
// MergeIfAll because x might have different types under different build tags.
// You shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
expr := node.(*ast.TypeAssertExpr)
if expr.Type == nil {
// skip type switches
//
// TODO(dh): we could flag type switches, too, when a case
// statement has the same type as expr.X – however,
// depending on the location of that case, it might behave
// identically to a default branch. we need to think
// carefully about the instances we want to flag. We also
// have to take nil interface values into consideration.
//
// It might make more sense to extend SA4020 to handle
// this.
return
}
t1 := pass.TypesInfo.TypeOf(expr.Type)
t2 := pass.TypesInfo.TypeOf(expr.X)
if types.IsInterface(t1) && types.Identical(t1, t2) {
report.Report(pass, expr,
fmt.Sprintf("type assertion to the same type: %s already has type %s", report.Render(pass, expr.X), report.Render(pass, expr.Type)),
report.FilterGenerated())
}
}
// TODO(dh): add suggested fixes. we need different fixes depending on the context:
// - assignment with 1 or 2 lhs
// - assignment to blank identifiers (as the first, second or both lhs)
// - initializers in if statements, with the same variations as above
code.Preorder(pass, fn, (*ast.TypeAssertExpr)(nil))
return nil, nil
}
================================================
FILE: simple/s1040/s1040_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package s1040
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: simple/s1040/testdata/go1.0/CheckSameTypeTypeAssertion/CheckSameTypeTypeAssertion.go
================================================
package pkg
type SomeInterface interface {
Foo()
}
func fn(x SomeInterface) {
_ = x.(SomeInterface) //@ diag(`type assertion to the same type: x already has type SomeInterface`)
y := x.(SomeInterface) //@ diag(`type assertion to the same type`)
y = x.(SomeInterface) //@ diag(`type assertion to the same type`)
var a SomeInterface = x.(SomeInterface) //@ diag(`type assertion to the same type`)
z, _ := x.(SomeInterface) //@ diag(`type assertion to the same type`)
z, _ = x.(SomeInterface) //@ diag(`type assertion to the same type`)
_, ok := x.(SomeInterface) //@ diag(`type assertion to the same type`)
_, ok = x.(SomeInterface) //@ diag(`type assertion to the same type`)
_, _ = x.(SomeInterface) //@ diag(`type assertion to the same type`)
if z, ok := x.(SomeInterface); ok { //@ diag(`type assertion to the same type`)
_ = z
}
if _, ok := x.(SomeInterface); !ok { //@ diag(`type assertion to the same type`)
}
if _, ok = x.(SomeInterface); !ok { //@ diag(`type assertion to the same type`)
}
if z, ok = x.(SomeInterface); ok { //@ diag(`type assertion to the same type`)
}
if z := x.(SomeInterface); true { //@ diag(`type assertion to the same type`)
_ = z
}
if z, _ := x.(SomeInterface); true { //@ diag(`type assertion to the same type`)
_ = z
}
if _, _ = x.(SomeInterface); true { //@ diag(`type assertion to the same type`)
}
if _ = x.(SomeInterface); true { //@ diag(`type assertion to the same type`)
}
switch x.(type) {
case SomeInterface:
}
_ = a
_ = y
_ = ok
_ = z
}
================================================
FILE: staticcheck/analysis.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package staticcheck
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/staticcheck/sa1000"
"honnef.co/go/tools/staticcheck/sa1001"
"honnef.co/go/tools/staticcheck/sa1002"
"honnef.co/go/tools/staticcheck/sa1003"
"honnef.co/go/tools/staticcheck/sa1004"
"honnef.co/go/tools/staticcheck/sa1005"
"honnef.co/go/tools/staticcheck/sa1006"
"honnef.co/go/tools/staticcheck/sa1007"
"honnef.co/go/tools/staticcheck/sa1008"
"honnef.co/go/tools/staticcheck/sa1010"
"honnef.co/go/tools/staticcheck/sa1011"
"honnef.co/go/tools/staticcheck/sa1012"
"honnef.co/go/tools/staticcheck/sa1013"
"honnef.co/go/tools/staticcheck/sa1014"
"honnef.co/go/tools/staticcheck/sa1015"
"honnef.co/go/tools/staticcheck/sa1016"
"honnef.co/go/tools/staticcheck/sa1017"
"honnef.co/go/tools/staticcheck/sa1018"
"honnef.co/go/tools/staticcheck/sa1019"
"honnef.co/go/tools/staticcheck/sa1020"
"honnef.co/go/tools/staticcheck/sa1021"
"honnef.co/go/tools/staticcheck/sa1023"
"honnef.co/go/tools/staticcheck/sa1024"
"honnef.co/go/tools/staticcheck/sa1025"
"honnef.co/go/tools/staticcheck/sa1026"
"honnef.co/go/tools/staticcheck/sa1027"
"honnef.co/go/tools/staticcheck/sa1028"
"honnef.co/go/tools/staticcheck/sa1029"
"honnef.co/go/tools/staticcheck/sa1030"
"honnef.co/go/tools/staticcheck/sa1031"
"honnef.co/go/tools/staticcheck/sa1032"
"honnef.co/go/tools/staticcheck/sa2000"
"honnef.co/go/tools/staticcheck/sa2001"
"honnef.co/go/tools/staticcheck/sa2002"
"honnef.co/go/tools/staticcheck/sa2003"
"honnef.co/go/tools/staticcheck/sa3000"
"honnef.co/go/tools/staticcheck/sa3001"
"honnef.co/go/tools/staticcheck/sa4000"
"honnef.co/go/tools/staticcheck/sa4001"
"honnef.co/go/tools/staticcheck/sa4003"
"honnef.co/go/tools/staticcheck/sa4004"
"honnef.co/go/tools/staticcheck/sa4005"
"honnef.co/go/tools/staticcheck/sa4006"
"honnef.co/go/tools/staticcheck/sa4008"
"honnef.co/go/tools/staticcheck/sa4009"
"honnef.co/go/tools/staticcheck/sa4010"
"honnef.co/go/tools/staticcheck/sa4011"
"honnef.co/go/tools/staticcheck/sa4012"
"honnef.co/go/tools/staticcheck/sa4013"
"honnef.co/go/tools/staticcheck/sa4014"
"honnef.co/go/tools/staticcheck/sa4015"
"honnef.co/go/tools/staticcheck/sa4016"
"honnef.co/go/tools/staticcheck/sa4017"
"honnef.co/go/tools/staticcheck/sa4018"
"honnef.co/go/tools/staticcheck/sa4019"
"honnef.co/go/tools/staticcheck/sa4020"
"honnef.co/go/tools/staticcheck/sa4021"
"honnef.co/go/tools/staticcheck/sa4022"
"honnef.co/go/tools/staticcheck/sa4023"
"honnef.co/go/tools/staticcheck/sa4024"
"honnef.co/go/tools/staticcheck/sa4025"
"honnef.co/go/tools/staticcheck/sa4026"
"honnef.co/go/tools/staticcheck/sa4027"
"honnef.co/go/tools/staticcheck/sa4028"
"honnef.co/go/tools/staticcheck/sa4029"
"honnef.co/go/tools/staticcheck/sa4030"
"honnef.co/go/tools/staticcheck/sa4031"
"honnef.co/go/tools/staticcheck/sa4032"
"honnef.co/go/tools/staticcheck/sa5000"
"honnef.co/go/tools/staticcheck/sa5001"
"honnef.co/go/tools/staticcheck/sa5002"
"honnef.co/go/tools/staticcheck/sa5003"
"honnef.co/go/tools/staticcheck/sa5004"
"honnef.co/go/tools/staticcheck/sa5005"
"honnef.co/go/tools/staticcheck/sa5007"
"honnef.co/go/tools/staticcheck/sa5008"
"honnef.co/go/tools/staticcheck/sa5009"
"honnef.co/go/tools/staticcheck/sa5010"
"honnef.co/go/tools/staticcheck/sa5011"
"honnef.co/go/tools/staticcheck/sa5012"
"honnef.co/go/tools/staticcheck/sa6000"
"honnef.co/go/tools/staticcheck/sa6001"
"honnef.co/go/tools/staticcheck/sa6002"
"honnef.co/go/tools/staticcheck/sa6003"
"honnef.co/go/tools/staticcheck/sa6005"
"honnef.co/go/tools/staticcheck/sa6006"
"honnef.co/go/tools/staticcheck/sa9001"
"honnef.co/go/tools/staticcheck/sa9002"
"honnef.co/go/tools/staticcheck/sa9003"
"honnef.co/go/tools/staticcheck/sa9004"
"honnef.co/go/tools/staticcheck/sa9005"
"honnef.co/go/tools/staticcheck/sa9006"
"honnef.co/go/tools/staticcheck/sa9007"
"honnef.co/go/tools/staticcheck/sa9008"
"honnef.co/go/tools/staticcheck/sa9009"
"honnef.co/go/tools/staticcheck/sa9010"
)
var Analyzers = []*lint.Analyzer{
sa1000.SCAnalyzer,
sa1001.SCAnalyzer,
sa1002.SCAnalyzer,
sa1003.SCAnalyzer,
sa1004.SCAnalyzer,
sa1005.SCAnalyzer,
sa1006.SCAnalyzer,
sa1007.SCAnalyzer,
sa1008.SCAnalyzer,
sa1010.SCAnalyzer,
sa1011.SCAnalyzer,
sa1012.SCAnalyzer,
sa1013.SCAnalyzer,
sa1014.SCAnalyzer,
sa1015.SCAnalyzer,
sa1016.SCAnalyzer,
sa1017.SCAnalyzer,
sa1018.SCAnalyzer,
sa1019.SCAnalyzer,
sa1020.SCAnalyzer,
sa1021.SCAnalyzer,
sa1023.SCAnalyzer,
sa1024.SCAnalyzer,
sa1025.SCAnalyzer,
sa1026.SCAnalyzer,
sa1027.SCAnalyzer,
sa1028.SCAnalyzer,
sa1029.SCAnalyzer,
sa1030.SCAnalyzer,
sa1031.SCAnalyzer,
sa1032.SCAnalyzer,
sa2000.SCAnalyzer,
sa2001.SCAnalyzer,
sa2002.SCAnalyzer,
sa2003.SCAnalyzer,
sa3000.SCAnalyzer,
sa3001.SCAnalyzer,
sa4000.SCAnalyzer,
sa4001.SCAnalyzer,
sa4003.SCAnalyzer,
sa4004.SCAnalyzer,
sa4005.SCAnalyzer,
sa4006.SCAnalyzer,
sa4008.SCAnalyzer,
sa4009.SCAnalyzer,
sa4010.SCAnalyzer,
sa4011.SCAnalyzer,
sa4012.SCAnalyzer,
sa4013.SCAnalyzer,
sa4014.SCAnalyzer,
sa4015.SCAnalyzer,
sa4016.SCAnalyzer,
sa4017.SCAnalyzer,
sa4018.SCAnalyzer,
sa4019.SCAnalyzer,
sa4020.SCAnalyzer,
sa4021.SCAnalyzer,
sa4022.SCAnalyzer,
sa4023.SCAnalyzer,
sa4024.SCAnalyzer,
sa4025.SCAnalyzer,
sa4026.SCAnalyzer,
sa4027.SCAnalyzer,
sa4028.SCAnalyzer,
sa4029.SCAnalyzer,
sa4030.SCAnalyzer,
sa4031.SCAnalyzer,
sa4032.SCAnalyzer,
sa5000.SCAnalyzer,
sa5001.SCAnalyzer,
sa5002.SCAnalyzer,
sa5003.SCAnalyzer,
sa5004.SCAnalyzer,
sa5005.SCAnalyzer,
sa5007.SCAnalyzer,
sa5008.SCAnalyzer,
sa5009.SCAnalyzer,
sa5010.SCAnalyzer,
sa5011.SCAnalyzer,
sa5012.SCAnalyzer,
sa6000.SCAnalyzer,
sa6001.SCAnalyzer,
sa6002.SCAnalyzer,
sa6003.SCAnalyzer,
sa6005.SCAnalyzer,
sa6006.SCAnalyzer,
sa9001.SCAnalyzer,
sa9002.SCAnalyzer,
sa9003.SCAnalyzer,
sa9004.SCAnalyzer,
sa9005.SCAnalyzer,
sa9006.SCAnalyzer,
sa9007.SCAnalyzer,
sa9008.SCAnalyzer,
sa9009.SCAnalyzer,
sa9010.SCAnalyzer,
}
================================================
FILE: staticcheck/doc.go
================================================
//go:generate go run ../generate.go
// Package staticcheck contains analyzes that find bugs and performance issues.
// Barring the rare false positive, any code flagged by these analyzes needs to be fixed.
package staticcheck
================================================
FILE: staticcheck/fakejson/encode.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains a modified copy of the encoding/json encoder.
// All dynamic behavior has been removed, and reflecttion has been replaced with go/types.
// This allows us to statically find unmarshable types
// with the same rules for tags, shadowing and addressability as encoding/json.
// This is used for SA1026.
package fakejson
import (
"go/types"
"sort"
"strings"
"unicode"
"golang.org/x/exp/typeparams"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/staticcheck/fakereflect"
)
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) string {
if before, _, ok := strings.Cut(tag, ","); ok {
return before
}
return tag
}
func Marshal(v types.Type) *UnsupportedTypeError {
enc := encoder{}
return enc.newTypeEncoder(fakereflect.TypeAndCanAddr{Type: v}, "x")
}
// An UnsupportedTypeError is returned by Marshal when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type types.Type
Path string
}
type encoder struct {
// TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know
// if this is actually required for correctness.
seenCanAddr typeutil.Map[struct{}]
seenCantAddr typeutil.Map[struct{}]
}
func (enc *encoder) newTypeEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
var m *typeutil.Map[struct{}]
if t.CanAddr() {
m = &enc.seenCanAddr
} else {
m = &enc.seenCantAddr
}
if _, ok := m.At(t.Type); ok {
return nil
}
m.Set(t.Type, struct{}{})
if t.Implements(knowledge.Interfaces["encoding/json.Marshaler"]) {
return nil
}
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding/json.Marshaler"]) {
return nil
}
if t.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
switch t.Type.Underlying().(type) {
case *types.Basic, *types.Interface:
return nil
case *types.Struct:
return enc.typeFields(t, stack)
case *types.Map:
return enc.newMapEncoder(t, stack)
case *types.Slice:
return enc.newSliceEncoder(t, stack)
case *types.Array:
return enc.newArrayEncoder(t, stack)
case *types.Pointer:
// we don't have to express the pointer dereference in the path; x.f is syntactic sugar for (*x).f
return enc.newTypeEncoder(t.Elem(), stack)
default:
return &UnsupportedTypeError{t.Type, stack}
}
}
func (enc *encoder) newMapEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
if typeparams.IsTypeParam(t.Key().Type) {
// We don't know enough about the concrete instantiation to say much about the key. The only time we could make
// a definite "this key is bad" statement is if the type parameter is constrained by type terms, none of which
// are tilde terms, none of which are a basic type. In all other cases, the key might implement TextMarshaler.
// It doesn't seem worth checking for that one single case.
return enc.newTypeEncoder(t.Elem(), stack+"[k]")
}
switch t.Key().Type.Underlying().(type) {
case *types.Basic:
default:
if !t.Key().Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return &UnsupportedTypeError{
Type: t.Type,
Path: stack,
}
}
}
return enc.newTypeEncoder(t.Elem(), stack+"[k]")
}
func (enc *encoder) newSliceEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
// Byte slices get special treatment; arrays don't.
basic, ok := t.Elem().Type.Underlying().(*types.Basic)
if ok && basic.Kind() == types.Uint8 {
p := fakereflect.PtrTo(t.Elem())
if !p.Implements(knowledge.Interfaces["encoding/json.Marshaler"]) && !p.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
}
return enc.newArrayEncoder(t, stack)
}
func (enc *encoder) newArrayEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
return enc.newTypeEncoder(t.Elem(), stack+"[0]")
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
return false
}
}
return true
}
func typeByIndex(t fakereflect.TypeAndCanAddr, index []int) fakereflect.TypeAndCanAddr {
for _, i := range index {
if t.IsPtr() {
t = t.Elem()
}
t = t.Field(i).Type
}
return t
}
func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string {
var path strings.Builder
for _, i := range index {
if t.IsPtr() {
t = t.Elem()
}
path.WriteString("." + t.Field(i).Name)
t = t.Field(i).Type
}
return path.String()
}
// A field represents a single field found in a struct.
type field struct {
name string
tag bool
index []int
typ fakereflect.TypeAndCanAddr
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func (enc *encoder) typeFields(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
var count, nextCount map[fakereflect.TypeAndCanAddr]int
// Types already visited at an earlier level.
visited := map[fakereflect.TypeAndCanAddr]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[fakereflect.TypeAndCanAddr]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.Anonymous {
t := sf.Type
if t.IsPtr() {
t = t.Elem()
}
if !sf.IsExported() && !t.IsStruct() {
// Ignore embedded fields of unexported non-struct types.
continue
}
// Do not ignore embedded fields of unexported struct types
// since they may have exported fields.
} else if !sf.IsExported() {
// Ignore unexported non-embedded fields.
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.IsPtr() {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || !ft.IsStruct() {
tagged := name != ""
if name == "" {
name = sf.Name
}
field := field{
name: name,
tag: tagged,
index: index,
typ: ft,
}
fields = append(fields, field)
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, field{name: ft.Name(), index: index, typ: ft})
}
}
}
}
sort.Slice(fields, func(i, j int) bool {
x := fields
// sort field by name, breaking ties with depth, then
// breaking ties with "name came from json tag", then
// breaking ties with index sequence.
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
})
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
for i := range fields {
f := &fields[i]
err := enc.newTypeEncoder(typeByIndex(t, f.index), stack+pathByIndex(t, f.index))
if err != nil {
return err
}
}
return nil
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order, then by presence of tag.
// That means that the first field is the dominant one. We need only check
// for error cases: two fields at top level, either both tagged or neither tagged.
if len(fields) > 1 && len(fields[0].index) == len(fields[1].index) && fields[0].tag == fields[1].tag {
return field{}, false
}
return fields[0], true
}
================================================
FILE: staticcheck/fakereflect/fakereflect.go
================================================
package fakereflect
import (
"fmt"
"go/types"
"reflect"
)
type TypeAndCanAddr struct {
Type types.Type
canAddr bool
}
type StructField struct {
Index []int
Name string
Anonymous bool
Tag reflect.StructTag
f *types.Var
Type TypeAndCanAddr
}
func (sf StructField) IsExported() bool { return sf.f.Exported() }
func (t TypeAndCanAddr) Field(i int) StructField {
st := t.Type.Underlying().(*types.Struct)
f := st.Field(i)
return StructField{
f: f,
Index: []int{i},
Name: f.Name(),
Anonymous: f.Anonymous(),
Tag: reflect.StructTag(st.Tag(i)),
Type: TypeAndCanAddr{
Type: f.Type(),
canAddr: t.canAddr,
},
}
}
func (t TypeAndCanAddr) FieldByIndex(index []int) StructField {
f := t.Field(index[0])
for _, idx := range index[1:] {
f = f.Type.Field(idx)
}
f.Index = index
return f
}
func PtrTo(t TypeAndCanAddr) TypeAndCanAddr {
// Note that we don't care about canAddr here because it's irrelevant to all uses of PtrTo
return TypeAndCanAddr{Type: types.NewPointer(t.Type)}
}
func (t TypeAndCanAddr) CanAddr() bool { return t.canAddr }
func (t TypeAndCanAddr) Implements(ityp *types.Interface) bool {
return types.Implements(t.Type, ityp)
}
func (t TypeAndCanAddr) IsSlice() bool {
_, ok := t.Type.Underlying().(*types.Slice)
return ok
}
func (t TypeAndCanAddr) IsArray() bool {
_, ok := t.Type.Underlying().(*types.Array)
return ok
}
func (t TypeAndCanAddr) IsPtr() bool {
_, ok := t.Type.Underlying().(*types.Pointer)
return ok
}
func (t TypeAndCanAddr) IsInterface() bool {
_, ok := t.Type.Underlying().(*types.Interface)
return ok
}
func (t TypeAndCanAddr) IsStruct() bool {
_, ok := t.Type.Underlying().(*types.Struct)
return ok
}
func (t TypeAndCanAddr) Name() string {
named, ok := types.Unalias(t.Type).(*types.Named)
if !ok {
return ""
}
return named.Obj().Name()
}
func (t TypeAndCanAddr) NumField() int {
return t.Type.Underlying().(*types.Struct).NumFields()
}
func (t TypeAndCanAddr) String() string {
return t.Type.String()
}
func (t TypeAndCanAddr) Key() TypeAndCanAddr {
return TypeAndCanAddr{Type: t.Type.Underlying().(*types.Map).Key()}
}
func (t TypeAndCanAddr) Elem() TypeAndCanAddr {
switch typ := t.Type.Underlying().(type) {
case *types.Pointer:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: true,
}
case *types.Slice:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: true,
}
case *types.Array:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: t.canAddr,
}
case *types.Map:
return TypeAndCanAddr{
Type: typ.Elem(),
canAddr: false,
}
default:
panic(fmt.Sprintf("unhandled type %T", typ))
}
}
================================================
FILE: staticcheck/fakexml/marshal.go
================================================
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains a modified copy of the encoding/xml encoder.
// All dynamic behavior has been removed, and reflecttion has been replaced with go/types.
// This allows us to statically find unmarshable types
// with the same rules for tags, shadowing and addressability as encoding/xml.
// This is used for SA1026 and SA5008.
// NOTE(dh): we do not check CanInterface in various places, which means we'll accept more marshaler implementations than encoding/xml does. This will lead to a small amount of false negatives.
package fakexml
import (
"fmt"
"go/types"
"strings"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/staticcheck/fakereflect"
)
func Marshal(v types.Type) error {
return NewEncoder().Encode(v)
}
type Encoder struct {
// TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know
// if this is actually required for correctness.
seenCanAddr typeutil.Map[struct{}]
seenCantAddr typeutil.Map[struct{}]
}
func NewEncoder() *Encoder {
e := &Encoder{}
return e
}
func (enc *Encoder) Encode(v types.Type) error {
rv := fakereflect.TypeAndCanAddr{Type: v}
return enc.marshalValue(rv, nil, nil, "x")
}
func implementsMarshaler(v fakereflect.TypeAndCanAddr) bool {
t := v.Type
obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXML")
if obj == nil {
return false
}
fn, ok := obj.(*types.Func)
if !ok {
return false
}
params := fn.Type().(*types.Signature).Params()
if params.Len() != 2 {
return false
}
if !typeutil.IsPointerToTypeWithName(params.At(0).Type(), "encoding/xml.Encoder") {
return false
}
if !typeutil.IsTypeWithName(params.At(1).Type(), "encoding/xml.StartElement") {
return false
}
rets := fn.Type().(*types.Signature).Results()
if rets.Len() != 1 {
return false
}
if !typeutil.IsTypeWithName(rets.At(0).Type(), "error") {
return false
}
return true
}
func implementsMarshalerAttr(v fakereflect.TypeAndCanAddr) bool {
t := v.Type
obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXMLAttr")
if obj == nil {
return false
}
fn, ok := obj.(*types.Func)
if !ok {
return false
}
params := fn.Type().(*types.Signature).Params()
if params.Len() != 1 {
return false
}
if !typeutil.IsTypeWithName(params.At(0).Type(), "encoding/xml.Name") {
return false
}
rets := fn.Type().(*types.Signature).Results()
if rets.Len() != 2 {
return false
}
if !typeutil.IsTypeWithName(rets.At(0).Type(), "encoding/xml.Attr") {
return false
}
if !typeutil.IsTypeWithName(rets.At(1).Type(), "error") {
return false
}
return true
}
type CyclicTypeError struct {
Type types.Type
Path string
}
func (err *CyclicTypeError) Error() string {
return "cyclic type"
}
// marshalValue writes one or more XML elements representing val.
// If val was obtained from a struct field, finfo must have its details.
func (e *Encoder) marshalValue(val fakereflect.TypeAndCanAddr, finfo *fieldInfo, startTemplate *StartElement, stack string) error {
var m *typeutil.Map[struct{}]
if val.CanAddr() {
m = &e.seenCanAddr
} else {
m = &e.seenCantAddr
}
if _, ok := m.At(val.Type); ok {
return nil
}
m.Set(val.Type, struct{}{})
// Drill into interfaces and pointers.
seen := map[fakereflect.TypeAndCanAddr]struct{}{}
for val.IsInterface() || val.IsPtr() {
if val.IsInterface() {
return nil
}
val = val.Elem()
if _, ok := seen[val]; ok {
// Loop in type graph, e.g. 'type P *P'
return &CyclicTypeError{val.Type, stack}
}
seen[val] = struct{}{}
}
// Check for marshaler.
if implementsMarshaler(val) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if implementsMarshaler(pv) {
return nil
}
}
// Check for text marshaler.
if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
}
// Slices and arrays iterate over the elements. They do not have an enclosing tag.
if (val.IsSlice() || val.IsArray()) && !isByteArray(val) && !isByteSlice(val) {
if err := e.marshalValue(val.Elem(), finfo, startTemplate, stack+"[0]"); err != nil {
return err
}
return nil
}
tinfo, err := getTypeInfo(val)
if err != nil {
return err
}
// Create start element.
// Precedence for the XML element name is:
// 0. startTemplate
// 1. XMLName field in underlying struct;
// 2. field name/tag in the struct field; and
// 3. type name
var start StartElement
if startTemplate != nil {
start.Name = startTemplate.Name
start.Attr = append(start.Attr, startTemplate.Attr...)
} else if tinfo.xmlname != nil {
xmlname := tinfo.xmlname
if xmlname.name != "" {
start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name
}
}
// Attributes
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
if finfo.flags&fAttr == 0 {
continue
}
fv := finfo.value(val)
name := Name{Space: finfo.xmlns, Local: finfo.name}
if err := e.marshalAttr(&start, name, fv, stack+pathByIndex(val, finfo.idx)); err != nil {
return err
}
}
if val.IsStruct() {
return e.marshalStruct(tinfo, val, stack)
} else {
return e.marshalSimple(val, stack)
}
}
func isSlice(v fakereflect.TypeAndCanAddr) bool {
_, ok := v.Type.Underlying().(*types.Slice)
return ok
}
func isByteSlice(v fakereflect.TypeAndCanAddr) bool {
slice, ok := v.Type.Underlying().(*types.Slice)
if !ok {
return false
}
basic, ok := slice.Elem().Underlying().(*types.Basic)
if !ok {
return false
}
return basic.Kind() == types.Uint8
}
func isByteArray(v fakereflect.TypeAndCanAddr) bool {
slice, ok := v.Type.Underlying().(*types.Array)
if !ok {
return false
}
basic, ok := slice.Elem().Underlying().(*types.Basic)
if !ok {
return false
}
return basic.Kind() == types.Uint8
}
// marshalAttr marshals an attribute with the given name and value, adding to start.Attr.
func (e *Encoder) marshalAttr(start *StartElement, name Name, val fakereflect.TypeAndCanAddr, stack string) error {
if implementsMarshalerAttr(val) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if implementsMarshalerAttr(pv) {
return nil
}
}
if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
if val.CanAddr() {
pv := fakereflect.PtrTo(val)
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
return nil
}
}
// Dereference or skip nil pointer
if val.IsPtr() {
val = val.Elem()
}
// Walk slices.
if isSlice(val) && !isByteSlice(val) {
if err := e.marshalAttr(start, name, val.Elem(), stack+"[0]"); err != nil {
return err
}
return nil
}
if typeutil.IsTypeWithName(val.Type, "encoding/xml.Attr") {
return nil
}
return e.marshalSimple(val, stack)
}
func (e *Encoder) marshalSimple(val fakereflect.TypeAndCanAddr, stack string) error {
switch val.Type.Underlying().(type) {
case *types.Basic, *types.Interface:
return nil
case *types.Slice, *types.Array:
basic, ok := val.Elem().Type.Underlying().(*types.Basic)
if !ok || basic.Kind() != types.Uint8 {
return &UnsupportedTypeError{val.Type, stack}
}
return nil
default:
return &UnsupportedTypeError{val.Type, stack}
}
}
func indirect(vf fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
for vf.IsPtr() {
vf = vf.Elem()
}
return vf
}
func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string {
var path strings.Builder
for _, i := range index {
if t.IsPtr() {
t = t.Elem()
}
path.WriteString("." + t.Field(i).Name)
t = t.Field(i).Type
}
return path.String()
}
func (e *Encoder) marshalStruct(tinfo *typeInfo, val fakereflect.TypeAndCanAddr, stack string) error {
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
if finfo.flags&fAttr != 0 {
continue
}
vf := finfo.value(val)
switch finfo.flags & fMode {
case fCDATA, fCharData:
if vf.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
continue
}
if vf.CanAddr() {
pv := fakereflect.PtrTo(vf)
if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) {
continue
}
}
continue
case fComment:
vf = indirect(vf)
if !(isByteSlice(vf) || isByteArray(vf)) {
return fmt.Errorf("xml: bad type for comment field of %s", val)
}
continue
case fInnerXML:
vf = indirect(vf)
if t, ok := vf.Type.(*types.Slice); (ok && types.Identical(t.Elem(), types.Typ[types.Byte])) || types.Identical(vf.Type, types.Typ[types.String]) {
continue
}
case fElement, fElement | fAny:
}
if err := e.marshalValue(vf, finfo, nil, stack+pathByIndex(val, finfo.idx)); err != nil {
return err
}
}
return nil
}
// UnsupportedTypeError is returned when Marshal encounters a type
// that cannot be converted into XML.
type UnsupportedTypeError struct {
Type types.Type
Path string
}
func (e *UnsupportedTypeError) Error() string {
return fmt.Sprintf("xml: unsupported type %s, via %s ", e.Type, e.Path)
}
================================================
FILE: staticcheck/fakexml/typeinfo.go
================================================
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fakexml
import (
"fmt"
"strconv"
"strings"
"sync"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/staticcheck/fakereflect"
)
// typeInfo holds details for the xml representation of a type.
type typeInfo struct {
xmlname *fieldInfo
fields []fieldInfo
}
// fieldInfo holds details for the xml representation of a single field.
type fieldInfo struct {
idx []int
name string
xmlns string
flags fieldFlags
parents []string
}
type fieldFlags int
const (
fElement fieldFlags = 1 << iota
fAttr
fCDATA
fCharData
fInnerXML
fComment
fAny
fOmitEmpty
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
xmlName = "XMLName"
)
func (f fieldFlags) String() string {
switch f {
case fAttr:
return "attr"
case fCDATA:
return "cdata"
case fCharData:
return "chardata"
case fInnerXML:
return "innerxml"
case fComment:
return "comment"
case fAny:
return "any"
case fOmitEmpty:
return "omitempty"
case fAny | fAttr:
return "any,attr"
default:
return strconv.Itoa(int(f))
}
}
var tinfoMap sync.Map // map[reflect.Type]*typeInfo
// getTypeInfo returns the typeInfo structure with details necessary
// for marshaling and unmarshaling typ.
func getTypeInfo(typ fakereflect.TypeAndCanAddr) (*typeInfo, error) {
if ti, ok := tinfoMap.Load(typ); ok {
return ti.(*typeInfo), nil
}
tinfo := &typeInfo{}
if typ.IsStruct() && !typeutil.IsTypeWithName(typ.Type, "encoding/xml.Name") {
n := typ.NumField()
for i := range n {
f := typ.Field(i)
if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" {
continue // Private field
}
// For embedded structs, embed its fields.
if f.Anonymous {
t := f.Type
if t.IsPtr() {
t = t.Elem()
}
if t.IsStruct() {
inner, err := getTypeInfo(t)
if err != nil {
return nil, err
}
if tinfo.xmlname == nil {
tinfo.xmlname = inner.xmlname
}
for _, finfo := range inner.fields {
finfo.idx = append([]int{i}, finfo.idx...)
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
return nil, err
}
}
continue
}
}
finfo, err := StructFieldInfo(f)
if err != nil {
return nil, err
}
if f.Name == xmlName {
tinfo.xmlname = finfo
continue
}
// Add the field if it doesn't conflict with other fields.
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
return nil, err
}
}
}
ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
return ti.(*typeInfo), nil
}
// StructFieldInfo builds and returns a fieldInfo for f.
func StructFieldInfo(f fakereflect.StructField) (*fieldInfo, error) {
finfo := &fieldInfo{idx: f.Index}
// Split the tag from the xml namespace if necessary.
tag := f.Tag.Get("xml")
if i := strings.Index(tag, " "); i >= 0 {
finfo.xmlns, tag = tag[:i], tag[i+1:]
}
// Parse flags.
tokens := strings.Split(tag, ",")
if len(tokens) == 1 {
finfo.flags = fElement
} else {
tag = tokens[0]
for _, flag := range tokens[1:] {
switch flag {
case "attr":
finfo.flags |= fAttr
case "cdata":
finfo.flags |= fCDATA
case "chardata":
finfo.flags |= fCharData
case "innerxml":
finfo.flags |= fInnerXML
case "comment":
finfo.flags |= fComment
case "any":
finfo.flags |= fAny
case "omitempty":
finfo.flags |= fOmitEmpty
}
}
// Validate the flags used.
switch mode := finfo.flags & fMode; mode {
case 0:
finfo.flags |= fElement
case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr:
if f.Name == xmlName {
return nil, fmt.Errorf("cannot use option %s on XMLName field", mode)
} else if tag != "" && mode != fAttr {
return nil, fmt.Errorf("cannot specify name together with option ,%s", mode)
}
default:
// This will also catch multiple modes in a single field.
return nil, fmt.Errorf("invalid combination of options: %q", f.Tag.Get("xml"))
}
if finfo.flags&fMode == fAny {
finfo.flags |= fElement
}
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
return nil, fmt.Errorf("can only use omitempty on elements and attributes")
}
}
// Use of xmlns without a name is not allowed.
if finfo.xmlns != "" && tag == "" {
return nil, fmt.Errorf("namespace without name: %q", f.Tag.Get("xml"))
}
if f.Name == xmlName {
// The XMLName field records the XML element name. Don't
// process it as usual because its name should default to
// empty rather than to the field name.
finfo.name = tag
return finfo, nil
}
if tag == "" {
// If the name part of the tag is completely empty, get
// default from XMLName of underlying struct if feasible,
// or field name otherwise.
if xmlname := lookupXMLName(f.Type); xmlname != nil {
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
} else {
finfo.name = f.Name
}
return finfo, nil
}
// Prepare field name and parents.
parents := strings.Split(tag, ">")
if parents[0] == "" {
parents[0] = f.Name
}
if parents[len(parents)-1] == "" {
return nil, fmt.Errorf("trailing '>'")
}
finfo.name = parents[len(parents)-1]
if len(parents) > 1 {
if (finfo.flags & fElement) == 0 {
return nil, fmt.Errorf("%s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
}
finfo.parents = parents[:len(parents)-1]
}
// If the field type has an XMLName field, the names must match
// so that the behavior of both marshaling and unmarshaling
// is straightforward and unambiguous.
if finfo.flags&fElement != 0 {
ftyp := f.Type
xmlname := lookupXMLName(ftyp)
if xmlname != nil && xmlname.name != finfo.name {
return nil, fmt.Errorf("name %q conflicts with name %q in %s.XMLName", finfo.name, xmlname.name, ftyp)
}
}
return finfo, nil
}
// lookupXMLName returns the fieldInfo for typ's XMLName field
// in case it exists and has a valid xml field tag, otherwise
// it returns nil.
func lookupXMLName(typ fakereflect.TypeAndCanAddr) (xmlname *fieldInfo) {
seen := map[fakereflect.TypeAndCanAddr]struct{}{}
for typ.IsPtr() {
typ = typ.Elem()
if _, ok := seen[typ]; ok {
// Loop in type graph, e.g. 'type P *P'
return nil
}
seen[typ] = struct{}{}
}
if !typ.IsStruct() {
return nil
}
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
if f.Name != xmlName {
continue
}
finfo, err := StructFieldInfo(f)
if err == nil && finfo.name != "" {
return finfo
}
// Also consider errors as a non-existent field tag
// and let getTypeInfo itself report the error.
break
}
return nil
}
// addFieldInfo adds finfo to tinfo.fields if there are no
// conflicts, or if conflicts arise from previous fields that were
// obtained from deeper embedded structures than finfo. In the latter
// case, the conflicting entries are dropped.
// A conflict occurs when the path (parent + name) to a field is
// itself a prefix of another path, or when two paths match exactly.
// It is okay for field paths to share a common, shorter prefix.
func addFieldInfo(typ fakereflect.TypeAndCanAddr, tinfo *typeInfo, newf *fieldInfo) error {
var conflicts []int
Loop:
// First, figure all conflicts. Most working code will have none.
for i := range tinfo.fields {
oldf := &tinfo.fields[i]
if oldf.flags&fMode != newf.flags&fMode {
continue
}
if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
continue
}
minl := min(len(newf.parents), len(oldf.parents))
for p := range minl {
if oldf.parents[p] != newf.parents[p] {
continue Loop
}
}
if len(oldf.parents) > len(newf.parents) {
if oldf.parents[len(newf.parents)] == newf.name {
conflicts = append(conflicts, i)
}
} else if len(oldf.parents) < len(newf.parents) {
if newf.parents[len(oldf.parents)] == oldf.name {
conflicts = append(conflicts, i)
}
} else {
if newf.name == oldf.name {
conflicts = append(conflicts, i)
}
}
}
// Without conflicts, add the new field and return.
if conflicts == nil {
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// If any conflict is shallower, ignore the new field.
// This matches the Go field resolution on embedding.
for _, i := range conflicts {
if len(tinfo.fields[i].idx) < len(newf.idx) {
return nil
}
}
// Otherwise, if any of them is at the same depth level, it's an error.
for _, i := range conflicts {
oldf := &tinfo.fields[i]
if len(oldf.idx) == len(newf.idx) {
f1 := typ.FieldByIndex(oldf.idx)
f2 := typ.FieldByIndex(newf.idx)
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
}
}
// Otherwise, the new field is shallower, and thus takes precedence,
// so drop the conflicting fields from tinfo and append the new one.
for c := len(conflicts) - 1; c >= 0; c-- {
i := conflicts[c]
copy(tinfo.fields[i:], tinfo.fields[i+1:])
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
}
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// A TagPathError represents an error in the unmarshaling process
// caused by the use of field tags with conflicting paths.
type TagPathError struct {
Struct fakereflect.TypeAndCanAddr
Field1, Tag1 string
Field2, Tag2 string
}
func (e *TagPathError) Error() string {
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
}
// value returns v's field value corresponding to finfo.
// It's equivalent to v.FieldByIndex(finfo.idx), but when passed
// initNilPointers, it initializes and dereferences pointers as necessary.
// When passed dontInitNilPointers and a nil pointer is reached, the function
// returns a zero reflect.Value.
func (finfo *fieldInfo) value(v fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
for i, x := range finfo.idx {
if i > 0 {
t := v
if t.IsPtr() && t.Elem().IsStruct() {
v = v.Elem()
}
}
v = v.Field(x).Type
}
return v
}
================================================
FILE: staticcheck/fakexml/xml.go
================================================
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fakexml
// References:
// Annotated XML spec: https://www.xml.com/axml/testaxml.htm
// XML name spaces: https://www.w3.org/TR/REC-xml-names/
// TODO(rsc):
// Test error handling.
// A Name represents an XML name (Local) annotated
// with a name space identifier (Space).
// In tokens returned by Decoder.Token, the Space identifier
// is given as a canonical URL, not the short prefix used
// in the document being parsed.
type Name struct {
Space, Local string
}
// An Attr represents an attribute in an XML element (Name=Value).
type Attr struct {
Name Name
Value string
}
// A StartElement represents an XML start element.
type StartElement struct {
Name Name
Attr []Attr
}
================================================
FILE: staticcheck/sa1000/sa1000.go
================================================
package sa1000
import (
"go/constant"
"regexp"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1000",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Invalid regular expression`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"regexp.MustCompile": check,
"regexp.Compile": check,
"regexp.Match": check,
"regexp.MatchReader": check,
"regexp.MatchString": check,
}
func check(call *callcheck.Call) {
arg := call.Args[0]
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
if _, err := regexp.Compile(s); err != nil {
arg.Invalid(err.Error())
}
}
}
================================================
FILE: staticcheck/sa1000/sa1000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1000/testdata/go1.0/CheckRegexps/CheckRegexps.go
================================================
package pkg
import (
"log"
"regexp"
)
const c1 = `[`
const c2 = `(abc)`
var re1 = regexp.MustCompile(`ab\yef`) //@ diag(`error parsing regexp`)
var re2 = regexp.MustCompile(c1) //@ diag(`error parsing regexp`)
var re3 = regexp.MustCompile(c2)
var re4 = regexp.MustCompile(
c1, //@ diag(`error parsing regexp`)
)
func fn() {
_, err := regexp.Compile(`foo(`) //@ diag(`error parsing regexp`)
if err != nil {
panic(err)
}
if re2.MatchString("foo(") {
log.Println("of course 'foo(' matches 'foo('")
}
regexp.Match("foo(", nil) //@ diag(`error parsing regexp`)
regexp.MatchReader("foo(", nil) //@ diag(`error parsing regexp`)
regexp.MatchString("foo(", "") //@ diag(`error parsing regexp`)
}
// must be a basic type to trigger SA4017 (in case of a test failure)
type T string
func (T) Fn() {}
// Don't get confused by methods named init
func (T) init() {}
// this will become a synthetic init function, that we don't want to
// ignore
var _ = regexp.MustCompile("(") //@ diag(`error parsing regexp`)
func fn2() {
regexp.MustCompile("foo(").FindAll(nil, 0) //@ diag(`error parsing regexp`)
}
================================================
FILE: staticcheck/sa1001/sa1001.go
================================================
package sa1001
import (
"go/ast"
htmltemplate "html/template"
"strings"
texttemplate "text/template"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1001",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Invalid template`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`
(CallExpr
(Symbol
name@(Or
"(*text/template.Template).Parse"
"(*html/template.Template).Parse"))
[s])`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, query) {
name := m.State["name"].(string)
var kind string
switch name {
case "(*text/template.Template).Parse":
kind = "text"
case "(*html/template.Template).Parse":
kind = "html"
}
call := node.(*ast.CallExpr)
sel := call.Fun.(*ast.SelectorExpr)
if !code.IsCallToAny(pass, sel.X, "text/template.New", "html/template.New") {
// TODO(dh): this is a cheap workaround for templates with
// different delims. A better solution with less false
// negatives would use data flow analysis to see where the
// template comes from and where it has been
continue
}
s, ok := code.ExprToString(pass, m.State["s"].(ast.Expr))
if !ok {
continue
}
var err error
switch kind {
case "text":
_, err = texttemplate.New("").Parse(s)
case "html":
_, err = htmltemplate.New("").Parse(s)
}
if err != nil {
// TODO(dominikh): whitelist other parse errors, if any
if strings.Contains(err.Error(), "unexpected") ||
strings.Contains(err.Error(), "bad character") {
report.Report(pass, call.Args[knowledge.Arg("(*text/template.Template).Parse.text")], err.Error())
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa1001/sa1001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1001/testdata/go1.0/CheckTemplate/CheckTemplate.go
================================================
package pkg
import (
th "html/template"
tt "text/template"
)
const tmpl1 = `{{.Name}} {{.LastName}`
const tmpl2 = `{{fn}}`
func fn() {
tt.New("").Parse(tmpl1) //@ diag(`template`)
tt.New("").Parse(tmpl2)
t1 := tt.New("")
t1.Parse(tmpl1)
th.New("").Parse(tmpl1) //@ diag(`template`)
th.New("").Parse(tmpl2)
t2 := th.New("")
t2.Parse(tmpl1)
tt.New("").Delims("[[", "]]").Parse("{{abc-}}")
}
================================================
FILE: staticcheck/sa1002/sa1002.go
================================================
package sa1002
import (
"go/constant"
"strings"
"time"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1002",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Invalid format in \'time.Parse\'`,
Text: `\'time.Parse\' requires a layout string that uses Go's reference time:
\"Mon Jan 2 15:04:05 MST 2006\". The layout must represent this date and time
exactly. See https://pkg.go.dev/time#pkg-constants for layout examples.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"time.Parse": func(call *callcheck.Call) {
arg := call.Args[knowledge.Arg("time.Parse.layout")]
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
s = strings.Replace(s, "_", " ", -1)
s = strings.Replace(s, "Z", "-", -1)
_, err := time.Parse(s, s)
if err != nil {
arg.Invalid(err.Error())
}
}
},
}
================================================
FILE: staticcheck/sa1002/sa1002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1002/testdata/go1.0/CheckTimeParse/CheckTimeParse.go
================================================
package pkg
import "time"
const c1 = "12345"
const c2 = "2006"
func fn() {
time.Parse("12345", "") //@ diag(`parsing time`)
time.Parse(c1, "") //@ diag(`parsing time`)
time.Parse(c2, "")
time.Parse(time.RFC3339Nano, "")
time.Parse(time.Kitchen, "")
}
================================================
FILE: staticcheck/sa1003/sa1003.go
================================================
package sa1003
import (
"fmt"
"go/types"
"go/version"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1003",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkEncodingBinaryRules),
},
Doc: &lint.RawDocumentation{
Title: `Unsupported argument to functions in \'encoding/binary\'`,
Text: `The \'encoding/binary\' package can only serialize types with known sizes.
This precludes the use of the \'int\' and \'uint\' types, as their sizes
differ on different architectures. Furthermore, it doesn't support
serializing maps, channels, strings, or functions.
Before Go 1.8, \'bool\' wasn't supported, either.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkEncodingBinaryRules = map[string]callcheck.Check{
"encoding/binary.Write": func(call *callcheck.Call) {
arg := call.Args[knowledge.Arg("encoding/binary.Write.data")]
if !CanBinaryMarshal(call.Pass, call.Parent, arg.Value) {
arg.Invalid(fmt.Sprintf("value of type %s cannot be used with binary.Write", arg.Value.Value.Type()))
}
},
}
func CanBinaryMarshal(pass *analysis.Pass, node code.Positioner, v callcheck.Value) bool {
typ := v.Value.Type().Underlying()
if ttyp, ok := typ.(*types.Pointer); ok {
typ = ttyp.Elem().Underlying()
}
if ttyp, ok := types.Unalias(typ).(interface {
Elem() types.Type
}); ok {
if _, ok := ttyp.(*types.Pointer); !ok {
typ = ttyp.Elem()
}
}
return validEncodingBinaryType(pass, node, typ)
}
func validEncodingBinaryType(pass *analysis.Pass, node code.Positioner, typ types.Type) bool {
typ = typ.Underlying()
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Uint8, types.Uint16, types.Uint32, types.Uint64,
types.Int8, types.Int16, types.Int32, types.Int64,
types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid:
return true
case types.Bool:
return version.Compare(code.StdlibVersion(pass, node), "go1.8") >= 0
}
return false
case *types.Struct:
n := typ.NumFields()
for i := range n {
if !validEncodingBinaryType(pass, node, typ.Field(i).Type()) {
return false
}
}
return true
case *types.Array:
return validEncodingBinaryType(pass, node, typ.Elem())
case *types.Interface:
// we can't determine if it's a valid type or not
return true
}
return false
}
================================================
FILE: staticcheck/sa1003/sa1003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1003/testdata/go1.0/CheckEncodingBinary/CheckEncodingBinary.go
================================================
package pkg
import (
"encoding/binary"
"io"
"io/ioutil"
"log"
)
func fn() {
type T1 struct {
A int32
}
type T2 struct {
A int32
B int
}
type T3 struct {
A []int32
}
type T4 struct {
A *int32
}
type T5 struct {
A int32
}
type T6 []byte
var x1 int
var x2 int32
var x3 []int
var x4 []int32
var x5 [1]int
var x6 [1]int32
var x7 T1
var x8 T2
var x9 T3
var x10 T4
var x11 = &T5{}
var x13 []byte
var x14 *[]byte
var x15 T6
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x1)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x2))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x3)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x4))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x5)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x6))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x7))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x8)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x9)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x10)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x11))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, &x13))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, &x14)) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x15))
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, &x15))
log.Println(binary.Write(fn2())) //@ diag(`cannot be used with binary.Write`)
log.Println(binary.Write(fn3()))
}
func fn2() (io.Writer, binary.ByteOrder, int) { return nil, binary.LittleEndian, 0 }
func fn3() (io.Writer, binary.ByteOrder, uint32) { return nil, binary.LittleEndian, 0 }
================================================
FILE: staticcheck/sa1003/testdata/go1.7/CheckEncodingBinary/CheckEncodingBinary.go
================================================
package pkg
import (
"encoding/binary"
"io/ioutil"
"log"
)
func fn() {
var x bool
log.Println(binary.Write(ioutil.Discard, binary.LittleEndian, x)) //@ diag(`cannot be used with binary.Write`)
}
================================================
FILE: staticcheck/sa1003/testdata/go1.8/CheckEncodingBinary/CheckEncodingBinary.go
================================================
package pkg
import (
"encoding/binary"
"io/ioutil"
)
func fn() {
var x bool
binary.Write(ioutil.Discard, binary.LittleEndian, x)
}
================================================
FILE: staticcheck/sa1004/sa1004.go
================================================
package sa1004
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1004",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Suspiciously small untyped constant in \'time.Sleep\'`,
Text: `The \'time\'.Sleep function takes a \'time.Duration\' as its only argument.
Durations are expressed in nanoseconds. Thus, calling \'time.Sleep(1)\'
will sleep for 1 nanosecond. This is a common source of bugs, as sleep
functions in other languages often accept seconds or milliseconds.
The \'time\' package provides constants such as \'time.Second\' to express
large durations. These can be combined with arithmetic to express
arbitrary durations, for example \'5 * time.Second\' for 5 seconds.
If you truly meant to sleep for a tiny amount of time, use
\'n * time.Nanosecond\' to signal to Staticcheck that you did mean to sleep
for some amount of nanoseconds.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkTimeSleepConstantPatternQ = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`)
checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`)
checkTimeSleepConstantPatternRs = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`)
)
func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, checkTimeSleepConstantPatternQ) {
n, ok := constant.Int64Val(m.State["value"].(types.TypeAndValue).Value)
if !ok {
continue
}
if n == 0 || n > 120 {
// time.Sleep(0) is a seldom used pattern in concurrency
// tests. >120 might be intentional. 120 was chosen
// because the user could've meant 2 minutes.
continue
}
lit := m.State["lit"].(ast.Node)
report.Report(pass, lit,
fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes(
edit.Fix("Explicitly use nanoseconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit})),
edit.Fix("Use seconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit}))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa1004/sa1004_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1004
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1004/testdata/go1.0/CheckTimeSleepConstant/CheckTimeSleepConstant.go
================================================
package pkg
import "time"
const c1 = 1
const c2 = 200
func fn() {
time.Sleep(1) //@ diag(`sleeping for 1`)
time.Sleep(42) //@ diag(`sleeping for 42`)
time.Sleep(201)
time.Sleep(c1)
time.Sleep(c2)
time.Sleep(2 * time.Nanosecond)
time.Sleep(time.Nanosecond)
}
================================================
FILE: staticcheck/sa1004/testdata/go1.0/CheckTimeSleepConstant/CheckTimeSleepConstant.go.golden
================================================
-- Explicitly use nanoseconds --
package pkg
import "time"
const c1 = 1
const c2 = 200
func fn() {
time.Sleep(1 * time.Nanosecond) //@ diag(`sleeping for 1`)
time.Sleep(42 * time.Nanosecond) //@ diag(`sleeping for 42`)
time.Sleep(201)
time.Sleep(c1)
time.Sleep(c2)
time.Sleep(2 * time.Nanosecond)
time.Sleep(time.Nanosecond)
}
-- Use seconds --
package pkg
import "time"
const c1 = 1
const c2 = 200
func fn() {
time.Sleep(1 * time.Second) //@ diag(`sleeping for 1`)
time.Sleep(42 * time.Second) //@ diag(`sleeping for 42`)
time.Sleep(201)
time.Sleep(c1)
time.Sleep(c2)
time.Sleep(2 * time.Nanosecond)
time.Sleep(time.Nanosecond)
}
================================================
FILE: staticcheck/sa1005/sa1005.go
================================================
package sa1005
import (
"go/ast"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1005",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Invalid first argument to \'exec.Command\'`,
Text: `\'os/exec\' runs programs directly (using variants of the fork and exec
system calls on Unix systems). This shouldn't be confused with running
a command in a shell. The shell will allow for features such as input
redirection, pipes, and general scripting. The shell is also
responsible for splitting the user's input into a program name and its
arguments. For example, the equivalent to
ls / /tmp
would be
exec.Command("ls", "/", "/tmp")
If you want to run a command in a shell, consider using something like
the following – but be aware that not all systems, particularly
Windows, will have a \'/bin/sh\' program:
exec.Command("/bin/sh", "-c", "ls | grep Awesome")`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`(CallExpr (Symbol "os/exec.Command") arg1:_)`)
func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, query) {
arg1 := m.State["arg1"].(ast.Expr)
val, ok := code.ExprToString(pass, arg1)
if !ok {
continue
}
if !strings.Contains(val, " ") || strings.Contains(val, `\`) || strings.Contains(val, "/") {
continue
}
report.Report(pass, arg1,
"first argument to exec.Command looks like a shell command, but a program name or path are expected")
}
return nil, nil
}
================================================
FILE: staticcheck/sa1005/sa1005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1005/testdata/go1.0/CheckExec/CheckExec.go
================================================
package pkg
import "os/exec"
func fn() {
exec.Command("ls")
exec.Command("ls arg1") //@ diag(`first argument to exec`)
exec.Command(`C:\Program Files\this\is\insane.exe`)
exec.Command("/Library/Application Support/VMware Tools/vmware-tools-daemon")
}
================================================
FILE: staticcheck/sa1006/sa1006.go
================================================
package sa1006
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1006",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `\'Printf\' with dynamic first argument and no further arguments`,
Text: `Using \'fmt.Printf\' with a dynamic first argument can lead to unexpected
output. The first argument is a format string, where certain character
combinations have special meaning. If, for example, a user were to
enter a string such as
Interest rate: 5%
and you printed it with
fmt.Printf(s)
it would lead to the following output:
Interest rate: 5%!(NOVERB).
Similarly, forming the first parameter via string concatenation with
user input should be avoided for the same reason. When printing user
input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb
and pass the string as an argument.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query1 = pattern.MustParse(`
(CallExpr
(Symbol
name@(Or
"fmt.Errorf"
"fmt.Printf"
"fmt.Sprintf"
"log.Fatalf"
"log.Panicf"
"log.Printf"
"(*log.Logger).Printf"
"(*testing.common).Logf"
"(*testing.common).Errorf"
"(*testing.common).Fatalf"
"(*testing.common).Skipf"
"(testing.TB).Logf"
"(testing.TB).Errorf"
"(testing.TB).Fatalf"
"(testing.TB).Skipf"))
format:[])
`)
var query2 = pattern.MustParse(`(CallExpr (Symbol "fmt.Fprintf") _:format:[])`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, query1, query2) {
call := node.(*ast.CallExpr)
name, ok := m.State["name"].(string)
if !ok {
name = "fmt.Fprintf"
}
arg := m.State["format"].(ast.Expr)
switch arg.(type) {
case *ast.CallExpr, *ast.Ident:
default:
continue
}
if _, ok := pass.TypesInfo.TypeOf(arg).(*types.Tuple); ok {
// the called function returns multiple values and got
// splatted into the call. for all we know, it is
// returning good arguments.
continue
}
var alt string
if name == "fmt.Errorf" {
// The alternative to fmt.Errorf isn't fmt.Error but errors.New
alt = "errors.New"
} else {
// This can be either a function call like log.Printf or a method call with an
// arbitrarily complex selector, such as foo.bar[0].Printf. In either case,
// all we have to do is remove the final 'f' from the existing call.Fun
// expression.
alt = report.Render(pass, call.Fun)
alt = alt[:len(alt)-1]
}
report.Report(pass, call,
"printf-style function with dynamic format string and no further arguments should use print-style function instead",
report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead of %s", alt, name), edit.ReplaceWithString(call.Fun, alt))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa1006/sa1006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1006/testdata/go1.0/CheckUnsafePrintf/CheckUnsafePrintf.go
================================================
package pkg
import (
"fmt"
"log"
"os"
"testing"
)
func fn(s string) {
fn2 := func() string { return "" }
fmt.Printf(fn2()) //@ diag(`should use print-style function`)
_ = fmt.Sprintf(fn2()) //@ diag(`should use print-style function`)
log.Printf(fn2()) //@ diag(`should use print-style function`)
fmt.Printf(s) //@ diag(`should use print-style function`)
fmt.Printf(s, "")
fmt.Fprintf(os.Stdout, s) //@ diag(`should use print-style function`)
fmt.Fprintf(os.Stdout, s, "")
fmt.Printf(fn2(), "")
fmt.Printf("")
fmt.Printf("%s", "")
fmt.Printf(fn3())
l := log.New(os.Stdout, "", 0)
l.Printf("xx: %q", "yy")
l.Printf(s) //@ diag(`should use print-style function`)
var t testing.T
t.Logf(fn2()) //@ diag(`should use print-style function`)
t.Errorf(s) //@ diag(`should use print-style function`)
t.Fatalf(s) //@ diag(`should use print-style function`)
t.Skipf(s) //@ diag(`should use print-style function`)
var b testing.B
b.Logf(fn2()) //@ diag(`should use print-style function`)
b.Errorf(s) //@ diag(`should use print-style function`)
b.Fatalf(s) //@ diag(`should use print-style function`)
b.Skipf(s) //@ diag(`should use print-style function`)
var tb testing.TB
tb.Logf(fn2()) //@ diag(`should use print-style function`)
tb.Errorf(s) //@ diag(`should use print-style function`)
tb.Fatalf(s) //@ diag(`should use print-style function`)
tb.Skipf(s) //@ diag(`should use print-style function`)
fmt.Errorf(s) //@ diag(`should use print-style function`)
var nested struct {
l log.Logger
}
nested.l.Printf(s) //@ diag(`should use print-style function`)
}
func fn3() (string, int) { return "", 0 }
================================================
FILE: staticcheck/sa1006/testdata/go1.0/CheckUnsafePrintf/CheckUnsafePrintf.go.golden
================================================
package pkg
import (
"fmt"
"log"
"os"
"testing"
)
func fn(s string) {
fn2 := func() string { return "" }
fmt.Print(fn2()) //@ diag(`should use print-style function`)
_ = fmt.Sprint(fn2()) //@ diag(`should use print-style function`)
log.Print(fn2()) //@ diag(`should use print-style function`)
fmt.Print(s) //@ diag(`should use print-style function`)
fmt.Printf(s, "")
fmt.Fprint(os.Stdout, s) //@ diag(`should use print-style function`)
fmt.Fprintf(os.Stdout, s, "")
fmt.Printf(fn2(), "")
fmt.Printf("")
fmt.Printf("%s", "")
fmt.Printf(fn3())
l := log.New(os.Stdout, "", 0)
l.Printf("xx: %q", "yy")
l.Print(s) //@ diag(`should use print-style function`)
var t testing.T
t.Log(fn2()) //@ diag(`should use print-style function`)
t.Error(s) //@ diag(`should use print-style function`)
t.Fatal(s) //@ diag(`should use print-style function`)
t.Skip(s) //@ diag(`should use print-style function`)
var b testing.B
b.Log(fn2()) //@ diag(`should use print-style function`)
b.Error(s) //@ diag(`should use print-style function`)
b.Fatal(s) //@ diag(`should use print-style function`)
b.Skip(s) //@ diag(`should use print-style function`)
var tb testing.TB
tb.Log(fn2()) //@ diag(`should use print-style function`)
tb.Error(s) //@ diag(`should use print-style function`)
tb.Fatal(s) //@ diag(`should use print-style function`)
tb.Skip(s) //@ diag(`should use print-style function`)
errors.New(s) //@ diag(`should use print-style function`)
var nested struct {
l log.Logger
}
nested.l.Print(s) //@ diag(`should use print-style function`)
}
func fn3() (string, int) { return "", 0 }
================================================
FILE: staticcheck/sa1007/sa1007.go
================================================
package sa1007
import (
"fmt"
"go/constant"
"net/url"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1007",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Invalid URL in \'net/url.Parse\'`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"net/url.Parse": func(call *callcheck.Call) {
arg := call.Args[knowledge.Arg("net/url.Parse.rawurl")]
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
_, err := url.Parse(s)
if err != nil {
arg.Invalid(fmt.Sprintf("%q is not a valid URL: %s", s, err))
}
}
},
}
================================================
FILE: staticcheck/sa1007/sa1007_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1007
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1007/testdata/go1.0/CheckURLs/CheckURLs.go
================================================
package pkg
import "net/url"
func fn() {
url.Parse("foobar")
url.Parse(":") //@ diag(`is not a valid URL`)
url.Parse("https://golang.org")
}
================================================
FILE: staticcheck/sa1008/sa1008.go
================================================
package sa1008
import (
"fmt"
"go/ast"
"net/http"
"strconv"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1008",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Non-canonical key in \'http.Header\' map`,
Text: `Keys in \'http.Header\' maps are canonical, meaning they follow a specific
combination of uppercase and lowercase letters. Methods such as
\'http.Header.Add\' and \'http.Header.Del\' convert inputs into this canonical
form before manipulating the map.
When manipulating \'http.Header\' maps directly, as opposed to using the
provided methods, care should be taken to stick to canonical form in
order to avoid inconsistencies. The following piece of code
demonstrates one such inconsistency:
h := http.Header{}
h["etag"] = []string{"1234"}
h.Add("etag", "5678")
fmt.Println(h)
// Output:
// map[Etag:[5678] etag:[1234]]
The easiest way of obtaining the canonical form of a key is to use
\'http.CanonicalHeaderKey\'.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node, push bool) bool {
if !push {
return false
}
if assign, ok := node.(*ast.AssignStmt); ok {
// TODO(dh): This risks missing some Header reads, for
// example in `h1["foo"] = h2["foo"]` – these edge
// cases are probably rare enough to ignore for now.
for _, expr := range assign.Lhs {
op, ok := expr.(*ast.IndexExpr)
if !ok {
continue
}
if code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
return false
}
}
return true
}
op, ok := node.(*ast.IndexExpr)
if !ok {
return true
}
if !code.IsOfTypeWithName(pass, op.X, "net/http.Header") {
return true
}
s, ok := code.ExprToString(pass, op.Index)
if !ok {
return true
}
canonical := http.CanonicalHeaderKey(s)
if s == canonical {
return true
}
var fix analysis.SuggestedFix
switch op.Index.(type) {
case *ast.BasicLit:
fix = edit.Fix("Canonicalize header key", edit.ReplaceWithString(op.Index, strconv.Quote(canonical)))
case *ast.Ident:
call := &ast.CallExpr{
Fun: edit.Selector("http", "CanonicalHeaderKey"),
Args: []ast.Expr{op.Index},
}
fix = edit.Fix("Wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
}
msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s)
if fix.Message != "" {
report.Report(pass, op, msg, report.Fixes(fix))
} else {
report.Report(pass, op, msg)
}
return true
}
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn)
return nil, nil
}
================================================
FILE: staticcheck/sa1008/sa1008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1008/testdata/go1.0/CheckCanonicalHeaderKey/CheckCanonicalHeaderKey.go
================================================
package pkg
import "net/http"
func fn() {
const hdr = "foo"
var r http.Request
h := http.Header{}
var m map[string][]string
_ = h["foo"] //@ diag(`keys in http.Header are canonicalized`)
_ = h[hdr] //@ diag(`keys in http.Header are canonicalized`)
h["foo"] = nil
_ = r.Header["foo"] //@ diag(`keys in http.Header are canonicalized`)
r.Header["foo"] = nil
_ = m["foo"]
}
================================================
FILE: staticcheck/sa1008/testdata/go1.0/CheckCanonicalHeaderKey/CheckCanonicalHeaderKey.go.golden
================================================
package pkg
import "net/http"
func fn() {
const hdr = "foo"
var r http.Request
h := http.Header{}
var m map[string][]string
_ = h["Foo"] //@ diag(`keys in http.Header are canonicalized`)
_ = h[http.CanonicalHeaderKey(hdr)] //@ diag(`keys in http.Header are canonicalized`)
h["foo"] = nil
_ = r.Header["Foo"] //@ diag(`keys in http.Header are canonicalized`)
r.Header["foo"] = nil
_ = m["foo"]
}
================================================
FILE: staticcheck/sa1010/sa1010.go
================================================
package sa1010
import (
"fmt"
"go/constant"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1010",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkRegexpFindAllRules),
},
Doc: &lint.RawDocumentation{
Title: `\'(*regexp.Regexp).FindAll\' called with \'n == 0\', which will always return zero results`,
Text: `If \'n >= 0\', the function returns at most \'n\' matches/submatches. To
return all results, specify a negative number.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkRegexpFindAllRules = map[string]callcheck.Check{
"(*regexp.Regexp).FindAll": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllIndex": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllString": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllStringIndex": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllStringSubmatch": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllStringSubmatchIndex": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllSubmatch": RepeatZeroTimes("a FindAll method", 1),
"(*regexp.Regexp).FindAllSubmatchIndex": RepeatZeroTimes("a FindAll method", 1),
}
func RepeatZeroTimes(name string, arg int) callcheck.Check {
return func(call *callcheck.Call) {
arg := call.Args[arg]
if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(k.Value); ok && v == 0 {
arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name))
}
}
}
}
================================================
FILE: staticcheck/sa1010/sa1010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1010/testdata/go1.0/checkStdlibUsageRegexpFindAll/checkStdlibUsageRegexpFindAll.go
================================================
package pkg
import "regexp"
func fn() {
var r *regexp.Regexp
_ = r.FindAll(nil, 0) //@ diag(`calling a FindAll method with n == 0 will return no results`)
}
func fn2() {
regexp.MustCompile("foo(").FindAll(nil, 0) //@ diag(`calling a FindAll`)
}
================================================
FILE: staticcheck/sa1011/sa1011.go
================================================
package sa1011
import (
"go/constant"
"unicode/utf8"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1011",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkUTF8CutsetRules),
},
Doc: &lint.RawDocumentation{
Title: `Various methods in the \"strings\" package expect valid UTF-8, but invalid input is provided`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkUTF8CutsetRules = map[string]callcheck.Check{
"strings.IndexAny": check,
"strings.LastIndexAny": check,
"strings.ContainsAny": check,
"strings.Trim": check,
"strings.TrimLeft": check,
"strings.TrimRight": check,
}
func check(call *callcheck.Call) {
arg := call.Args[1]
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil {
s := constant.StringVal(c.Value)
if !utf8.ValidString(s) {
arg.Invalid("argument is not a valid UTF-8 encoded string")
}
}
}
================================================
FILE: staticcheck/sa1011/sa1011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1011/testdata/go1.0/checkStdlibUsageUTF8Cutset/checkStdlibUsageUTF8Cutset.go
================================================
package pkg
import "strings"
func fn() {
println(strings.Trim("\x80test\xff", "\xff")) //@ diag(`is not a valid UTF-8 encoded string`)
println(strings.Trim("foo", "bar"))
s := "\xff"
if true {
s = ""
}
println(strings.Trim("", s))
}
================================================
FILE: staticcheck/sa1012/sa1012.go
================================================
package sa1012
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1012",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`,
Text: `The context package prohibits the use of a \'nil\' context.
If no parent context is available, a new context should be used,
e.g. \'context.TODO\' or \'context.Background\'.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Symbol _) (Builtin "nil"):_)`)
func run(pass *analysis.Pass) (any, error) {
todo := &ast.CallExpr{
Fun: edit.Selector("context", "TODO"),
}
bg := &ast.CallExpr{
Fun: edit.Selector("context", "Background"),
}
for node, m := range code.Matches(pass, checkNilContextQ) {
call := node.(*ast.CallExpr)
fun, ok := m.State["fun"].(*types.Func)
if !ok {
// it might also be a builtin
continue
}
sig := fun.Type().(*types.Signature)
if sig.Params().Len() == 0 {
// Our CallExpr might've matched a method expression, like
// (*T).Foo(nil) – here, nil isn't the first argument of
// the Foo method, but the method receiver.
continue
}
if !typeutil.IsTypeWithName(sig.Params().At(0).Type(), "context.Context") {
continue
}
report.Report(pass, call.Args[0],
"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes(
edit.Fix("Use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)),
edit.Fix("Use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa1012/sa1012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1012/testdata/go1.0/checkStdlibUsageNilContext/checkStdlibUsageNilContext.go
================================================
package pkg
import "context"
func fn1(ctx context.Context) {}
func fn2(x string, ctx context.Context) {}
func fn4() {}
type T struct{}
func (*T) Foo() {}
func fn3() {
fn1(nil) //@ diag(`do not pass a nil Context`)
fn1(context.TODO())
fn2("", nil)
fn4()
// don't flag this conversion
_ = (func(context.Context))(nil)
// and don't crash on these
_ = (func())(nil)
(*T).Foo(nil)
}
================================================
FILE: staticcheck/sa1012/testdata/go1.0/checkStdlibUsageNilContext/checkStdlibUsageNilContext.go.golden
================================================
-- Use context.Background --
package pkg
import "context"
func fn1(ctx context.Context) {}
func fn2(x string, ctx context.Context) {}
func fn4() {}
type T struct{}
func (*T) Foo() {}
func fn3() {
fn1(context.Background()) //@ diag(`do not pass a nil Context`)
fn1(context.TODO())
fn2("", nil)
fn4()
// don't flag this conversion
_ = (func(context.Context))(nil)
// and don't crash on these
_ = (func())(nil)
(*T).Foo(nil)
}
-- Use context.TODO --
package pkg
import "context"
func fn1(ctx context.Context) {}
func fn2(x string, ctx context.Context) {}
func fn4() {}
type T struct{}
func (*T) Foo() {}
func fn3() {
fn1(context.TODO()) //@ diag(`do not pass a nil Context`)
fn1(context.TODO())
fn2("", nil)
fn4()
// don't flag this conversion
_ = (func(context.Context))(nil)
// and don't crash on these
_ = (func())(nil)
(*T).Foo(nil)
}
================================================
FILE: staticcheck/sa1012/testdata/go1.18/checkStdlibUsageNilContext/checkStdlibUsageNilContext_generics.go
================================================
package pkg
import "context"
func tpfn1[T any](ctx context.Context, x T) {}
func tpfn2[T1, T2 any](ctx context.Context, x T1, y T2) {}
func tpbar() {
tpfn1[int](nil, 0) //@ diag(`do not pass a nil Context`)
tpfn1(nil, 0) //@ diag(`do not pass a nil Context`)
tpfn2[int, int](nil, 0, 0) //@ diag(`do not pass a nil Context`)
tpfn2(nil, 0, 0) //@ diag(`do not pass a nil Context`)
}
================================================
FILE: staticcheck/sa1012/testdata/go1.18/checkStdlibUsageNilContext/checkStdlibUsageNilContext_generics.go.golden
================================================
-- Use context.Background --
package pkg
import "context"
func tpfn1[T any](ctx context.Context, x T) {}
func tpfn2[T1, T2 any](ctx context.Context, x T1, y T2) {}
func tpbar() {
tpfn1[int](context.Background(), 0) //@ diag(`do not pass a nil Context`)
tpfn1(context.Background(), 0) //@ diag(`do not pass a nil Context`)
tpfn2[int, int](context.Background(), 0, 0) //@ diag(`do not pass a nil Context`)
tpfn2(context.Background(), 0, 0) //@ diag(`do not pass a nil Context`)
}
-- Use context.TODO --
package pkg
import "context"
func tpfn1[T any](ctx context.Context, x T) {}
func tpfn2[T1, T2 any](ctx context.Context, x T1, y T2) {}
func tpbar() {
tpfn1[int](context.TODO(), 0) //@ diag(`do not pass a nil Context`)
tpfn1(context.TODO(), 0) //@ diag(`do not pass a nil Context`)
tpfn2[int, int](context.TODO(), 0, 0) //@ diag(`do not pass a nil Context`)
tpfn2(context.TODO(), 0, 0) //@ diag(`do not pass a nil Context`)
}
================================================
FILE: staticcheck/sa1013/sa1013.go
================================================
package sa1013
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1013",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `\'io.Seeker.Seek\' is being called with the whence constant as the first argument, but it should be the second`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkSeekerQ = pattern.MustParse(`(CallExpr fun@(SelectorExpr _ (Ident "Seek")) [arg1@(SelectorExpr _ (Symbol (Or "io.SeekStart" "io.SeekCurrent" "io.SeekEnd"))) arg2])`)
checkSeekerR = pattern.MustParse(`(CallExpr fun [arg2 arg1])`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkSeekerQ) {
if !code.IsMethod(pass, m.State["fun"].(*ast.SelectorExpr), "Seek", knowledge.Signatures["(io.Seeker).Seek"]) {
continue
}
edits := code.EditMatch(pass, node, m, checkSeekerR)
report.Report(pass, node, "the first argument of io.Seeker is the offset, but an io.Seek* constant is being used instead",
report.Fixes(edit.Fix("Swap arguments", edits...)))
}
return nil, nil
}
================================================
FILE: staticcheck/sa1013/sa1013_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1013
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1013/testdata/go1.0/checkStdlibUsageSeeker/checkStdlibUsageSeeker.go
================================================
package pkg
import (
"io"
myio "io"
"os"
)
type T struct{}
func (T) Seek(whence int, offset int64) (int64, error) {
// This method does NOT implement io.Seeker
return 0, nil
}
func fn() {
const SeekStart = 0
var s io.Seeker
s.Seek(0, 0)
s.Seek(0, io.SeekStart)
s.Seek(io.SeekStart, 0) //@ diag(`the first argument of io.Seeker is the offset`)
s.Seek(myio.SeekStart, 0) //@ diag(`the first argument of io.Seeker is the offset`)
s.Seek(SeekStart, 0)
var f *os.File
f.Seek(io.SeekStart, 0) //@ diag(`the first argument of io.Seeker is the offset`)
var t T
t.Seek(io.SeekStart, 0) // not flagged, T is not an io.Seeker
}
================================================
FILE: staticcheck/sa1013/testdata/go1.0/checkStdlibUsageSeeker/checkStdlibUsageSeeker.go.golden
================================================
package pkg
import (
"io"
myio "io"
"os"
)
type T struct{}
func (T) Seek(whence int, offset int64) (int64, error) {
// This method does NOT implement io.Seeker
return 0, nil
}
func fn() {
const SeekStart = 0
var s io.Seeker
s.Seek(0, 0)
s.Seek(0, io.SeekStart)
s.Seek(0, io.SeekStart) //@ diag(`the first argument of io.Seeker is the offset`)
s.Seek(0, myio.SeekStart) //@ diag(`the first argument of io.Seeker is the offset`)
s.Seek(SeekStart, 0)
var f *os.File
f.Seek(0, io.SeekStart) //@ diag(`the first argument of io.Seeker is the offset`)
var t T
t.Seek(io.SeekStart, 0) // not flagged, T is not an io.Seeker
}
================================================
FILE: staticcheck/sa1014/sa1014.go
================================================
package sa1014
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1014",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkUnmarshalPointerRules),
},
Doc: &lint.RawDocumentation{
Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`,
Text: `Functions such as \'encoding/json.Unmarshal\' and
\'(*encoding/json.Decoder).Decode\' require a pointer to the value that should
be populated. Passing a non-pointer value results in the function returning an
error at runtime, as it cannot modify the target value.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkUnmarshalPointerRules = map[string]callcheck.Check{
"encoding/xml.Unmarshal": unmarshalPointer("xml.Unmarshal", 1),
"(*encoding/xml.Decoder).Decode": unmarshalPointer("Decode", 0),
"(*encoding/xml.Decoder).DecodeElement": unmarshalPointer("DecodeElement", 0),
"encoding/json.Unmarshal": unmarshalPointer("json.Unmarshal", 1),
"(*encoding/json.Decoder).Decode": unmarshalPointer("Decode", 0),
}
func unmarshalPointer(name string, arg int) callcheck.Check {
return func(call *callcheck.Call) {
if !Pointer(call.Args[arg].Value) {
call.Args[arg].Invalid(fmt.Sprintf("%s expects to unmarshal into a pointer, but the provided value is not a pointer", name))
}
}
}
func Pointer(v callcheck.Value) bool {
switch v.Value.Type().Underlying().(type) {
case *types.Pointer, *types.Interface:
return true
}
return false
}
================================================
FILE: staticcheck/sa1014/sa1014_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1014
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1014/testdata/go1.0/CheckUnmarshalPointer/CheckUnmarshalPointer.go
================================================
package pkg
import "encoding/json"
func fn1(i3 interface{}) {
var v map[string]interface{}
var i1 interface{} = v
var i2 interface{} = &v
p := &v
json.Unmarshal([]byte(`{}`), v) //@ diag(`Unmarshal expects to unmarshal into a pointer`)
json.Unmarshal([]byte(`{}`), &v)
json.Unmarshal([]byte(`{}`), i1) //@ diag(`Unmarshal expects to unmarshal into a pointer`)
json.Unmarshal([]byte(`{}`), i2)
json.Unmarshal([]byte(`{}`), i3)
json.Unmarshal([]byte(`{}`), p)
json.NewDecoder(nil).Decode(v) //@ diag(`Decode expects to unmarshal into a pointer`)
}
================================================
FILE: staticcheck/sa1015/sa1015.go
================================================
package sa1015
import (
"go/token"
"go/version"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1015",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Using \'time.Tick\' in a way that will leak. Consider using \'time.NewTicker\', and only use \'time.Tick\' in tests, commands and endless functions`,
Text: `Before Go 1.23, \'time.Ticker\'s had to be closed to be able to be garbage
collected. Since \'time.Tick\' doesn't make it possible to close the underlying
ticker, using it repeatedly would leak memory.
Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if fn.Pos() == token.NoPos || version.Compare(code.StdlibVersion(pass, fn), "go1.23") >= 0 {
// Beginning with Go 1.23, the GC is able to collect unreferenced, unclosed
// tickers, which makes time.Tick safe(r) to use.
//
// When we don't have a valid position, we err on the side of false negatives.
// This shouldn't actually lead to any false negatives, as no functions
// without valid positions (such as the synthesized init function) should be
// able to use time.Tick.
continue
}
if code.IsMainLike(pass) || code.IsInTest(pass, fn) {
continue
}
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
call, ok := ins.(*ir.Call)
if !ok || !irutil.IsCallTo(call.Common(), "time.Tick") {
continue
}
if !irutil.Terminates(call.Parent()) {
continue
}
report.Report(pass, call, "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here")
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa1015/sa1015_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1015
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1015/testdata/go1.0/CheckLeakyTimeTick/CheckLeakyTimeTick.go
================================================
package pkg
import "time"
func fn1() {
for range time.Tick(0) {
println("")
}
}
func fn2() {
for range time.Tick(0) { //@ diag(`leaks the underlying ticker`)
println("")
if true {
break
}
}
}
func fn3() {
for range time.Tick(0) { //@ diag(`leaks the underlying ticker`)
println("")
if true {
return
}
}
}
func fn4() {
go func() {
for range time.Tick(0) {
println("")
}
}()
}
func fn5() {
if false {
panic("foo")
}
for range time.Tick(0) {
println("")
}
}
type T struct{}
func (t *T) foo() {
for range time.Tick(0) {
println("")
}
}
func (t T) bar() {
for range time.Tick(0) {
println("")
}
}
================================================
FILE: staticcheck/sa1015/testdata/go1.0/CheckLeakyTimeTick-main/CheckLeakyTimeTick-main.go
================================================
package main
import "time"
func fn2() {
for range time.Tick(0) {
println("")
if true {
break
}
}
}
func main() {
_ = time.Tick(0)
}
================================================
FILE: staticcheck/sa1015/testdata/go1.23/CheckLeakyTimeTick/CheckLeakyTimeTick.go
================================================
package pkg
import "time"
func fn1() {
// Not flagged because this is no longer a problem in Go 1.23.
for range time.Tick(0) {
println("")
if true {
break
}
}
}
================================================
FILE: staticcheck/sa1016/sa1016.go
================================================
package sa1016
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1016",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Trapping a signal that cannot be trapped`,
Text: `Not all signals can be intercepted by a process. Specifically, on
UNIX-like systems, the \'syscall.SIGKILL\' and \'syscall.SIGSTOP\' signals are
never passed to the process, but instead handled directly by the
kernel. It is therefore pointless to try and handle these signals.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`
(CallExpr
(Symbol
(Or
"os/signal.Ignore"
"os/signal.Notify"
"os/signal.Reset"))
_)`)
func run(pass *analysis.Pass) (any, error) {
isSignal := func(pass *analysis.Pass, expr ast.Expr, name string) bool {
if expr, ok := expr.(*ast.SelectorExpr); ok {
return code.SelectorName(pass, expr) == name
} else {
return false
}
}
for node := range code.Matches(pass, query) {
call := node.(*ast.CallExpr)
hasSigterm := false
for _, arg := range call.Args {
if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
arg = conv.Args[0]
}
if isSignal(pass, arg, "syscall.SIGTERM") {
hasSigterm = true
break
}
}
for i, arg := range call.Args {
if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") {
arg = conv.Args[0]
}
if isSignal(pass, arg, "os.Kill") || isSignal(pass, arg, "syscall.SIGKILL") {
var fixes []analysis.SuggestedFix
if !hasSigterm {
nargs := make([]ast.Expr, len(call.Args))
for j, a := range call.Args {
if i == j {
nargs[j] = edit.Selector("syscall", "SIGTERM")
} else {
nargs[j] = a
}
}
ncall := *call
ncall.Args = nargs
fixes = append(fixes, edit.Fix(fmt.Sprintf("Use syscall.SIGTERM instead of %s", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
}
nargs := make([]ast.Expr, 0, len(call.Args))
for j, a := range call.Args {
if i == j {
continue
}
nargs = append(nargs, a)
}
ncall := *call
ncall.Args = nargs
fixes = append(fixes, edit.Fix(fmt.Sprintf("Remove %s from list of arguments", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall)))
report.Report(pass, arg, fmt.Sprintf("%s cannot be trapped (did you mean syscall.SIGTERM?)", report.Render(pass, arg)), report.Fixes(fixes...))
}
if isSignal(pass, arg, "syscall.SIGSTOP") {
nargs := make([]ast.Expr, 0, len(call.Args)-1)
for j, a := range call.Args {
if i == j {
continue
}
nargs = append(nargs, a)
}
ncall := *call
ncall.Args = nargs
report.Report(pass, arg, "syscall.SIGSTOP cannot be trapped", report.Fixes(edit.Fix("Remove syscall.SIGSTOP from list of arguments", edit.ReplaceWithNode(pass.Fset, call, &ncall))))
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa1016/sa1016_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1016
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1016/testdata/go1.0/CheckUntrappableSignal/CheckUntrappableSignal.go
================================================
package main
import (
"os"
"os/signal"
"syscall"
)
func fn() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Ignore(os.Signal(syscall.SIGKILL)) //@ diag(`cannot be trapped`)
signal.Ignore(os.Kill) //@ diag(`cannot be trapped`)
signal.Notify(c, os.Kill) //@ diag(`cannot be trapped`)
signal.Reset(os.Kill) //@ diag(`cannot be trapped`)
signal.Ignore(syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGKILL) //@ diag(`cannot be trapped`)
}
================================================
FILE: staticcheck/sa1016/testdata/go1.0/CheckUntrappableSignal/CheckUntrappableSignal.go.golden
================================================
-- Remove syscall.SIGKILL from list of arguments --
package main
import (
"os"
"os/signal"
"syscall"
)
func fn() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Ignore() //@ diag(`cannot be trapped`)
signal.Ignore(os.Kill) //@ diag(`cannot be trapped`)
signal.Notify(c, os.Kill) //@ diag(`cannot be trapped`)
signal.Reset(os.Kill) //@ diag(`cannot be trapped`)
signal.Ignore() //@ diag(`cannot be trapped`)
signal.Notify(c) //@ diag(`cannot be trapped`)
signal.Reset() //@ diag(`cannot be trapped`)
}
-- Remove os.Kill from list of arguments --
package main
import (
"os"
"os/signal"
"syscall"
)
func fn() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Ignore(os.Signal(syscall.SIGKILL)) //@ diag(`cannot be trapped`)
signal.Ignore() //@ diag(`cannot be trapped`)
signal.Notify(c) //@ diag(`cannot be trapped`)
signal.Reset() //@ diag(`cannot be trapped`)
signal.Ignore(syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGKILL) //@ diag(`cannot be trapped`)
}
-- Use syscall.SIGTERM instead of syscall.SIGKILL --
package main
import (
"os"
"os/signal"
"syscall"
)
func fn() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Ignore(syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Ignore(os.Kill) //@ diag(`cannot be trapped`)
signal.Notify(c, os.Kill) //@ diag(`cannot be trapped`)
signal.Reset(os.Kill) //@ diag(`cannot be trapped`)
signal.Ignore(syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGTERM) //@ diag(`cannot be trapped`)
}
-- Use syscall.SIGTERM instead of os.Kill --
package main
import (
"os"
"os/signal"
"syscall"
)
func fn() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Ignore(os.Signal(syscall.SIGKILL)) //@ diag(`cannot be trapped`)
signal.Ignore(syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGTERM) //@ diag(`cannot be trapped`)
signal.Ignore(syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGKILL) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGKILL) //@ diag(`cannot be trapped`)
}
================================================
FILE: staticcheck/sa1016/testdata/go1.0/CheckUntrappableSignal/CheckUntrappableSignal_unix.go
================================================
//go:build android || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build android darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"os"
"os/signal"
"syscall"
)
func fn2() {
c := make(chan os.Signal, 1)
signal.Ignore(syscall.SIGSTOP) //@ diag(`cannot be trapped`)
signal.Notify(c, syscall.SIGSTOP) //@ diag(`cannot be trapped`)
signal.Reset(syscall.SIGSTOP) //@ diag(`cannot be trapped`)
}
================================================
FILE: staticcheck/sa1016/testdata/go1.0/CheckUntrappableSignal/CheckUntrappableSignal_unix.go.golden
================================================
//go:build android || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build android darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"os"
"os/signal"
"syscall"
)
func fn2() {
c := make(chan os.Signal, 1)
signal.Ignore() //@ diag(`cannot be trapped`)
signal.Notify(c) //@ diag(`cannot be trapped`)
signal.Reset() //@ diag(`cannot be trapped`)
}
================================================
FILE: staticcheck/sa1017/sa1017.go
================================================
package sa1017
import (
"go/constant"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1017",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Channels used with \'os/signal.Notify\' should be buffered`,
Text: `The \'os/signal\' package uses non-blocking channel sends when delivering
signals. If the receiving end of the channel isn't ready and the
channel is either unbuffered or full, the signal will be dropped. To
avoid missing signals, the channel should be buffered and of the
appropriate size. For a channel used for notification of just one
signal value, a buffer of size 1 is sufficient.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"os/signal.Notify": func(call *callcheck.Call) {
arg := call.Args[knowledge.Arg("os/signal.Notify.c")]
if isUnbufferedChannel(arg.Value) {
arg.Invalid("the channel used with signal.Notify should be buffered")
}
},
}
func isUnbufferedChannel(v callcheck.Value) bool {
// TODO(dh): this check of course misses many cases of unbuffered
// channels, such as any in phi or sigma nodes. We'll eventually
// replace this function.
val := v.Value
if ct, ok := val.(*ir.ChangeType); ok {
val = ct.X
}
mk, ok := val.(*ir.MakeChan)
if !ok {
return false
}
if k, ok := mk.Size.(*ir.Const); ok && k.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(k.Value); ok && v == 0 {
return true
}
}
return false
}
================================================
FILE: staticcheck/sa1017/sa1017_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1017
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1017/testdata/go1.0/CheckUnbufferedSignalChan/CheckUnbufferedSignalChan.go
================================================
package pkg
import (
"os"
"os/signal"
"syscall"
)
func fn(b bool) {
c0 := make(chan os.Signal)
signal.Notify(c0, os.Interrupt) //@ diag(`the channel used with signal.Notify should be buffered`)
c1 := make(chan os.Signal, 1)
signal.Notify(c1, os.Interrupt, syscall.SIGHUP)
c2 := c0
if b {
c2 = c1
}
signal.Notify(c2, os.Interrupt, syscall.SIGHUP)
}
================================================
FILE: staticcheck/sa1018/sa1018.go
================================================
package sa1018
import (
"fmt"
"go/constant"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1018",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `\'strings.Replace\' called with \'n == 0\', which does nothing`,
Text: `With \'n == 0\', zero instances will be replaced. To replace all
instances, use a negative number, or use \'strings.ReplaceAll\'.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"strings.Replace": check("strings.Replace", 3),
"bytes.Replace": check("bytes.Replace", 3),
}
func check(name string, arg int) callcheck.Check {
return func(call *callcheck.Call) {
arg := call.Args[arg]
if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(k.Value); ok && v == 0 {
arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name))
}
}
}
}
================================================
FILE: staticcheck/sa1018/sa1018_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1018
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1018/testdata/go1.0/CheckStringsReplaceZero/CheckStringsReplaceZero.go
================================================
package pkg
import "strings"
func fn() {
_ = strings.Replace("", "", "", 0) //@ diag(`calling strings.Replace with n == 0`)
_ = strings.Replace("", "", "", -1)
_ = strings.Replace("", "", "", 1)
}
================================================
FILE: staticcheck/sa1019/sa1019.go
================================================
package sa1019
import (
"fmt"
"go/ast"
"go/types"
"go/version"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/deprecated"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1019",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, deprecated.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Using a deprecated function, variable, constant or field`,
Since: "2017.1",
Severity: lint.SeverityDeprecated,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func formatGoVersion(s string) string {
return "Go " + strings.TrimPrefix(s, "go")
}
func run(pass *analysis.Pass) (any, error) {
deprs := pass.ResultOf[deprecated.Analyzer].(deprecated.Result)
// Selectors can appear outside of function literals, e.g. when
// declaring package level variables.
isStdlibPath := func(path string) bool {
// Modules with no dot in the first path element are reserved for the standard library and tooling.
// This is the best we can currently do.
// Nobody tells us which import paths are part of the standard library.
//
// We check the entire path instead of just the first path element, because the standard library doesn't contain paths with any dots, anyway.
return !strings.Contains(path, ".")
}
handleDeprecation := func(depr *deprecated.IsDeprecated, node ast.Node, deprecatedObjName string, pkgPath string, tfn types.Object) {
std, ok := knowledge.StdlibDeprecations[deprecatedObjName]
if !ok && isStdlibPath(pkgPath) {
// Deprecated object in the standard library, but we don't know the details of the deprecation.
// Don't flag it at all, to avoid flagging an object that was deprecated in 1.N when targeting 1.N-1.
// See https://staticcheck.dev/issues/1108 for the background on this.
return
}
if ok {
// In the past, we made use of the AlternativeAvailableSince field. If a function was deprecated in Go
// 1.6 and an alternative had been available in Go 1.0, then we'd recommend using the alternative even
// if targeting Go 1.2. The idea was to suggest writing future-proof code by using already-existing
// alternatives. This had a major flaw, however: the user would need to use at least Go 1.6 for
// Staticcheck to know that the function had been deprecated. Thus, targeting Go 1.2 and using Go 1.2
// would behave differently from targeting Go 1.2 and using Go 1.6. This is especially a problem if the
// user tries to ignore the warning. Depending on the Go version in use, the ignore directive may or may
// not match, causing a warning of its own.
//
// To avoid this issue, we no longer try to be smart. We now only compare the targeted version against
// the version that deprecated an object.
//
// Unfortunately, this issue also applies to AlternativeAvailableSince == DeprecatedNeverUse. Even though it
// is only applied to seriously flawed API, such as broken cryptography, users may wish to ignore those
// warnings.
//
// See also https://staticcheck.dev/issues/1318.
if version.Compare(code.StdlibVersion(pass, node), std.DeprecatedSince) == -1 {
return
}
}
if tfn != nil {
if _, ok := deprs.Objects[tfn]; ok {
// functions that are deprecated may use deprecated
// symbols
return
}
}
if ok {
switch std.AlternativeAvailableSince {
case knowledge.DeprecatedNeverUse:
report.Report(pass, node,
fmt.Sprintf("%s has been deprecated since %s because it shouldn't be used: %s",
deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg))
case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger:
report.Report(pass, node,
fmt.Sprintf("%s has been deprecated since %s: %s",
deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg))
default:
report.Report(pass, node,
fmt.Sprintf("%s has been deprecated since %s and an alternative has been available since %s: %s",
deprecatedObjName, formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg))
}
} else {
report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", deprecatedObjName, depr.Msg))
}
}
var tfn types.Object
stack := 0
checkIdentObj := func(sel *ast.SelectorExpr) bool {
obj := pass.TypesInfo.ObjectOf(sel.Sel)
if obj_, ok := obj.(*types.Func); ok {
obj = obj_.Origin()
}
if obj.Pkg() == nil {
return true
}
if obj.Pkg() == pass.Pkg {
// A package is allowed to use its own deprecated objects
return true
}
// A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
// generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
// and "foo_test".
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
// foo_test (the external tests of foo) can use objects from foo.
return true
}
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
// foo.test (the main package of foo's tests) can use objects from foo.
return true
}
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
// foo.test (the main package of foo's tests) can use objects from foo's external tests.
return true
}
node := ast.Node(sel)
if pass.TypesInfo.Types[sel.X].IsType() {
node = sel.Sel
}
if depr, ok := deprs.Objects[obj]; ok {
handleDeprecation(depr, node, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn)
}
return true
}
fn := func(node ast.Node, push bool) bool {
if !push {
stack--
return false
}
stack++
if stack == 1 {
tfn = nil
}
if fn, ok := node.(*ast.FuncDecl); ok {
tfn = pass.TypesInfo.ObjectOf(fn.Name)
}
switch v := node.(type) {
// FIXME(dh): this misses dot-imported objects
case *ast.SelectorExpr:
return checkIdentObj(v)
case *ast.CompositeLit:
litType := pass.TypesInfo.Types[v.Type]
if !litType.IsType() {
// This is probably unreachable.
return true
}
if _, ok := litType.Type.Underlying().(*types.Struct); !ok {
// We don't want to look at expressions in map initializers, for
// example.
return true
}
for _, elt := range v.Elts {
kv, ok := elt.(*ast.KeyValueExpr)
if !ok {
return true
}
key, ok := kv.Key.(*ast.Ident)
if !ok {
// This is probably unreachable, since we're looking at keys
// in a struct initializer.
return true
}
sel := &ast.SelectorExpr{X: v.Type, Sel: key}
checkIdentObj(sel)
}
}
return true
}
fn2 := func(node ast.Node) {
spec := node.(*ast.ImportSpec)
var imp *types.Package
if spec.Name != nil {
imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported()
} else {
imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported()
}
p := spec.Path.Value
path := p[1 : len(p)-1]
if depr, ok := deprs.Packages[imp]; ok {
if path == "github.com/golang/protobuf/proto" {
gen, ok := code.Generator(pass, spec.Path.Pos())
if ok && gen == generated.ProtocGenGo {
return
}
}
if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path {
// foo_test can import foo
return
}
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path {
// foo.test can import foo
return
}
if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") {
// foo.test can import foo_test
return
}
handleDeprecation(depr, spec.Path, path, path, nil)
}
}
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn)
code.Preorder(pass, fn2, (*ast.ImportSpec)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa1019/sa1019_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1019
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/AnotherCheckDeprecated.assist/CheckDeprecatedassist.go
================================================
// Package pkg is a nice package.
//
// Deprecated: Alas, it is deprecated.
package AnotherCheckDeprecatedassist
func Fn() {}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated/CheckDeprecated.go
================================================
// Deprecated: this package is deprecated.
package pkg
import _ "example.com/CheckDeprecated.assist" //@ diag(`Alas, it is deprecated.`)
import _ "example.com/AnotherCheckDeprecated.assist" //@ diag(`Alas, it is deprecated.`)
import foo "example.com/AnotherCheckDeprecated.assist" //@ diag(`Alas, it is deprecated.`)
import "example.com/AnotherCheckDeprecated.assist" //@ diag(`Alas, it is deprecated.`)
import ae "example.com/CheckDeprecated.assist_external"
func init() {
foo.Fn()
AnotherCheckDeprecatedassist.Fn()
// Field is deprecated, but we're using it from the same package, which is fine.
var s S
_ = s.Field
s2 := ae.SD{
D: "used", //@ diag(`external don't use me`)
}
_ = s2
// Struct with the same name should not be flagged
s3 := ae.SN{
D:"also",
}
_ = s3
// Other Key-Value expressions should be safely ignored
_ = map[string]string {
"left":"right",
}
}
type S struct {
// Deprecated: this is deprecated.
Field int
}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated/CheckDeprecated_test.go
================================================
package pkg
import "testing"
// Deprecated: deprecating tests is silly but possible.
func TestFoo(t *testing.T) {
var s S
// Internal tests can use deprecated objects from the package they test.
_ = s.Field
}
// This test isn't deprecated, to test that s.Field doesn't get flagged because it's from the package under test. If
// TestBar was itself deprecated, it could use any deprecated objects it wanted.
func TestBar(t *testing.T) {
var s S
// Internal tests can use deprecated objects from the package under test
_ = s.Field
}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated/external_test.go
================================================
// Deprecated: deprecating external test packages is silly but possible.
package pkg_test
import (
"testing"
// External tests can import deprecated packages under test.
pkg "example.com/CheckDeprecated"
)
// Deprecated: deprecating tests is silly but possible.
func TestFoo(t *testing.T) {
}
// This test isn't deprecated, to test that s.Field doesn't get flagged because it's from the package under test. If
// TestBar was itself deprecated, it could use any deprecated objects it wanted.
func TestBar(t *testing.T) {
var s pkg.S
// External tests can use deprecated objects from the package under test
_ = s.Field
}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated/not-protobuf.go
================================================
package pkg
import _ "github.com/golang/protobuf/proto" //@ diag(`Alas, it is deprecated.`)
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated/protobuf.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
package pkg
import _ "github.com/golang/protobuf/proto"
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated.assist/CheckDeprecatedassist.go
================================================
// Package pkg is a nice package.
//
// Deprecated: Alas, it is deprecated.
package pkg
================================================
FILE: staticcheck/sa1019/testdata/go1.0/CheckDeprecated.assist_external/CheckDeprecatedassist_external.go
================================================
package pkg
type SD struct {
// Deprecated: external don't use me
D string
}
type SN struct {
// Not deprecated, but named the same
D string
}
================================================
FILE: staticcheck/sa1019/testdata/go1.0/vendor/github.com/golang/protobuf/proto/pkg.go
================================================
// Package proto exists.
//
// Deprecated: Alas, it is deprecated.
package proto
================================================
FILE: staticcheck/sa1019/testdata/go1.18/CheckDeprecated/CheckDeprecated_generics.go
================================================
package pkg
import pkg "example.com/CheckDeprecated.assist"
func tpFn() {
var x pkg.S[int]
x.Foo()
x.Bar() //@ diag(`deprecated`)
x.Baz() //@ diag(`deprecated`)
x.Qux()
_ = x.Field1
_ = x.Field2 // This should be flagged, but see issue 1215
}
================================================
FILE: staticcheck/sa1019/testdata/go1.18/CheckDeprecated.assist/CheckDeprecatedassist_generics.go
================================================
package pkg
type S[T any] struct {
Field1 T
// Deprecated: don't use me
Field2 T
}
func (S[T]) Foo() {}
// Deprecated: don't use me
func (S[T]) Bar() {}
// Deprecated: don't use me
func (S[T]) Baz() {}
func (S[T]) Qux() {}
================================================
FILE: staticcheck/sa1019/testdata/go1.19/CheckDeprecated/CheckDeprecated.go
================================================
package pkg
import _ "io/ioutil" //@ diag("has been deprecated")
// We test this in Go 1.19 even though io/ioutil has technically been deprecated since Go 1.16, because only in Go 1.19
// was the proper deprecation marker added.
================================================
FILE: staticcheck/sa1019/testdata/go1.19/CheckDeprecated/stub.go
================================================
package pkg
================================================
FILE: staticcheck/sa1019/testdata/go1.3/CheckDeprecated/CheckDeprecated.go
================================================
package pkg
import (
"crypto/x509"
"net/http/httputil"
"path/filepath"
)
func fn() {
filepath.HasPrefix("", "") //@ diag(`filepath.HasPrefix has been deprecated since Go 1.0 because it shouldn't be used:`)
_ = httputil.ErrPersistEOF //@ diag(`httputil.ErrPersistEOF has been deprecated since Go 1.0:`)
_ = httputil.ServerConn{} //@ diag(`httputil.ServerConn has been deprecated since Go 1.0:`)
_ = x509.CertificateRequest{}.Attributes
}
================================================
FILE: staticcheck/sa1019/testdata/go1.4/CheckDeprecated/CheckDeprecated.go
================================================
package pkg
import (
"compress/flate"
"database/sql/driver"
"net/http"
"os"
"syscall"
)
var _ = syscall.StringByteSlice("") //@ diag(`Use ByteSliceFromString instead`)
func fn1(err error) {
var r *http.Request
_ = r.Cancel
_ = syscall.StringByteSlice("") //@ diag(`Use ByteSliceFromString instead`)
_ = os.SEEK_SET
var _ flate.ReadError
var tr *http.Transport
tr.CancelRequest(nil)
var conn driver.Conn
conn.Begin()
}
// Deprecated: Don't use this.
func fn2() {
_ = syscall.StringByteSlice("")
anon := func(x int) {
println(x)
_ = syscall.StringByteSlice("")
}
anon(1)
}
================================================
FILE: staticcheck/sa1019/testdata/go1.8/CheckDeprecated/CheckDeprecated.go
================================================
package pkg
import (
"compress/flate"
"crypto/x509"
"database/sql/driver"
"net/http"
"os"
"syscall"
)
var _ = syscall.StringByteSlice("") //@ diag(`Use ByteSliceFromString instead`)
func fn1(err error) {
var r http.Request
var rp *http.Request
_ = r.Cancel //@ diag(re`deprecated since Go 1\.7:.+If a Request's Cancel field and context are both`)
_ = rp.Cancel //@ diag(re`deprecated since Go 1\.7:.+If a Request's Cancel field and context are both`)
_ = syscall.StringByteSlice("") //@ diag(`Use ByteSliceFromString instead`)
_ = os.SEEK_SET //@ diag(`Use io.SeekStart, io.SeekCurrent, and io.SeekEnd`)
_ = os.SEEK_CUR //@ diag(`Use io.SeekStart, io.SeekCurrent, and io.SeekEnd`)
_ = os.SEEK_END //@ diag(`Use io.SeekStart, io.SeekCurrent, and io.SeekEnd`)
if err == http.ErrWriteAfterFlush { //@ diag(`ErrWriteAfterFlush is no longer`)
println()
}
var _ flate.ReadError //@ diag(`No longer returned`)
var tr *http.Transport
tr.CancelRequest(nil) //@ diag(`CancelRequest has been deprecated`)
var conn driver.Conn
conn.Begin() //@ diag(`Begin has been deprecated`)
_ = x509.CertificateRequest{}.Attributes //@ diag(`(crypto/x509.CertificateRequest).Attributes has been deprecated since Go 1.5 and an alternative has been available since Go 1.3:`)
}
// Deprecated: Don't use this.
func fn2() {
_ = syscall.StringByteSlice("")
anon := func(x int) {
println(x)
_ = syscall.StringByteSlice("")
anon := func(x int) {
println(x)
_ = syscall.StringByteSlice("")
}
anon(2)
}
anon(1)
}
================================================
FILE: staticcheck/sa1020/sa1020.go
================================================
package sa1020
import (
"go/constant"
"net"
"strconv"
"strings"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1020",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkListenAddressRules),
},
Doc: &lint.RawDocumentation{
Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`,
Text: `Functions such as \'net.Listen\', \'net.ListenTCP\', and similar,
expect a valid network address in the form of host:port. The host, the port,
or both, can be omitted, e.g. \'localhost:8080\', \':8080\' or \':\' are valid
host:port pairs.
See https://pkg.go.dev/net#Listen for the full documentation.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkListenAddressRules = map[string]callcheck.Check{
"net/http.ListenAndServe": checkValidHostPort(0),
"net/http.ListenAndServeTLS": checkValidHostPort(0),
}
func checkValidHostPort(arg int) callcheck.Check {
return func(call *callcheck.Call) {
if !ValidHostPort(call.Args[arg].Value) {
const MsgInvalidHostPort = "invalid port or service name in host:port pair"
call.Args[arg].Invalid(MsgInvalidHostPort)
}
}
}
func ValidHostPort(v callcheck.Value) bool {
if k := callcheck.ExtractConstExpectKind(v, constant.String); k != nil {
s := constant.StringVal(k.Value)
if s == "" {
return true
}
_, port, err := net.SplitHostPort(s)
if err != nil {
return false
}
// TODO(dh): check hostname
if !validatePort(port) {
return false
}
}
return true
}
func validateServiceName(s string) bool {
if len(s) < 1 || len(s) > 15 {
return false
}
if s[0] == '-' || s[len(s)-1] == '-' {
return false
}
if strings.Contains(s, "--") {
return false
}
hasLetter := false
for _, r := range s {
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
hasLetter = true
continue
}
if r >= '0' && r <= '9' {
continue
}
return false
}
return hasLetter
}
func validatePort(s string) bool {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return validateServiceName(s)
}
return n >= 0 && n <= 65535
}
================================================
FILE: staticcheck/sa1020/sa1020_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1020
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1020/testdata/go1.0/CheckListenAddress/CheckListenAddress.go
================================================
package pkg
import "net/http"
func fn() {
// Seen in actual code
http.ListenAndServe("localhost:8080/", nil) //@ diag(`invalid port or service name in host:port pair`)
http.ListenAndServe("localhost", nil) //@ diag(`invalid port or service name in host:port pair`)
http.ListenAndServe("localhost:8080", nil)
http.ListenAndServe(":8080", nil)
http.ListenAndServe(":http", nil)
http.ListenAndServe("localhost:http", nil)
http.ListenAndServe("local_host:8080", nil)
http.ListenAndServe("", nil) // providing no address at all makes it default to :http
}
================================================
FILE: staticcheck/sa1021/sa1021.go
================================================
package sa1021
import (
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1021",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Using \'bytes.Equal\' to compare two \'net.IP\'`,
Text: `A \'net.IP\' stores an IPv4 or IPv6 address as a slice of bytes. The
length of the slice for an IPv4 address, however, can be either 4 or
16 bytes long, using different ways of representing IPv4 addresses. In
order to correctly compare two \'net.IP\'s, the \'net.IP.Equal\' method should
be used, as it takes both representations into account.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"bytes.Equal": func(call *callcheck.Call) {
if isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.a")].Value, "net.IP") &&
isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.b")].Value, "net.IP") {
call.Invalid("use net.IP.Equal to compare net.IPs, not bytes.Equal")
}
},
}
// ConvertedFrom reports whether value v was converted from type typ.
func isConvertedFrom(v callcheck.Value, typ string) bool {
change, ok := v.Value.(*ir.ChangeType)
return ok && types.TypeString(types.Unalias(change.X.Type()), nil) == typ
}
================================================
FILE: staticcheck/sa1021/sa1021_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1021
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1021/testdata/go1.0/CheckBytesEqualIP/CheckBytesEqualIP.go
================================================
package pkg
import (
"bytes"
"net"
)
func fn() {
type T []byte
var i1, i2 net.IP
var b1, b2 []byte
var t1, t2 T
bytes.Equal(i1, i2) //@ diag(`use net.IP.Equal to compare net.IPs, not bytes.Equal`)
bytes.Equal(b1, b2)
bytes.Equal(t1, t2)
bytes.Equal(i1, b1)
bytes.Equal(b1, i1)
}
================================================
FILE: staticcheck/sa1023/sa1023.go
================================================
package sa1023
import (
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1023",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Modifying the buffer in an \'io.Writer\' implementation`,
Text: `\'Write\' must not modify the slice data, even temporarily.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): this might be a good candidate for taint analysis.
// Taint the argument as MUST_NOT_MODIFY, then propagate that
// through functions like bytes.Split
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
sig := fn.Signature
if fn.Name() != "Write" || sig.Recv() == nil {
continue
}
if !types.Identical(sig, knowledge.Signatures["(io.Writer).Write"]) {
continue
}
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
switch ins := ins.(type) {
case *ir.Store:
addr, ok := ins.Addr.(*ir.IndexAddr)
if !ok {
continue
}
if addr.X != fn.Params[1] {
continue
}
report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
case *ir.Call:
if !irutil.IsCallTo(ins.Common(), "append") {
continue
}
if ins.Common().Args[0] != fn.Params[1] {
continue
}
report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily")
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa1023/sa1023_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1023
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1023/testdata/go1.0/CheckWriterBufferModified/CheckWriterBufferModified.go
================================================
package pkg
type T1 struct{}
type T2 struct{}
type T3 struct{}
type T4 struct{}
type T5 struct{}
type T6 struct{}
type T7 struct{}
type Bytes []byte
func (T1) Write(b []byte) (int, error) {
b = append(b, '\n') //@ diag(`io.Writer.Write must not modify the provided buffer`)
_ = b
return 0, nil
}
func (T2) Write(b []byte) (int, error) {
b[0] = 0 //@ diag(`io.Writer.Write must not modify the provided buffer`)
return 0, nil
}
func (T3) Write(b []byte) string {
b[0] = 0
return ""
}
func (T4) Write(b []byte, r byte) (int, error) {
b[0] = r
return 0, nil
}
func (T7) Write(b Bytes) (int, error) {
b[0] = 0
return 0, nil
}
================================================
FILE: staticcheck/sa1023/testdata/go1.9/CheckWriterBufferModified/CheckWriterBufferModified.go
================================================
package pkg
type T5 struct{}
type T6 struct{}
type AliasByte = byte
type AliasByteSlice = []byte
type AliasInt = int
type AliasError = error
func (T5) Write(b []AliasByte) (int, error) {
b[0] = 0 //@ diag(`io.Writer.Write must not modify the provided buffer`)
return 0, nil
}
func (T6) Write(b AliasByteSlice) (AliasInt, AliasError) {
b[0] = 0 //@ diag(`io.Writer.Write must not modify the provided buffer`)
return 0, nil
}
================================================
FILE: staticcheck/sa1024/sa1024.go
================================================
package sa1024
import (
"go/constant"
"sort"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1024",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `A string cutset contains duplicate characters`,
Text: `The \'strings.TrimLeft\' and \'strings.TrimRight\' functions take cutsets, not
prefixes. A cutset is treated as a set of characters to remove from a
string. For example,
strings.TrimLeft("42133word", "1234")
will result in the string \'"word"\' – any characters that are 1, 2, 3 or
4 are cut from the left of the string.
In order to remove one string from another, use \'strings.TrimPrefix\' instead.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"strings.Trim": check,
"strings.TrimLeft": check,
"strings.TrimRight": check,
}
func check(call *callcheck.Call) {
arg := call.Args[1]
if !isUniqueStringCutset(arg.Value) {
const MsgNonUniqueCutset = "cutset contains duplicate characters"
arg.Invalid(MsgNonUniqueCutset)
}
}
func isUniqueStringCutset(v callcheck.Value) bool {
if c := callcheck.ExtractConstExpectKind(v, constant.String); c != nil {
s := constant.StringVal(c.Value)
rs := runeSlice(s)
if len(rs) < 2 {
return true
}
sort.Sort(rs)
for i, r := range rs[1:] {
if rs[i] == r {
return false
}
}
}
return true
}
type runeSlice []rune
func (rs runeSlice) Len() int { return len(rs) }
func (rs runeSlice) Less(i int, j int) bool { return rs[i] < rs[j] }
func (rs runeSlice) Swap(i int, j int) { rs[i], rs[j] = rs[j], rs[i] }
================================================
FILE: staticcheck/sa1024/sa1024_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1024
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1024/testdata/go1.0/CheckNonUniqueCutset/CheckNonUniqueCutset.go
================================================
package pkg
import "strings"
func fn(s string) {
_ = strings.TrimLeft(s, "")
_ = strings.TrimLeft(s, "a")
_ = strings.TrimLeft(s, "µ")
_ = strings.TrimLeft(s, "abc")
_ = strings.TrimLeft(s, "http://") //@ diag(`duplicate characters`)
}
================================================
FILE: staticcheck/sa1025/sa1025.go
================================================
package sa1025
import (
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1025",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`,
Since: "2019.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
call, ok := ins.(*ir.Call)
if !ok {
continue
}
if !irutil.IsCallTo(call.Common(), "(*time.Timer).Reset") {
continue
}
refs := call.Referrers()
if refs == nil {
continue
}
for _, ref := range irutil.FilterDebug(*refs) {
ifstmt, ok := ref.(*ir.If)
if !ok {
continue
}
found := false
for _, succ := range ifstmt.Block().Succs {
if len(succ.Preds) != 1 {
// Merge point, not a branch in the
// syntactical sense.
// FIXME(dh): this is broken for if
// statements a la "if x || y"
continue
}
irutil.Walk(succ, func(b *ir.BasicBlock) bool {
if !succ.Dominates(b) {
// We've reached the end of the branch
return false
}
for _, ins := range b.Instrs {
// TODO(dh): we should check that we're receiving from the
// channel of a time.Timer to further reduce false
// positives. Not a key priority, considering the rarity
// of Reset and the tiny likeliness of a false positive
//
// We intentionally don't handle aliases here, because
// we're only interested in time.Timer.C.
if ins, ok := ins.(*ir.Recv); ok && types.TypeString(ins.Chan.Type(), nil) == "<-chan time.Time" {
found = true
return false
}
}
return true
})
}
if found {
report.Report(pass, call, "it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring")
}
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa1025/sa1025_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1025
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1025/testdata/go1.0/CheckTimerResetReturnValue/CheckTimerResetReturnValue.go
================================================
package fn
import "time"
func fn1() {
t := time.NewTimer(time.Second)
t.Reset(time.Second)
}
func fn2() {
t := time.NewTimer(time.Second)
_ = t.Reset(time.Second)
}
func fn3() {
t := time.NewTimer(time.Second)
println(t.Reset(time.Second))
}
func fn4() {
t := time.NewTimer(time.Second)
if t.Reset(time.Second) {
println("x")
}
}
func fn5() {
t := time.NewTimer(time.Second)
if t.Reset(time.Second) { //@ diag(`it is not possible to use Reset's return value correctly`)
<-t.C
}
}
func fn6(x bool) {
// Not matched because we don't support complex boolean
// expressions
t := time.NewTimer(time.Second)
if t.Reset(time.Second) || x {
<-t.C
}
}
func fn7(x bool) {
// Not matched because we don't analyze that deeply
t := time.NewTimer(time.Second)
y := t.Reset(2 * time.Second)
z := x || y
println(z)
if z {
<-t.C
}
}
func fn8() {
t := time.NewTimer(time.Second)
abc := t.Reset(time.Second) //@ diag(`it is not possible to use Reset's return value correctly`)
if abc {
<-t.C
}
}
func fn9() {
t := time.NewTimer(time.Second)
if t.Reset(time.Second) {
println("x")
}
<-t.C
}
func fn10() {
t := time.NewTimer(time.Second)
if !t.Reset(time.Second) { //@ diag(`it is not possible to use Reset's return value correctly`)
<-t.C
}
}
func fn11(ch chan int) {
t := time.NewTimer(time.Second)
if !t.Reset(time.Second) {
<-ch
}
}
================================================
FILE: staticcheck/sa1026/sa1026.go
================================================
package sa1026
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/staticcheck/fakejson"
"honnef.co/go/tools/staticcheck/fakexml"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1026",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Cannot marshal channels or functions`,
Since: "2019.2",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"encoding/json.Marshal": checkJSON,
"encoding/xml.Marshal": checkXML,
"(*encoding/json.Encoder).Encode": checkJSON,
"(*encoding/xml.Encoder).Encode": checkXML,
}
func checkJSON(call *callcheck.Call) {
arg := call.Args[0]
T := arg.Value.Value.Type()
if err := fakejson.Marshal(T); err != nil {
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
if err.Path == "x" {
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ))
} else {
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path))
}
}
}
func checkXML(call *callcheck.Call) {
arg := call.Args[0]
T := arg.Value.Value.Type()
if err := fakexml.Marshal(T); err != nil {
switch err := err.(type) {
case *fakexml.UnsupportedTypeError:
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
if err.Path == "x" {
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ))
} else {
arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path))
}
case *fakexml.CyclicTypeError:
typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg))
if err.Path == "x" {
arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s", typ))
} else {
arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s, via %s", typ, err.Path))
}
case *fakexml.TagPathError:
// Vet does a better job at reporting this error, because it can flag the actual struct tags, not just the call to Marshal
default:
// These errors get reported by SA5008 instead, which can flag the actual fields, independently of calls to xml.Marshal
}
}
}
================================================
FILE: staticcheck/sa1026/sa1026_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1026
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1026/testdata/go1.0/CheckUnsupportedMarshal/CheckUnsupportedMarshal.go
================================================
package pkg
import (
"encoding/json"
"encoding/xml"
"time"
)
type T1 struct {
A int
B func() `json:"-" xml:"-"`
c chan int
}
type T2 struct {
T1
}
type T3 struct {
Ch chan int
}
type T4 struct {
C ValueMarshaler
}
type T5 struct {
B func() `xml:"-"`
}
type T6 struct {
B func() `json:"-"`
}
type T7 struct {
A int
B int
T3
}
type T8 struct {
C int
*T7
}
type T9 struct {
F PointerMarshaler
}
type T10 struct {
F *struct {
PointerMarshaler
}
}
type Recursive struct {
Field *Recursive
}
type ValueMarshaler chan int
func (ValueMarshaler) MarshalText() ([]byte, error) { return nil, nil }
type PointerMarshaler chan int
func (*PointerMarshaler) MarshalText() ([]byte, error) { return nil, nil }
func fn() {
var t1 T1
var t2 T2
var t3 T3
var t4 T4
var t5 T5
var t6 T6
var t8 T8
var t9 T9
var t10 T10
var t11 Recursive
json.Marshal(t1)
json.Marshal(t2)
json.Marshal(t3) //@ diag(`unsupported type chan int, via x.Ch`)
json.Marshal(t4)
json.Marshal(t5) //@ diag(`unsupported type func(), via x.B`)
json.Marshal(t6)
(*json.Encoder)(nil).Encode(t1)
(*json.Encoder)(nil).Encode(t2)
(*json.Encoder)(nil).Encode(t3) //@ diag(`unsupported type chan int, via x.Ch`)
(*json.Encoder)(nil).Encode(t4)
(*json.Encoder)(nil).Encode(t5) //@ diag(`unsupported type func(), via x.B`)
(*json.Encoder)(nil).Encode(t6)
xml.Marshal(t1)
xml.Marshal(t2)
xml.Marshal(t3) //@ diag(`unsupported type chan int, via x.Ch`)
xml.Marshal(t4)
xml.Marshal(t5)
xml.Marshal(t6) //@ diag(`unsupported type func(), via x.B`)
(*xml.Encoder)(nil).Encode(t1)
(*xml.Encoder)(nil).Encode(t2)
(*xml.Encoder)(nil).Encode(t3) //@ diag(`unsupported type chan int, via x.C`)
(*xml.Encoder)(nil).Encode(t4)
(*xml.Encoder)(nil).Encode(t5)
(*xml.Encoder)(nil).Encode(t6) //@ diag(`unsupported type func(), via x.B`)
json.Marshal(t8) //@ diag(`unsupported type chan int, via x.T7.T3.Ch`)
json.Marshal(t9) //@ diag(`unsupported type PointerMarshaler, via x.F`)
json.Marshal(&t9) // this is fine, t9 is addressable, therefore T9.D is, too
json.Marshal(t10) // this is fine, T10.F.D is addressable
xml.Marshal(t8) //@ diag(`unsupported type chan int, via x.T7.T3.Ch`)
xml.Marshal(t9) //@ diag(`unsupported type PointerMarshaler, via x.F`)
xml.Marshal(&t9) // this is fine, t9 is addressable, therefore T9.D is, too
xml.Marshal(t10) // this is fine, T10.F.D is addressable
json.Marshal(t11)
xml.Marshal(t11)
}
func addressabilityJSON() {
var a PointerMarshaler
var b []PointerMarshaler
var c struct {
F PointerMarshaler
}
var d [4]PointerMarshaler
json.Marshal(a) //@ diag(re`unsupported type PointerMarshaler$`)
json.Marshal(&a)
json.Marshal(b)
json.Marshal(&b)
json.Marshal(c) //@ diag(`unsupported type PointerMarshaler, via x.F`)
json.Marshal(&c)
json.Marshal(d) //@ diag(`unsupported type PointerMarshaler, via x[0]`)
json.Marshal(&d)
var m1 map[string]PointerMarshaler
json.Marshal(m1) //@ diag(`unsupported type PointerMarshaler, via x[k]`)
json.Marshal(&m1) //@ diag(`unsupported type PointerMarshaler, via x[k]`)
json.Marshal([]map[string]PointerMarshaler{m1}) //@ diag(`unsupported type PointerMarshaler, via x[0][k]`)
var m2 map[string]*PointerMarshaler
json.Marshal(m2)
json.Marshal(&m2)
json.Marshal([]map[string]*PointerMarshaler{m2})
}
func addressabilityXML() {
var a PointerMarshaler
var b []PointerMarshaler
var c struct {
XMLName xml.Name `json:"foo"`
F PointerMarshaler
}
var d [4]PointerMarshaler
xml.Marshal(a) //@ diag(re`unsupported type PointerMarshaler$`)
xml.Marshal(&a)
xml.Marshal(b)
xml.Marshal(&b)
xml.Marshal(c) //@ diag(`unsupported type PointerMarshaler, via x.F`)
xml.Marshal(&c)
xml.Marshal(d) //@ diag(`unsupported type PointerMarshaler, via x[0]`)
xml.Marshal(&d)
}
func mapsJSON() {
var good map[int]string
var bad map[interface{}]string
// the map key has to be statically known good; it must be a number or a string
json.Marshal(good)
json.Marshal(bad) //@ diag(`unsupported type map[interface{}]string`)
var m1 map[string]PointerMarshaler
json.Marshal(m1) //@ diag(`unsupported type PointerMarshaler, via x[k]`)
json.Marshal(&m1) //@ diag(`unsupported type PointerMarshaler, via x[k]`)
json.Marshal([]map[string]PointerMarshaler{m1}) //@ diag(`unsupported type PointerMarshaler, via x[0][k]`)
var m2 map[string]*PointerMarshaler
json.Marshal(m2)
json.Marshal(&m2)
json.Marshal([]map[string]*PointerMarshaler{m2})
var m3 map[string]ValueMarshaler
json.Marshal(m3)
json.Marshal(&m3)
json.Marshal([]map[string]ValueMarshaler{m3})
var m4 map[string]*ValueMarshaler
json.Marshal(m4)
json.Marshal(&m4)
json.Marshal([]map[string]*ValueMarshaler{m4})
var m5 map[ValueMarshaler]string
var m6 map[*ValueMarshaler]string
var m7 map[PointerMarshaler]string
var m8 map[*PointerMarshaler]string
json.Marshal(m5)
json.Marshal(m6)
json.Marshal(m7) //@ diag(`unsupported type map[PointerMarshaler]string`)
json.Marshal(m8)
}
func mapsXML() {
// encoding/xml doesn't support any maps
var bad map[string]string
xml.Marshal(bad) //@ diag(`unsupported type`)
}
func fieldPriorityJSON() {
// In this example, the channel doesn't matter, because T1.F has higher priority than T1.T2.F
type lT2 struct {
F chan int
}
type lT1 struct {
F int
lT2
}
json.Marshal(lT1{})
// In this example, it does matter
type lT4 struct {
C chan int
}
type lT3 struct {
F int
lT4
}
json.Marshal(lT3{}) //@ diag(`unsupported type chan int, via x.lT4.C`)
}
func fieldPriorityXML() {
// In this example, the channel doesn't matter, because T1.F has higher priority than T1.T2.F
type lT2 struct {
F chan int
}
type lT1 struct {
F int
lT2
}
xml.Marshal(lT1{})
// In this example, it does matter
type lT4 struct {
C chan int
}
type lT3 struct {
F int
lT4
}
xml.Marshal(lT3{}) //@ diag(`unsupported type chan int, via x.lT4.C`)
}
func longPathJSON() {
var foo struct {
Field struct {
Field2 []struct {
Map map[string]chan int
}
}
}
json.Marshal(foo) //@ diag(`unsupported type chan int, via x.Field.Field2[0].Map[k]`)
}
func otherPackageJSON() {
var x time.Ticker
json.Marshal(x) //@ diag(`unsupported type <-chan time.Time, via x.C`)
}
func longPathXML() {
var foo struct {
Field struct {
Field2 []struct {
Map map[string]chan int
}
}
}
xml.Marshal(foo) //@ diag(`unsupported type map[string]chan int, via x.Field.Field2[0].Map`)
}
func otherPackageXML() {
var x time.Ticker
xml.Marshal(x) //@ diag(`unsupported type <-chan time.Time, via x.C`)
}
type ToplevelPointerMarshalerXML struct {
Field map[string]string
}
func (*ToplevelPointerMarshalerXML) MarshalXML(*xml.Encoder, xml.StartElement) error {
return nil
}
type ToplevelPointerMarshalerText struct {
Field map[string]string
}
func (*ToplevelPointerMarshalerText) MarshalText() ([]byte, error) {
return nil, nil
}
func toplevelPointer() {
xml.Marshal(&ToplevelPointerMarshalerXML{})
xml.Marshal(&ToplevelPointerMarshalerText{})
xml.Marshal(ToplevelPointerMarshalerXML{}) //@ diag(`unsupported type`)
xml.Marshal(ToplevelPointerMarshalerText{}) //@ diag(`unsupported type`)
}
func cyclicPointer() {
type P *P
type S2 struct {
Bar P
}
type S1 struct {
Foo S2
}
var s S1
xml.Marshal(s) //@ diag(`cyclic type P, via x.Foo.Bar`)
}
func functionAsArgument(arg T1) {
// https://staticcheck.io/issues/1660
json.Marshal(functionAsArgument) //@ diag(`unsupported type func(arg T1)`)
xml.Marshal(functionAsArgument) //@ diag(`unsupported type func(arg T1)`)
}
================================================
FILE: staticcheck/sa1026/testdata/go1.18/CheckUnsupportedMarshal/generics.go
================================================
package pkg
import (
"encoding/json"
"encoding/xml"
)
type LMap[K comparable, V any] struct {
M1 map[K]V
M2 map[K]chan int
}
func (lm *LMap[K, V]) MarshalJSON() {
json.Marshal(lm.M1)
json.Marshal(lm.M2) //@ diag(`unsupported type`)
}
func recursiveGeneric() {
// don't recurse infinitely
var t Tree[int]
json.Marshal(t)
xml.Marshal(t)
}
type Tree[T any] struct {
Node *Node[T]
}
type Node[T any] struct {
Tree *Tree[T]
}
================================================
FILE: staticcheck/sa1027/sa1027.go
================================================
package sa1027
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1027",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkAtomicAlignment),
},
Doc: &lint.RawDocumentation{
Title: `Atomic access to 64-bit variable must be 64-bit aligned`,
Text: `On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to
arrange for 64-bit alignment of 64-bit words accessed atomically. The
first word in a variable or in an allocated struct, array, or slice
can be relied upon to be 64-bit aligned.
You can use the structlayout tool to inspect the alignment of fields
in a struct.`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkAtomicAlignment = map[string]callcheck.Check{
"sync/atomic.AddInt64": checkAtomicAlignmentImpl,
"sync/atomic.AddUint64": checkAtomicAlignmentImpl,
"sync/atomic.CompareAndSwapInt64": checkAtomicAlignmentImpl,
"sync/atomic.CompareAndSwapUint64": checkAtomicAlignmentImpl,
"sync/atomic.LoadInt64": checkAtomicAlignmentImpl,
"sync/atomic.LoadUint64": checkAtomicAlignmentImpl,
"sync/atomic.StoreInt64": checkAtomicAlignmentImpl,
"sync/atomic.StoreUint64": checkAtomicAlignmentImpl,
"sync/atomic.SwapInt64": checkAtomicAlignmentImpl,
"sync/atomic.SwapUint64": checkAtomicAlignmentImpl,
}
func checkAtomicAlignmentImpl(call *callcheck.Call) {
sizes := call.Pass.TypesSizes
if sizes.Sizeof(types.Typ[types.Uintptr]) != 4 {
// Not running on a 32-bit platform
return
}
v, ok := irutil.Flatten(call.Args[0].Value.Value).(*ir.FieldAddr)
if !ok {
// TODO(dh): also check indexing into arrays and slices
return
}
T := v.X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct)
fields := make([]*types.Var, 0, T.NumFields())
for i := 0; i < T.NumFields() && i <= v.Field; i++ {
fields = append(fields, T.Field(i))
}
off := sizes.Offsetsof(fields)[v.Field]
if off%8 != 0 {
msg := fmt.Sprintf("address of non 64-bit aligned field %s passed to %s",
T.Field(v.Field).Name(),
irutil.CallName(call.Instr.Common()))
call.Invalid(msg)
}
}
================================================
FILE: staticcheck/sa1027/sa1027_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1027
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1027/testdata/go1.0/CheckAtomicAlignment/atomic32.go
================================================
// +build 386 arm armbe mips mipsle ppc s390 sparc riscv
package pkg
import "sync/atomic"
type T struct {
A int64
B int32
C int64
}
func fn() {
var v T
atomic.AddInt64(&v.A, 0)
atomic.AddInt64(&v.C, 0) //@ diag(`address of non 64-bit aligned field C passed to sync/atomic.AddInt64`)
atomic.LoadInt64(&v.C) //@ diag(`address of non 64-bit aligned field C passed to sync/atomic.LoadInt64`)
}
func fn2(t *T) {
addr := &t.C
if true {
atomic.LoadInt64(addr) //@ diag(`address of non 64-bit`)
} else {
_ = addr
}
}
================================================
FILE: staticcheck/sa1027/testdata/go1.0/CheckAtomicAlignment/atomic64.go
================================================
// +build amd64 amd64p32 arm64 ppc64 ppc64le mips64 mips64le mips64p32 mips64p32le s390x sparc64 riscv64 loong64
package pkg
import "sync/atomic"
type T struct {
A int64
B int32
C int64
}
func fn() {
var v T
atomic.AddInt64(&v.A, 0)
atomic.AddInt64(&v.C, 0)
atomic.LoadInt64(&v.C)
}
================================================
FILE: staticcheck/sa1028/sa1028.go
================================================
package sa1028
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1028",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `\'sort.Slice\' can only be used on slices`,
Text: `The first argument of \'sort.Slice\' must be a slice.`,
Since: "2020.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"sort.Slice": check,
"sort.SliceIsSorted": check,
"sort.SliceStable": check,
}
func check(call *callcheck.Call) {
c := call.Instr.Common().StaticCallee()
arg := call.Args[0]
T := arg.Value.Value.Type().Underlying()
switch T.(type) {
case *types.Interface:
// we don't know.
// TODO(dh): if the value is a phi node we can look at its edges
if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value == nil {
// literal nil, e.g. sort.Sort(nil, ...)
arg.Invalid(fmt.Sprintf("cannot call %s on nil literal", c))
}
case *types.Slice:
// this is fine
default:
// this is not fine
arg.Invalid(fmt.Sprintf("%s must only be called on slices, was called on %s", c, T))
}
}
================================================
FILE: staticcheck/sa1028/sa1028_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1028
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1028/testdata/go1.0/CheckSortSlice/slice.go
================================================
package pkg
import "sort"
type T1 []int
type T2 T1
type T3 [1]int
type T4 string
func fn(arg1 interface{}, arg2 []int) {
var v1 T1
var v2 T2
var v3 T3
var v4 T4
var v5 []int
var v6 interface{} = []int{}
var v7 interface{}
if true {
v7 = []int{}
} else {
v7 = 0
}
var v8 interface{} = 0
sort.Slice(arg1, nil)
sort.Slice(arg2, nil)
sort.Slice(v1, nil)
sort.Slice(v2, nil)
sort.Slice(v3, nil) //@ diag(`sort.Slice must only be called on slices, was called on [1]int`)
sort.Slice(v4, nil) //@ diag(`sort.Slice must only be called on slices, was called on string`)
sort.Slice(v5, nil)
sort.Slice(v6, nil)
sort.Slice(v7, nil)
sort.Slice(v8, nil) //@ diag(`sort.Slice must only be called on slices, was called on int`)
sort.Slice([]int{}, nil)
sort.Slice(0, nil) //@ diag(`sort.Slice must only be called on slices, was called on int`)
sort.Slice(nil, nil) //@ diag(`cannot call sort.Slice on nil literal`)
sort.SliceIsSorted(0, nil) //@ diag(`sort.SliceIsSorted must only be called on slices, was called on int`)
sort.SliceStable(0, nil) //@ diag(`sort.SliceStable must only be called on slices, was called on int`)
}
================================================
FILE: staticcheck/sa1029/sa1029.go
================================================
package sa1029
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1029",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkWithValueKeyRules),
},
Doc: &lint.RawDocumentation{
Title: `Inappropriate key in call to \'context.WithValue\'`,
Text: `The provided key must be comparable and should not be
of type \'string\' or any other built-in type to avoid collisions between
packages using context. Users of \'WithValue\' should define their own
types for keys.
To avoid allocating when assigning to an \'interface{}\',
context keys often have concrete type \'struct{}\'. Alternatively,
exported context key variables' static type should be a pointer or
interface.`,
Since: "2020.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkWithValueKeyRules = map[string]callcheck.Check{
"context.WithValue": checkWithValueKey,
}
func checkWithValueKey(call *callcheck.Call) {
arg := call.Args[1]
T := arg.Value.Value.Type()
if typ, ok := types.Unalias(T).(*types.Basic); ok {
if _, ok := T.(*types.Alias); ok {
arg.Invalid(
fmt.Sprintf("should not use built-in type %s (via alias %s) as key for value; define your own type to avoid collisions", typ, types.TypeString(T, types.RelativeTo(call.Pass.Pkg))))
} else {
arg.Invalid(
fmt.Sprintf("should not use built-in type %s as key for value; define your own type to avoid collisions", typ))
}
}
// TODO(dh): we should probably flag all anonymous structs, as they all risk collisions
if s, ok := T.(*types.Struct); ok && s.NumFields() == 0 {
arg.Invalid("should not use empty anonymous struct as key for value; define your own type to avoid collisions")
} else if !types.Comparable(T) {
arg.Invalid(fmt.Sprintf("keys used with context.WithValue must be comparable, but type %s is not comparable", T))
}
}
================================================
FILE: staticcheck/sa1029/sa1029_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1029
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1029/testdata/go1.0/CheckWithValueKey/CheckWithValueKey.go
================================================
package pkg
import "context"
type T string
type T2 struct {
A int
}
type T3 struct {
A []int
}
func fn(arg1 interface{}, arg2 string) {
var ctx context.Context
context.WithValue(ctx, "hi", nil) //@ diag(`should not use built-in type string`)
context.WithValue(ctx, arg1, nil)
context.WithValue(ctx, arg2, nil) //@ diag(`should not use built-in type string`)
v1 := interface{}("byte")
context.WithValue(ctx, v1, nil) //@ diag(`should not use built-in type string`)
var key T
context.WithValue(ctx, key, nil)
v2 := interface{}(key)
context.WithValue(ctx, v2, nil)
context.WithValue(ctx, T(""), nil)
context.WithValue(ctx, string(key), nil) //@ diag(`should not use built-in type string`)
context.WithValue(ctx, []byte(nil), nil) //@ diag(`must be comparable`)
context.WithValue(ctx, T2{}, nil)
context.WithValue(ctx, T3{}, nil) //@ diag(`must be comparable`)
context.WithValue(ctx, struct{ key string }{"k"}, nil)
var empty struct{}
context.WithValue(ctx, struct{}{}, nil) //@ diag(`should not use empty anonymous struct`)
context.WithValue(ctx, empty, nil) //@ diag(`should not use empty anonymous struct`)
}
================================================
FILE: staticcheck/sa1030/sa1030.go
================================================
package sa1030
import (
"fmt"
"go/constant"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1030",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Invalid argument in call to a \'strconv\' function`,
Text: `This check validates the format, number base and bit size arguments of
the various parsing and formatting functions in \'strconv\'.`,
Since: "2021.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"strconv.ParseComplex": func(call *callcheck.Call) {
validateComplexBitSize(call.Args[knowledge.Arg("strconv.ParseComplex.bitSize")])
},
"strconv.ParseFloat": func(call *callcheck.Call) {
validateFloatBitSize(call.Args[knowledge.Arg("strconv.ParseFloat.bitSize")])
},
"strconv.ParseInt": func(call *callcheck.Call) {
validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseInt.bitSize")], 0, 64)
validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseInt.base")])
},
"strconv.ParseUint": func(call *callcheck.Call) {
validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseUint.bitSize")], 0, 64)
validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseUint.base")])
},
"strconv.FormatComplex": func(call *callcheck.Call) {
validateComplexFormat(call.Args[knowledge.Arg("strconv.FormatComplex.fmt")])
validateComplexBitSize(call.Args[knowledge.Arg("strconv.FormatComplex.bitSize")])
},
"strconv.FormatFloat": func(call *callcheck.Call) {
validateFloatFormat(call.Args[knowledge.Arg("strconv.FormatFloat.fmt")])
validateFloatBitSize(call.Args[knowledge.Arg("strconv.FormatFloat.bitSize")])
},
"strconv.FormatInt": func(call *callcheck.Call) {
validateIntBase(call.Args[knowledge.Arg("strconv.FormatInt.base")])
},
"strconv.FormatUint": func(call *callcheck.Call) {
validateIntBase(call.Args[knowledge.Arg("strconv.FormatUint.base")])
},
"strconv.AppendFloat": func(call *callcheck.Call) {
validateFloatFormat(call.Args[knowledge.Arg("strconv.AppendFloat.fmt")])
validateFloatBitSize(call.Args[knowledge.Arg("strconv.AppendFloat.bitSize")])
},
"strconv.AppendInt": func(call *callcheck.Call) {
validateIntBase(call.Args[knowledge.Arg("strconv.AppendInt.base")])
},
"strconv.AppendUint": func(call *callcheck.Call) {
validateIntBase(call.Args[knowledge.Arg("strconv.AppendUint.base")])
},
}
func validateDiscreetBitSize(arg *callcheck.Argument, size1 int, size2 int) {
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
val, _ := constant.Int64Val(c.Value)
if val != int64(size1) && val != int64(size2) {
arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be either %d or %d", size1, size2))
}
}
}
func validateComplexBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 64, 128) }
func validateFloatBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 32, 64) }
func validateContinuousBitSize(arg *callcheck.Argument, min int, max int) {
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
val, _ := constant.Int64Val(c.Value)
if val < int64(min) || val > int64(max) {
arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be within %d and %d", min, max))
}
}
}
func validateIntBase(arg *callcheck.Argument) {
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
val, _ := constant.Int64Val(c.Value)
if val < 2 {
arg.Invalid("'base' must not be smaller than 2")
}
if val > 36 {
arg.Invalid("'base' must not be larger than 36")
}
}
}
func validateIntBaseAllowZero(arg *callcheck.Argument) {
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
val, _ := constant.Int64Val(c.Value)
if val < 2 && val != 0 {
arg.Invalid("'base' must not be smaller than 2, unless it is 0")
}
if val > 36 {
arg.Invalid("'base' must not be larger than 36")
}
}
}
func validateComplexFormat(arg *callcheck.Argument) {
validateFloatFormat(arg)
}
func validateFloatFormat(arg *callcheck.Argument) {
if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil {
val, _ := constant.Int64Val(c.Value)
switch val {
case 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X':
default:
arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", val))
}
}
}
================================================
FILE: staticcheck/sa1030/sa1030_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1030
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1030/testdata/go1.0/CheckStrconv/CheckStrconv.go
================================================
package pkg
import "strconv"
func fn() {
strconv.ParseFloat("", 16) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.ParseFloat("", 32)
strconv.ParseFloat("", 64)
strconv.ParseFloat("", 128) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.ParseInt("", 0, -1) //@ diag(`'bitSize' argument is invalid, must be within 0 and 64`)
strconv.ParseInt("", 0, 0)
strconv.ParseInt("", 0, 1)
strconv.ParseInt("", 0, 64)
strconv.ParseInt("", 0, 65) //@ diag(`'bitSize' argument is invalid, must be within 0 and 64`)
strconv.ParseInt("", -1, 0) //@ diag(`'base' must not be smaller than 2, unless it is 0`)
strconv.ParseInt("", 1, 0) //@ diag(`'base' must not be smaller than 2, unless it is 0`)
strconv.ParseInt("", 2, 0)
strconv.ParseInt("", 10, 0)
strconv.ParseInt("", 36, 0)
strconv.ParseInt("", 37, 0) //@ diag(`'base' must not be larger than 36`)
strconv.ParseUint("", 0, -1) //@ diag(`'bitSize' argument is invalid, must be within 0 and 64`)
strconv.ParseUint("", 0, 0)
strconv.ParseUint("", 0, 1)
strconv.ParseUint("", 0, 64)
strconv.ParseUint("", 0, 65) //@ diag(`'bitSize' argument is invalid, must be within 0 and 64`)
strconv.ParseUint("", -1, 0) //@ diag(`'base' must not be smaller than 2, unless it is 0`)
strconv.ParseUint("", 1, 0) //@ diag(`'base' must not be smaller than 2, unless it is 0`)
strconv.ParseUint("", 2, 0)
strconv.ParseUint("", 10, 0)
strconv.ParseUint("", 36, 0)
strconv.ParseUint("", 37, 0) //@ diag(`'base' must not be larger than 36`)
strconv.FormatFloat(0, 'e', 0, 18) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.FormatFloat(0, 'e', 0, 32)
strconv.FormatFloat(0, 'e', 0, 64)
strconv.FormatFloat(0, 'e', 0, 128) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.FormatFloat(0, 'j', 0, 32) //@ diag(`'fmt' argument is invalid: unknown format 'j'`)
strconv.FormatInt(0, 0) //@ diag(`'base' must not be smaller than 2`)
strconv.FormatInt(0, 1) //@ diag(`'base' must not be smaller than 2`)
strconv.FormatInt(0, 2)
strconv.FormatInt(0, 3)
strconv.FormatInt(0, 36)
strconv.FormatInt(0, 37) //@ diag(`'base' must not be larger than 36`)
strconv.FormatUint(0, 0) //@ diag(`'base' must not be smaller than 2`)
strconv.FormatUint(0, 1) //@ diag(`'base' must not be smaller than 2`)
strconv.FormatUint(0, 2)
strconv.FormatUint(0, 3)
strconv.FormatUint(0, 36)
strconv.FormatUint(0, 37) //@ diag(`'base' must not be larger than 36`)
strconv.AppendFloat(nil, 0, 'e', 0, 18) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.AppendFloat(nil, 0, 'e', 0, 32)
strconv.AppendFloat(nil, 0, 'e', 0, 64)
strconv.AppendFloat(nil, 0, 'e', 0, 128) //@ diag(`'bitSize' argument is invalid, must be either 32 or 64`)
strconv.AppendFloat(nil, 0, 'j', 0, 32) //@ diag(`'fmt' argument is invalid: unknown format 'j'`)
strconv.AppendInt(nil, 0, 0) //@ diag(`'base' must not be smaller than 2`)
strconv.AppendInt(nil, 0, 1) //@ diag(`'base' must not be smaller than 2`)
strconv.AppendInt(nil, 0, 2)
strconv.AppendInt(nil, 0, 3)
strconv.AppendInt(nil, 0, 36)
strconv.AppendInt(nil, 0, 37) //@ diag(`'base' must not be larger than 36`)
strconv.AppendUint(nil, 0, 0) //@ diag(`'base' must not be smaller than 2`)
strconv.AppendUint(nil, 0, 1) //@ diag(`'base' must not be smaller than 2`)
strconv.AppendUint(nil, 0, 2)
strconv.AppendUint(nil, 0, 3)
strconv.AppendUint(nil, 0, 36)
strconv.AppendUint(nil, 0, 37) //@ diag(`'base' must not be larger than 36`)
}
================================================
FILE: staticcheck/sa1030/testdata/go1.15/CheckStrconv/CheckStrconv.go
================================================
// +build go1.15
package pkg
import "strconv"
func fn() {
strconv.ParseComplex("", 32) //@ diag(`'bitSize' argument is invalid, must be either 64 or 128`)
strconv.ParseComplex("", 64)
strconv.ParseComplex("", 128)
strconv.ParseComplex("", 256) //@ diag(`'bitSize' argument is invalid, must be either 64 or 128`)
strconv.FormatComplex(0, 'e', 0, 32) //@ diag(`'bitSize' argument is invalid, must be either 64 or 128`)
strconv.FormatComplex(0, 'e', 0, 64)
strconv.FormatComplex(0, 'e', 0, 128)
strconv.FormatComplex(0, 'e', 0, 256) //@ diag(`'bitSize' argument is invalid, must be either 64 or 128`)
strconv.FormatComplex(0, 'j', 0, 64) //@ diag(`'fmt' argument is invalid: unknown format 'j'`)
}
================================================
FILE: staticcheck/sa1030/testdata/go1.15/CheckStrconv/stub.go
================================================
package pkg
================================================
FILE: staticcheck/sa1031/sa1031.go
================================================
package sa1031
import (
"go/constant"
"go/token"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1031",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkEncodeRules),
},
Doc: &lint.RawDocumentation{
Title: `Overlapping byte slices passed to an encoder`,
Text: `In an encoding function of the form \'Encode(dst, src)\', \'dst\' and
\'src\' were found to reference the same memory. This can result in
\'src\' bytes being overwritten before they are read, when the encoder
writes more than one byte per \'src\' byte.`,
Since: "2024.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkEncodeRules = map[string]callcheck.Check{
"encoding/ascii85.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/ascii85.Encode.dst"), knowledge.Arg("encoding/ascii85.Encode.src")),
"(*encoding/base32.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base32.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base32.Encoding).Encode.src")),
"(*encoding/base64.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base64.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base64.Encoding).Encode.src")),
"encoding/hex.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/hex.Encode.dst"), knowledge.Arg("encoding/hex.Encode.src")),
}
func checkNonOverlappingDstSrc(dstArg, srcArg int) callcheck.Check {
return func(call *callcheck.Call) {
dst := call.Args[dstArg]
src := call.Args[srcArg]
_, dstConst := irutil.Flatten(dst.Value.Value).(*ir.Const)
_, srcConst := irutil.Flatten(src.Value.Value).(*ir.Const)
if dstConst || srcConst {
// one of the arguments is nil, therefore overlap is not possible
return
}
if dst.Value == src.Value {
// simple case of f(b, b)
dst.Invalid("overlapping dst and src")
return
}
dstSlice, ok := irutil.Flatten(dst.Value.Value).(*ir.Slice)
if !ok {
return
}
srcSlice, ok := irutil.Flatten(src.Value.Value).(*ir.Slice)
if !ok {
return
}
if irutil.Flatten(dstSlice.X) != irutil.Flatten(srcSlice.X) {
// differing underlying arrays, all is well
return
}
l1 := irutil.Flatten(dstSlice.Low)
l2 := irutil.Flatten(srcSlice.Low)
c1, ok1 := l1.(*ir.Const)
c2, ok2 := l2.(*ir.Const)
if l1 == l2 || (ok1 && ok2 && constant.Compare(c1.Value, token.EQL, c2.Value)) {
// dst and src are the same slice, and have the same lower bound
dst.Invalid("overlapping dst and src")
return
}
}
}
================================================
FILE: staticcheck/sa1031/sa1031_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1031
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1031/testdata/go1.0/CheckEncodingAscii85/CheckEncodingAscii85.go
================================================
package pkg
import (
"encoding/ascii85"
)
func fn() {
ascii85.Encode(nil, nil)
ascii85.Encode(make([]byte, 0), nil)
sliceA := make([]byte, 8)
sliceB := make([]byte, 8)
ascii85.Encode(sliceA, sliceB)
ascii85.Encode(sliceA, sliceA) //@ diag(`overlapping dst and src`)
ascii85.Encode(sliceA[1:], sliceA[2:])
ascii85.Encode(sliceA[1:], sliceA[1:]) //@ diag(`overlapping dst and src`)
sliceC := sliceA
ascii85.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
if true {
ascii85.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
}
sliceD := sliceA[1:]
sliceE := sliceA[1:]
if true {
ascii85.Encode(sliceD, sliceE) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaA(a *[4]byte) {
low := 2
x := a[low:]
if true {
y := a[low:]
ascii85.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaB(a *[4]byte) {
x := a[:]
if true {
y := a[:]
ascii85.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
================================================
FILE: staticcheck/sa1031/testdata/go1.0/CheckEncodingBase32/CheckEncodingBase32.go
================================================
package pkg
import (
"encoding/base32"
)
func fn() {
encoding := base32.StdEncoding
encoding.Encode(nil, nil)
encoding.Encode(make([]byte, 0), nil)
sliceA := make([]byte, 8)
sliceB := make([]byte, 8)
encoding.Encode(sliceA, sliceB)
encoding.Encode(sliceA, sliceA) //@ diag(`overlapping dst and src`)
encoding.Encode(sliceA[1:], sliceA[2:])
encoding.Encode(sliceA[1:], sliceA[1:]) //@ diag(`overlapping dst and src`)
sliceC := sliceA
encoding.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
if true {
encoding.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
}
sliceD := sliceA[1:]
sliceE := sliceA[1:]
if true {
encoding.Encode(sliceD, sliceE) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaA(a *[4]byte) {
encoding := base32.StdEncoding
low := 2
x := a[low:]
if true {
y := a[low:]
encoding.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaB(a *[4]byte) {
encoding := base32.StdEncoding
x := a[:]
if true {
y := a[:]
encoding.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
================================================
FILE: staticcheck/sa1031/testdata/go1.0/CheckEncodingBase64/CheckEncodingBase64.go
================================================
package pkg
import (
"encoding/base64"
)
func fn() {
encoding := base64.StdEncoding
encoding.Encode(nil, nil)
encoding.Encode(make([]byte, 0), nil)
sliceA := make([]byte, 8)
sliceB := make([]byte, 8)
encoding.Encode(sliceA, sliceB)
encoding.Encode(sliceA, sliceA) //@ diag(`overlapping dst and src`)
encoding.Encode(sliceA[1:], sliceA[2:])
encoding.Encode(sliceA[1:], sliceA[1:]) //@ diag(`overlapping dst and src`)
sliceC := sliceA
encoding.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
if true {
encoding.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
}
sliceD := sliceA[1:]
sliceE := sliceA[1:]
if true {
encoding.Encode(sliceD, sliceE) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaA(a *[4]byte) {
encoding := base64.StdEncoding
low := 2
x := a[low:]
if true {
y := a[low:]
encoding.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaB(a *[4]byte) {
encoding := base64.StdEncoding
x := a[:]
if true {
y := a[:]
encoding.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
================================================
FILE: staticcheck/sa1031/testdata/go1.0/CheckEncodingHex/CheckEncodingHex.go
================================================
package pkg
import (
"encoding/hex"
)
func fn() {
hex.Encode(nil, nil)
hex.Encode(make([]byte, 0), nil)
sliceA := make([]byte, 8)
sliceB := make([]byte, 8)
hex.Encode(sliceA, sliceB)
hex.Encode(sliceA, sliceA) //@ diag(`overlapping dst and src`)
hex.Encode(sliceA[1:], sliceA[2:])
hex.Encode(sliceA[1:], sliceA[1:]) //@ diag(`overlapping dst and src`)
sliceC := sliceA
hex.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
if true {
hex.Encode(sliceA, sliceC) //@ diag(`overlapping dst and src`)
}
sliceD := sliceA[1:]
sliceE := sliceA[1:]
if true {
hex.Encode(sliceD, sliceE) //@ diag(`overlapping dst and src`)
}
var b bool
if !b && true {
hex.Encode(sliceD, sliceE) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaA(a *[4]byte) {
low := 2
x := a[low:]
if true {
y := a[low:]
hex.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
func fooSigmaB(a *[4]byte) {
x := a[:]
if true {
y := a[:]
hex.Encode(x, y) //@ diag(`overlapping dst and src`)
}
}
================================================
FILE: staticcheck/sa1032/sa1032.go
================================================
package sa1032
import (
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA1032",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Wrong order of arguments to \'errors.Is\'`,
Text: `
The first argument of the function \'errors.Is\' is the error
that we have and the second argument is the error we're trying to match against.
For example:
if errors.Is(err, io.EOF) { ... }
This check detects some cases where the two arguments have been swapped. It
flags any calls where the first argument is referring to a package-level error
variable, such as
if errors.Is(io.EOF, err) { /* this is wrong */ }`,
Since: "2024.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"errors.Is": validateIs,
}
func validateIs(call *callcheck.Call) {
if len(call.Args) != 2 {
return
}
global := func(arg *callcheck.Argument) *ir.Global {
v, ok := arg.Value.Value.(*ir.Load)
if !ok {
return nil
}
g, _ := v.X.(*ir.Global)
return g
}
x, y := call.Args[0], call.Args[1]
gx := global(x)
if gx == nil {
return
}
if pkgx := gx.Package().Pkg; pkgx != nil && pkgx.Path() != call.Pass.Pkg.Path() {
// x is a global that's not in this package
if gy := global(y); gy != nil {
if pkgy := gy.Package().Pkg; pkgy != nil && pkgy.Path() != call.Pass.Pkg.Path() {
// Both arguments refer to globals that aren't in this package. This can
// genuinely happen for external tests that check that one error "is"
// another one. net/http's external tests, for example, do
// `errors.Is(http.ErrNotSupported, errors.ErrUnsupported)`.
return
}
}
call.Invalid("arguments have the wrong order")
}
}
================================================
FILE: staticcheck/sa1032/sa1032_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa1032
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa1032/testdata/go1.0/example.com/ErrorsOrder/ErrorsOrder.go
================================================
package errors
import (
"errors"
"io"
"io/fs"
)
var gErr = errors.New("global")
type myErr struct{}
func (myErr) Error() string { return "" }
func is() {
err := errors.New("oh noes")
_ = errors.Is(err, fs.ErrNotExist)
_ = errors.Is(fs.ErrNotExist, err) //@ diag(`wrong order`)
if errors.Is(err, fs.ErrNotExist) {
}
if errors.Is(fs.ErrNotExist, err) { //@ diag(`wrong order`)
}
_ = errors.Is(gErr, fs.ErrNotExist)
_ = errors.Is(fs.ErrNotExist, gErr) //@ diag(`wrong order`)
if errors.Is(gErr, fs.ErrNotExist) {
}
if errors.Is(fs.ErrNotExist, gErr) { //@ diag(`wrong order`)
}
_ = errors.Is(myErr{}, fs.ErrNotExist)
_ = errors.Is(fs.ErrNotExist, myErr{}) //@ diag(`wrong order`)
if errors.Is(myErr{}, fs.ErrNotExist) {
}
if errors.Is(fs.ErrNotExist, myErr{}) { //@ diag(`wrong order`)
}
_ = errors.Is(&myErr{}, fs.ErrNotExist)
_ = errors.Is(fs.ErrNotExist, &myErr{}) //@ diag(`wrong order`)
if errors.Is(&myErr{}, fs.ErrNotExist) {
}
if errors.Is(fs.ErrNotExist, &myErr{}) { //@ diag(`wrong order`)
}
if !errors.Is(io.EOF, fs.ErrNotExist) {
// If both arguments are globals there's no right or wrong order.
}
}
================================================
FILE: staticcheck/sa2000/sa2000.go
================================================
package sa2000
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA2000",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `\'(*sync.WaitGroup).Add\' called inside the goroutine, leading to a race condition`,
Text: `\'(*sync.WaitGroup).Add\' must be called before starting the goroutine
it is meant to wait for. Calling \'Add\' inside the goroutine creates a race
condition between the call to \'Add\' and the call to \'Wait\'.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkWaitgroupAddQ = pattern.MustParse(`
(GoStmt
(CallExpr
(FuncLit
_
call@(CallExpr (Symbol "(*sync.WaitGroup).Add") _):_) _))`)
func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, checkWaitgroupAddQ) {
call := m.State["call"].(ast.Node)
report.Report(pass, call, fmt.Sprintf("should call %s before starting the goroutine to avoid a race", report.Render(pass, call)))
}
return nil, nil
}
================================================
FILE: staticcheck/sa2000/sa2000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa2000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa2000/testdata/go1.0/CheckWaitgroupAdd/CheckWaitgroupAdd.go
================================================
package pkg
import (
"sync"
)
func fn() {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
wg.Done()
}()
go func() {
wg.Add(1) //@ diag(`should call wg.Add(1) before starting`)
wg.Done()
}()
wg.Add(1)
go func(wg sync.WaitGroup) {
wg.Done()
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup) {
wg.Done()
}(&wg)
wg.Wait()
}
func fn2(wg sync.WaitGroup) {
wg.Add(1)
}
================================================
FILE: staticcheck/sa2001/sa2001.go
================================================
package sa2001
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA2001",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Empty critical section, did you mean to defer the unlock?`,
Text: `Empty critical sections of the kind
mu.Lock()
mu.Unlock()
are very often a typo, and the following was intended instead:
mu.Lock()
defer mu.Unlock()
Do note that sometimes empty critical sections can be useful, as a
form of signaling to wait on another goroutine. Many times, there are
simpler ways of achieving the same effect. When that isn't the case,
the code should be amply commented to avoid confusion. Combining such
comments with a \'//lint:ignore\' directive can be used to suppress this
rare false positive.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
if pass.Pkg.Path() == "sync_test" {
// exception for the sync package's tests
return nil, nil
}
// Initially it might seem like this check would be easier to
// implement using IR. After all, we're only checking for two
// consecutive method calls. In reality, however, there may be any
// number of other instructions between the lock and unlock, while
// still constituting an empty critical section. For example,
// given `m.x().Lock(); m.x().Unlock()`, there will be a call to
// x(). In the AST-based approach, this has a tiny potential for a
// false positive (the second call to x might be doing work that
// is protected by the mutex). In an IR-based approach, however,
// it would miss a lot of real bugs.
mutexParams := func(s ast.Stmt) (x ast.Expr, funcName string, ok bool) {
expr, ok := s.(*ast.ExprStmt)
if !ok {
return nil, "", false
}
call, ok := astutil.Unparen(expr.X).(*ast.CallExpr)
if !ok {
return nil, "", false
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return nil, "", false
}
fn, ok := pass.TypesInfo.ObjectOf(sel.Sel).(*types.Func)
if !ok {
return nil, "", false
}
sig := fn.Type().(*types.Signature)
if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
return nil, "", false
}
return sel.X, fn.Name(), true
}
fn := func(node ast.Node) {
block := node.(*ast.BlockStmt)
if len(block.List) < 2 {
return
}
for i := range block.List[:len(block.List)-1] {
sel1, method1, ok1 := mutexParams(block.List[i])
sel2, method2, ok2 := mutexParams(block.List[i+1])
if !ok1 || !ok2 || report.Render(pass, sel1) != report.Render(pass, sel2) {
continue
}
if (method1 == "Lock" && method2 == "Unlock") ||
(method1 == "RLock" && method2 == "RUnlock") {
report.Report(pass, block.List[i+1], "empty critical section")
}
}
}
code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa2001/sa2001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa2001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa2001/testdata/go1.0/CheckEmptyCriticalSection/CheckEmptyCriticalSection.go
================================================
package pkg
import "sync"
func fn1() {
var x sync.Mutex
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
}
func fn2() {
x := struct {
m1 struct {
m2 sync.Mutex
}
}{}
x.m1.m2.Lock()
x.m1.m2.Unlock() //@ diag(`empty critical section`)
}
func fn3() {
var x sync.RWMutex
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
x.RLock()
x.RUnlock() //@ diag(`empty critical section`)
x.Lock()
defer x.Unlock()
}
func fn4() {
x := struct {
m func() *sync.Mutex
}{
m: func() *sync.Mutex {
return new(sync.Mutex)
},
}
x.m().Lock()
x.m().Unlock() //@ diag(`empty critical section`)
}
func fn5() {
i := 0
var x sync.Mutex
x.Lock()
i++
x.Unlock()
}
func fn6() {
x := &sync.Mutex{}
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
}
func fn7() {
x := &struct {
sync.Mutex
}{}
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
}
func fn8() {
var x sync.Locker
x = new(sync.Mutex)
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
}
func fn9() {
x := &struct {
sync.Locker
}{&sync.Mutex{}}
x.Lock()
x.Unlock() //@ diag(`empty critical section`)
}
type T struct{}
func (T) Lock() int { return 0 }
func (T) Unlock() {}
func fn10() {
var t T
t.Lock()
t.Unlock()
}
================================================
FILE: staticcheck/sa2002/sa2002.go
================================================
package sa2002
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA2002",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Called \'testing.T.FailNow\' or \'SkipNow\' in a goroutine, which isn't allowed`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
gostmt, ok := ins.(*ir.Go)
if !ok {
continue
}
var fn *ir.Function
switch val := gostmt.Call.Value.(type) {
case *ir.Function:
fn = val
case *ir.MakeClosure:
fn = val.Fn.(*ir.Function)
default:
continue
}
if fn.Blocks == nil {
continue
}
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
call, ok := ins.(*ir.Call)
if !ok {
continue
}
if call.Call.IsInvoke() {
continue
}
callee := call.Call.StaticCallee()
if callee == nil {
continue
}
recv := callee.Signature.Recv()
if recv == nil {
continue
}
if !typeutil.IsPointerToTypeWithName(recv.Type(), "testing.common") {
continue
}
fn, ok := call.Call.StaticCallee().Object().(*types.Func)
if !ok {
continue
}
name := fn.Name()
switch name {
case "FailNow", "Fatal", "Fatalf", "SkipNow", "Skip", "Skipf":
default:
continue
}
// TODO(dh): don't report multiple diagnostics
// for multiple calls to T.Fatal, but do
// collect all of them as related information
report.Report(pass, gostmt, fmt.Sprintf("the goroutine calls T.%s, which must be called in the same goroutine as the test", name),
report.Related(call, fmt.Sprintf("call to T.%s", name)))
}
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa2002/sa2002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa2002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa2002/testdata/go1.0/CheckConcurrentTesting/CheckConcurrentTesting.go
================================================
package pkg
import "testing"
func fn1() {
var t *testing.T
go func() { //@ diag(`the goroutine calls T.Fatal, which must be called in the same goroutine as the test`)
t.Fatal()
}()
go fn2(t) //@ diag(`the goroutine calls T.Fatal, which must be called in the same goroutine as the test`)
fn := func() {
t.Fatal()
}
go fn() //@ diag(`the goroutine calls T.Fatal, which must be called in the same goroutine as the test`)
}
func fn2(t *testing.T) {
t.Fatal()
}
func fn3(t *testing.T) {
fn := func() {
t.Fatal()
}
fn()
}
func fn4(t *testing.T) {
t.Fatal()
}
func fn5(t *testing.T) {
func() {
t.Fatal()
}()
}
================================================
FILE: staticcheck/sa2003/sa2003.go
================================================
package sa2003
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA2003",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`,
Text: `Deferring a call to \'Lock\' immediately after locking is almost always
a typo. For example:
mu.Lock()
defer mu.Lock()
While this does not strictly guarantee a deadlock depending on how the
surrounding code is structured, it is highly likely to be a mistake.
The intended code was likely this:
mu.Lock()
defer mu.Unlock()`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, block := range fn.Blocks {
instrs := irutil.FilterDebug(block.Instrs)
if len(instrs) < 2 {
continue
}
for i, ins := range instrs[:len(instrs)-1] {
call, ok := ins.(*ir.Call)
if !ok {
continue
}
if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
continue
}
nins, ok := instrs[i+1].(*ir.Defer)
if !ok {
continue
}
if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") {
continue
}
if call.Common().Args[0] != nins.Call.Args[0] {
continue
}
name := shortCallName(call.Common())
alt := ""
switch name {
case "Lock":
alt = "Unlock"
case "RLock":
alt = "RUnlock"
}
report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt))
}
}
}
return nil, nil
}
func shortCallName(call *ir.CallCommon) string {
if call.IsInvoke() {
return ""
}
switch v := call.Value.(type) {
case *ir.Function:
fn, ok := v.Object().(*types.Func)
if !ok {
return ""
}
return fn.Name()
case *ir.Builtin:
return v.Name()
}
return ""
}
================================================
FILE: staticcheck/sa2003/sa2003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa2003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa2003/testdata/go1.0/CheckDeferLock/CheckDeferLock.go
================================================
package pkg
import "sync"
var r sync.Mutex
var rw sync.RWMutex
func fn1() {
r.Lock()
defer r.Lock() //@ diag(`deferring Lock right after having locked already; did you mean to defer Unlock`)
}
func fn2() {
r.Lock()
defer r.Unlock()
}
func fn3() {
println("")
defer r.Lock()
}
func fn4() {
rw.RLock()
defer rw.RLock() //@ diag(`deferring RLock right after having locked already; did you mean to defer RUnlock`)
}
func fn5() {
rw.RLock()
defer rw.Lock()
}
func fn6() {
r.Lock()
defer rw.Lock()
}
================================================
FILE: staticcheck/sa3000/sa3000.go
================================================
package sa3000
import (
"go/ast"
"go/types"
"go/version"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA3000",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `\'TestMain\' doesn't call \'os.Exit\', hiding test failures`,
Text: `Test executables (and in turn \"go test\") exit with a non-zero status
code if any tests failed. When specifying your own \'TestMain\' function,
it is your responsibility to arrange for this, by calling \'os.Exit\' with
the correct code. The correct code is returned by \'(*testing.M).Run\', so
the usual way of implementing \'TestMain\' is to end it with
\'os.Exit(m.Run())\'.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
var (
fnmain ast.Node
callsExit bool
callsRun bool
arg types.Object
)
fn := func(node ast.Node, push bool) bool {
if !push {
if fnmain != nil && node == fnmain {
if !callsExit && callsRun {
report.Report(pass, fnmain, "TestMain should call os.Exit to set exit code")
}
fnmain = nil
callsExit = false
callsRun = false
arg = nil
}
return true
}
switch node := node.(type) {
case *ast.FuncDecl:
if fnmain != nil {
return true
}
if !isTestMain(pass, node) {
return false
}
if version.Compare(code.StdlibVersion(pass, node), "go1.15") >= 0 {
// Beginning with Go 1.15, the test framework will call
// os.Exit for us.
return false
}
fnmain = node
arg = pass.TypesInfo.ObjectOf(node.Type.Params.List[0].Names[0])
return true
case *ast.CallExpr:
if code.IsCallTo(pass, node, "os.Exit") {
callsExit = true
return false
}
sel, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
ident, ok := sel.X.(*ast.Ident)
if !ok {
return true
}
if arg != pass.TypesInfo.ObjectOf(ident) {
return true
}
if sel.Sel.Name == "Run" {
callsRun = true
return false
}
return true
default:
lint.ExhaustiveTypeSwitch(node)
return true
}
}
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.FuncDecl)(nil), (*ast.CallExpr)(nil)}, fn)
return nil, nil
}
func isTestMain(pass *analysis.Pass, decl *ast.FuncDecl) bool {
if decl.Name.Name != "TestMain" {
return false
}
if len(decl.Type.Params.List) != 1 {
return false
}
arg := decl.Type.Params.List[0]
if len(arg.Names) != 1 {
return false
}
return code.IsOfPointerToTypeWithName(pass, arg.Type, "testing.M")
}
================================================
FILE: staticcheck/sa3000/sa3000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa3000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa3000/testdata/go1.15/CheckTestMainExit-1/CheckTestMainExit-1.go
================================================
package pkg
import "testing"
func TestMain(m *testing.M) {
m.Run()
}
================================================
FILE: staticcheck/sa3000/testdata/go1.4/CheckTestMainExit-1/CheckTestMainExit-1.go
================================================
package pkg
import "testing"
func TestMain(m *testing.M) { //@ diag(`should call os.Exit`)
m.Run()
}
================================================
FILE: staticcheck/sa3000/testdata/go1.4/CheckTestMainExit-2/CheckTestMainExit-2.go
================================================
package pkg
import (
"os"
"testing"
)
func TestMain(m *testing.M) {
m.Run()
os.Exit(1)
}
================================================
FILE: staticcheck/sa3000/testdata/go1.4/CheckTestMainExit-3/CheckTestMainExit-3.go
================================================
package pkg
import "testing"
func TestMain(t *testing.T) {
}
================================================
FILE: staticcheck/sa3000/testdata/go1.4/CheckTestMainExit-4/CheckTestMainExit-4.go
================================================
package pkg
import (
"os"
"testing"
)
func helper() { os.Exit(1) }
func TestMain(m *testing.M) { //@ diag(`should call os.Exit`)
// FIXME(dominikh): this is a false positive
m.Run()
helper()
}
================================================
FILE: staticcheck/sa3000/testdata/go1.4/CheckTestMainExit-5/CheckTestMainExit-5.go
================================================
package pkg
import (
"os"
"testing"
)
func helper(m *testing.M) { os.Exit(m.Run()) }
func TestMain(m *testing.M) {
helper(m)
}
================================================
FILE: staticcheck/sa3001/sa3001.go
================================================
package sa3001
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA3001",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Assigning to \'b.N\' in benchmarks distorts the results`,
Text: `The testing package dynamically sets \'b.N\' to improve the reliability of
benchmarks and uses it in computations to determine the duration of a
single operation. Benchmark code must not alter \'b.N\' as this would
falsify results.`,
Since: "2017.1",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`(AssignStmt sel@(SelectorExpr selX (Ident "N")) "=" [_] )`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, query) {
assign := node.(*ast.AssignStmt)
if !code.IsOfPointerToTypeWithName(pass, m.State["selX"].(ast.Expr), "testing.B") {
continue
}
report.Report(pass, assign,
fmt.Sprintf("should not assign to %s", report.Render(pass, m.State["sel"])))
}
return nil, nil
}
================================================
FILE: staticcheck/sa3001/sa3001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa3001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa3001/testdata/go1.0/CheckBenchmarkN/CheckBenchmarkN.go
================================================
package foo
import "testing"
func foo() {
var b *testing.B
b.N = 1 //@ diag(`should not assign to b.N`)
_ = b
}
================================================
FILE: staticcheck/sa4000/sa4000.go
================================================
package sa4000
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4000",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Binary operator has identical expressions on both sides`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
var isFloat func(T types.Type) bool
isFloat = func(T types.Type) bool {
tset := typeutil.NewTypeSet(T)
if len(tset.Terms) == 0 {
// no terms, so floats are a possibility
return true
}
return tset.Any(func(term *types.Term) bool {
switch typ := term.Type().Underlying().(type) {
case *types.Basic:
kind := typ.Kind()
return kind == types.Float32 || kind == types.Float64
case *types.Array:
return isFloat(typ.Elem())
case *types.Struct:
for field := range typ.Fields() {
if isFloat(field.Type()) {
return true
}
}
return false
default:
return false
}
})
}
// TODO(dh): this check ignores the existence of side-effects and
// happily flags fn() == fn() – so far, we've had only two complains
// about false positives, and it's caught several bugs in real
// code.
//
// We special case functions from the math/rand package. Someone ran
// into the following false positive: "rand.Intn(2) - rand.Intn(2), which I wrote to generate values {-1, 0, 1} with {0.25, 0.5, 0.25} probability."
skipComparableCheck := func(c inspector.Cursor) bool {
op, ok := c.Node().(*ast.BinaryExpr)
if !ok {
return false
}
if clit, ok := op.X.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
return false
}
if clit, ok := op.Y.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 {
return false
}
// TODO(dh): we should probably skip ParenExprs, but users should
// probably not use unnecessary ParenExprs.
vspec, ok := c.Parent().Node().(*ast.ValueSpec)
if !ok {
return false
}
e, i := c.ParentEdge()
if e != edge.ValueSpec_Values {
return false
}
if vspec.Names[i].Name == "_" {
// `var _ = T{} == T{}` is permitted, as a compile-time
// check that T implements comparable.
return true
}
return false
}
for c := range code.Cursor(pass).Preorder((*ast.BinaryExpr)(nil)) {
node := c.Node()
op := node.(*ast.BinaryExpr)
switch op.Op {
case token.EQL, token.NEQ:
if skipComparableCheck(c) {
continue
}
case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT,
token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ:
default:
// For some ops, such as + and *, it can make sense to
// have identical operands
continue
}
if isFloat(pass.TypesInfo.TypeOf(op.X)) {
// 'float float' makes sense for several operators.
// We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead.
continue
}
if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) {
continue
}
if report.Render(pass, op.X) != report.Render(pass, op.Y) {
continue
}
l1, ok1 := op.X.(*ast.BasicLit)
l2, ok2 := op.Y.(*ast.BasicLit)
if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) {
// cgo generates the following function call:
// _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0
// instead of true in case the user shadowed the
// identifier. Ideally we'd restrict this exception to
// calls of _cgoCheckPointer, but it's not worth the
// hassle of keeping track of the stack.
// are very rare to begin with, and we're mostly checking
// for them to catch typos such as 1 == 1 where the user
// meant to type i == 1. The odds of a false negative for
// 0 == 0 are slim.
continue
}
if expr, ok := op.X.(*ast.CallExpr); ok {
call := code.CallName(pass, expr)
switch call {
case "math/rand.Int",
"math/rand.Int31",
"math/rand.Int31n",
"math/rand.Int63",
"math/rand.Int63n",
"math/rand.Intn",
"math/rand.Uint32",
"math/rand.Uint64",
"math/rand.ExpFloat64",
"math/rand.Float32",
"math/rand.Float64",
"math/rand.NormFloat64",
"(*math/rand.Rand).Int",
"(*math/rand.Rand).Int31",
"(*math/rand.Rand).Int31n",
"(*math/rand.Rand).Int63",
"(*math/rand.Rand).Int63n",
"(*math/rand.Rand).Intn",
"(*math/rand.Rand).Uint32",
"(*math/rand.Rand).Uint64",
"(*math/rand.Rand).ExpFloat64",
"(*math/rand.Rand).Float32",
"(*math/rand.Rand).Float64",
"(*math/rand.Rand).NormFloat64",
"math/rand/v2.Int",
"math/rand/v2.Int32",
"math/rand/v2.Int32N",
"math/rand/v2.Int64",
"math/rand/v2.Int64N",
"math/rand/v2.IntN",
"math/rand/v2.N",
"math/rand/v2.Uint",
"math/rand/v2.Uint32",
"math/rand/v2.Uint32N",
"math/rand/v2.Uint64",
"math/rand/v2.Uint64N",
"math/rand/v2.UintN",
"math/rand/v2.ExpFloat64",
"math/rand/v2.Float32",
"math/rand/v2.Float64",
"math/rand/v2.NormFloat64",
"(*math/rand/v2.Rand).Int",
"(*math/rand/v2.Rand).Int32",
"(*math/rand/v2.Rand).Int32N",
"(*math/rand/v2.Rand).Int64",
"(*math/rand/v2.Rand).Int64N",
"(*math/rand/v2.Rand).IntN",
"(*math/rand/v2.Rand).N",
"(*math/rand/v2.Rand).Uint",
"(*math/rand/v2.Rand).Uint32",
"(*math/rand/v2.Rand).Uint32N",
"(*math/rand/v2.Rand).Uint64",
"(*math/rand/v2.Rand).Uint64N",
"(*math/rand/v2.Rand).UintN",
"(*math/rand/v2.Rand).ExpFloat64",
"(*math/rand/v2.Rand).Float32",
"(*math/rand/v2.Rand).Float64",
"(*math/rand/v2.Rand).NormFloat64":
continue
}
}
report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op))
}
return nil, nil
}
================================================
FILE: staticcheck/sa4000/sa4000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4000/testdata/go1.0/CheckLhsRhsIdentical/CheckLhsRhsIdentical.go
================================================
package pkg
import "math/rand"
type Float float64
type Floats [5]float64
type Ints [5]int
type T1 struct {
A float64
B float64
}
type T2 struct {
A float64
B int
}
func fn(a int, s []int, f1 float64, f2 Float, fs Floats, is Ints, t1 T1, t2 T2) {
if 0 == 0 { //@ diag(`identical expressions`)
println()
}
if 1 == 1 { //@ diag(`identical expressions`)
println()
}
if a == a { //@ diag(`identical expressions`)
println()
}
if a != a { //@ diag(`identical expressions`)
println()
}
if s[0] == s[0] { //@ diag(`identical expressions`)
println()
}
if 1&1 == 1 { //@ diag(`identical expressions`)
println()
}
if (1 + 2 + 3) == (1 + 2 + 3) { //@ diag(`identical expressions`)
println()
}
if f1 == f1 {
println()
}
if f1 != f1 {
println()
}
if f1 > f1 {
println()
}
if f1-f1 == 0 {
println()
}
if f2 == f2 {
println()
}
if fs == fs {
println()
}
if is == is { //@ diag(`identical expressions`)
println()
}
if t1 == t1 {
println()
}
if t2 == t2 {
println()
}
}
func fn2() {
_ = rand.Int() - rand.Int()
_ = rand.Int31() - rand.Int31()
_ = rand.Int31n(0) - rand.Int31n(0)
_ = rand.Int63() - rand.Int63()
_ = rand.Int63n(0) - rand.Int63n(0)
_ = rand.Intn(0) - rand.Intn(0)
_ = rand.Uint32() - rand.Uint32()
_ = rand.Uint64() - rand.Uint64()
_ = rand.ExpFloat64() - rand.ExpFloat64()
_ = rand.Float32() - rand.Float32()
_ = rand.Float64() - rand.Float64()
_ = rand.NormFloat64() - rand.NormFloat64()
var rng *rand.Rand
_ = rng.Int() - rng.Int()
_ = rng.Int31() - rng.Int31()
_ = rng.Int31n(0) - rng.Int31n(0)
_ = rng.Int63() - rng.Int63()
_ = rng.Int63n(0) - rng.Int63n(0)
_ = rng.Intn(0) - rng.Intn(0)
_ = rng.Uint32() - rng.Uint32()
_ = rng.Uint64() - rng.Uint64()
_ = rng.ExpFloat64() - rng.ExpFloat64()
_ = rng.Float32() - rng.Float32()
_ = rng.Float64() - rng.Float64()
_ = rng.NormFloat64() - rng.NormFloat64()
// not flagged; the return value is an interface and might be a float.
_ = rand.NewSource(0) == rand.NewSource(0)
}
type T struct{ X int }
// Not flagged, used as a compile-time assertion that T is comparable
var _ = T{} == T{}
// Flagged, the presence of initializers is dubious
var _ = T{1} == T{1} //@ diag(`identical expressions`)
================================================
FILE: staticcheck/sa4000/testdata/go1.0/CheckLhsRhsIdentical/cgo.go
================================================
package pkg
// void foo(void **p) {}
import "C"
import "unsafe"
func Foo() {
var p unsafe.Pointer
C.foo(&p)
if 0 == 0 {
// We don't currently flag this instance of 0 == 0 because of
// our cgo-specific exception.
println()
}
}
================================================
FILE: staticcheck/sa4000/testdata/go1.18/CheckLhsRhsIdentical/generics.go
================================================
package pkg
func tpfn1[T comparable](x T) {
if x != x {
}
}
func tpfn2[T int | string](x T) {
if x != x { //@ diag(`identical expressions`)
}
}
func tpfn3[T int | float64](x T) {
if x != x {
}
}
func tpfn4[E int | int64, T [4]E](x T) {
if x != x { //@ diag(`identical expressions`)
}
}
func tpfn5[E int | float64, T [4]E](x T) {
if x != x {
}
}
================================================
FILE: staticcheck/sa4000/testdata/go1.22/CheckLhsRhsIdentical/randv2.go
================================================
package pkg
import "math/rand/v2"
func fn() {
_ = rand.N[uint](5) == rand.N[uint](5)
}
================================================
FILE: staticcheck/sa4001/sa4001.go
================================================
package sa4001
import (
"go/ast"
"regexp"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4001",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `\'&*x\' gets simplified to \'x\', it does not copy \'x\'`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
// cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't
// want to flag.
cgoIdent = regexp.MustCompile(`^_C(func|var)_.+$`)
checkIneffectiveCopyQ1 = pattern.MustParse(`(UnaryExpr "&" (StarExpr obj))`)
checkIneffectiveCopyQ2 = pattern.MustParse(`(StarExpr (UnaryExpr "&" _))`)
)
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
if m, ok := code.Match(pass, checkIneffectiveCopyQ1, node); ok {
if ident, ok := m.State["obj"].(*ast.Ident); !ok || !cgoIdent.MatchString(ident.Name) {
report.Report(pass, node, "&*x will be simplified to x. It will not copy x.")
}
} else if _, ok := code.Match(pass, checkIneffectiveCopyQ2, node); ok {
report.Report(pass, node, "*&x will be simplified to x. It will not copy x.")
}
}
code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.StarExpr)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4001/sa4001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4001/testdata/go1.0/CheckIneffectiveCopy/CheckIneffectiveCopy.go
================================================
package pkg
type T struct{}
func fn1(_ *T) {}
func fn2() {
t1 := &T{}
fn1(&*t1) //@ diag(`will not copy`)
fn1(*&t1) //@ diag(`will not copy`)
_Cvar_something := &T{}
fn1(&*_Cvar_something)
}
================================================
FILE: staticcheck/sa4003/sa4003.go
================================================
package sa4003
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"math"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4003",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Comparing unsigned values against negative values is pointless`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
isobj := func(expr ast.Expr, name string) bool {
if name == "" {
return false
}
sel, ok := expr.(*ast.SelectorExpr)
if !ok {
return false
}
return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name)
}
fn := func(node ast.Node) {
expr := node.(*ast.BinaryExpr)
tx := pass.TypesInfo.TypeOf(expr.X)
basic, ok := tx.Underlying().(*types.Basic)
if !ok {
return
}
// We only check for the math constants and integer literals, not for all constant expressions. This is to avoid
// false positives when constant values differ under different build tags.
var (
maxMathConst string
minMathConst string
maxLiteral constant.Value
minLiteral constant.Value
)
switch basic.Kind() {
case types.Uint8:
maxMathConst = "math.MaxUint8"
minLiteral = constant.MakeUint64(0)
maxLiteral = constant.MakeUint64(math.MaxUint8)
case types.Uint16:
maxMathConst = "math.MaxUint16"
minLiteral = constant.MakeUint64(0)
maxLiteral = constant.MakeUint64(math.MaxUint16)
case types.Uint32:
maxMathConst = "math.MaxUint32"
minLiteral = constant.MakeUint64(0)
maxLiteral = constant.MakeUint64(math.MaxUint32)
case types.Uint64:
maxMathConst = "math.MaxUint64"
minLiteral = constant.MakeUint64(0)
maxLiteral = constant.MakeUint64(math.MaxUint64)
case types.Uint:
// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
maxMathConst = "math.MaxUint64"
minLiteral = constant.MakeUint64(0)
maxLiteral = constant.MakeUint64(math.MaxUint64)
case types.Int8:
minMathConst = "math.MinInt8"
maxMathConst = "math.MaxInt8"
minLiteral = constant.MakeInt64(math.MinInt8)
maxLiteral = constant.MakeInt64(math.MaxInt8)
case types.Int16:
minMathConst = "math.MinInt16"
maxMathConst = "math.MaxInt16"
minLiteral = constant.MakeInt64(math.MinInt16)
maxLiteral = constant.MakeInt64(math.MaxInt16)
case types.Int32:
minMathConst = "math.MinInt32"
maxMathConst = "math.MaxInt32"
minLiteral = constant.MakeInt64(math.MinInt32)
maxLiteral = constant.MakeInt64(math.MaxInt32)
case types.Int64:
minMathConst = "math.MinInt64"
maxMathConst = "math.MaxInt64"
minLiteral = constant.MakeInt64(math.MinInt64)
maxLiteral = constant.MakeInt64(math.MaxInt64)
case types.Int:
// TODO(dh): we could chose 32 bit vs 64 bit depending on the file's build tags
minMathConst = "math.MinInt64"
maxMathConst = "math.MaxInt64"
minLiteral = constant.MakeInt64(math.MinInt64)
maxLiteral = constant.MakeInt64(math.MaxInt64)
}
isLiteral := func(expr ast.Expr, c constant.Value) bool {
if c == nil {
return false
}
return code.IsIntegerLiteral(pass, expr, c)
}
isZeroLiteral := func(expr ast.Expr) bool {
return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0))
}
if (expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
(expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
report.Report(pass, expr, fmt.Sprintf("no value of type %s is greater than %s", basic, maxMathConst), report.FilterGenerated())
}
if expr.Op == token.LEQ && (isobj(expr.Y, maxMathConst) || isLiteral(expr.Y, maxLiteral)) ||
expr.Op == token.GEQ && (isobj(expr.X, maxMathConst) || isLiteral(expr.X, maxLiteral)) {
report.Report(pass, expr, fmt.Sprintf("every value of type %s is <= %s", basic, maxMathConst), report.FilterGenerated())
}
if (basic.Info() & types.IsUnsigned) != 0 {
if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) ||
(expr.Op == token.GTR && isZeroLiteral(expr.X)) {
report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than 0", basic), report.FilterGenerated())
}
if expr.Op == token.GEQ && isZeroLiteral(expr.Y) ||
expr.Op == token.LEQ && isZeroLiteral(expr.X) {
report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= 0", basic), report.FilterGenerated())
}
} else {
if (expr.Op == token.LSS || expr.Op == token.LEQ) && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
(expr.Op == token.GTR || expr.Op == token.GEQ) && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
report.Report(pass, expr, fmt.Sprintf("no value of type %s is less than %s", basic, minMathConst), report.FilterGenerated())
}
if expr.Op == token.GEQ && (isobj(expr.Y, minMathConst) || isLiteral(expr.Y, minLiteral)) ||
expr.Op == token.LEQ && (isobj(expr.X, minMathConst) || isLiteral(expr.X, minLiteral)) {
report.Report(pass, expr, fmt.Sprintf("every value of type %s is >= %s", basic, minMathConst), report.FilterGenerated())
}
}
}
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4003/sa4003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4003/testdata/go1.0/CheckExtremeComparison/CheckExtremeComparison.go
================================================
package pkg
import "math"
func fn1() {
var (
u8 uint8
u16 uint16
u uint
i8 int8
i16 int16
i int
)
_ = u8 > math.MaxUint8 //@ diag(`no value of type uint8 is greater than math.MaxUint8`)
_ = u8 >= math.MaxUint8 //@ diag(`no value of type uint8 is greater than math.MaxUint8`)
_ = u8 >= 0 //@ diag(`every value of type uint8 is >= 0`)
_ = u8 <= math.MaxUint8 //@ diag(`every value of type uint8 is <= math.MaxUint8`)
_ = u8 > 0
_ = u8 >= 1
_ = u8 < math.MaxUint8
_ = u8 < 0 //@ diag(`no value of type uint8 is less than 0`)
_ = u8 <= 0
_ = 0 > u8 //@ diag(`no value of type uint8 is less than 0`)
_ = 0 >= u8
_ = u16 > math.MaxUint8
_ = u16 > math.MaxUint16 //@ diag(`no value of type uint16 is greater than math.MaxUint16`)
_ = u16 <= math.MaxUint8
_ = u16 <= math.MaxUint16 //@ diag(`every value of type uint16 is <= math.MaxUint16`)
_ = u > math.MaxUint32
_ = i8 > math.MaxInt8 //@ diag(`no value of type int8 is greater than math.MaxInt8`)
_ = i16 > math.MaxInt8
_ = i16 > math.MaxInt16 //@ diag(`no value of type int16 is greater than math.MaxInt16`)
_ = i > math.MaxInt32
_ = i8 < 0
_ = i8 <= math.MinInt8 //@ diag(`no value of type int8 is less than math.MinInt8`)
_ = i8 < math.MinInt8 //@ diag(`no value of type int8 is less than math.MinInt8`)
_ = i8 >= math.MinInt8 //@ diag(`every value of type int8 is >= math.MinInt8`)
_ = i8 > -128
_ = i8 >= -128 //@ diag(`every value of type int8 is >= math.MinInt8`)
_ = i8 < 127
_ = i8 <= 127 //@ diag(`every value of type int8 is <= math.MaxInt8`)
_ = i8 > 127 //@ diag(`no value of type int8 is greater than math.MaxInt8`)
_ = i8 >= 127 //@ diag(`no value of type int8 is greater than math.MaxInt8`)
_ = u8 < 255
_ = u8 <= 255 //@ diag(`every value of type uint8 is <= math.MaxUint8`)
_ = u8 > 255 //@ diag(`no value of type uint8 is greater than math.MaxUint8`)
_ = u8 >= 255 //@ diag(`no value of type uint8 is greater than math.MaxUint8`)
const k = 255
_ = u8 <= k
}
================================================
FILE: staticcheck/sa4003/testdata/go1.0/CheckExtremeComparison/CheckExtremeComparison64.go
================================================
//go:build amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || mips64p32 || mips64p32le || sparc64
// +build amd64 arm64 ppc64 ppc64le mips64 mips64le mips64p32 mips64p32le sparc64
package pkg
import "math"
func fn2() {
var (
u uint
i int
)
_ = u > math.MaxUint64 //@ diag(`no value of type uint is greater than math.MaxUint64`)
_ = i > math.MaxInt64 //@ diag(`no value of type int is greater than math.MaxInt64`)
}
================================================
FILE: staticcheck/sa4004/sa4004.go
================================================
package sa4004
import (
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4004",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `The loop exits unconditionally after one iteration`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// This check detects some, but not all unconditional loop exits.
// We give up in the following cases:
//
// - a goto anywhere in the loop. The goto might skip over our
// return, and we don't check that it doesn't.
//
// - any nested, unlabelled continue, even if it is in another
// loop or closure.
fn := func(node ast.Node) {
var body *ast.BlockStmt
switch fn := node.(type) {
case *ast.FuncDecl:
body = fn.Body
case *ast.FuncLit:
body = fn.Body
default:
lint.ExhaustiveTypeSwitch(node)
}
if body == nil {
return
}
labels := map[types.Object]ast.Stmt{}
ast.Inspect(body, func(node ast.Node) bool {
label, ok := node.(*ast.LabeledStmt)
if !ok {
return true
}
labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt
return true
})
ast.Inspect(body, func(node ast.Node) bool {
var loop ast.Node
var body *ast.BlockStmt
switch node := node.(type) {
case *ast.ForStmt:
body = node.Body
loop = node
case *ast.RangeStmt:
ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool {
switch term.Type().Underlying().(type) {
case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array:
return true
case *types.Map:
// looping once over a map is a valid pattern for
// getting an arbitrary element.
return false
case *types.Signature:
// we have no idea what semantics the function implements
return false
default:
lint.ExhaustiveTypeSwitch(term.Type().Underlying())
return false
}
})
if !ok {
return true
}
body = node.Body
loop = node
default:
return true
}
if len(body.List) < 2 {
// TODO(dh): is this check needed? when body.List < 2,
// then we can't find both an unconditional exit and a
// branching statement (if, ...). and we don't flag
// unconditional exits if there has been no branching
// in the loop body.
// avoid flagging the somewhat common pattern of using
// a range loop to get the first element in a slice,
// or the first rune in a string.
return true
}
var unconditionalExit ast.Node
hasBranching := false
for _, stmt := range body.List {
switch stmt := stmt.(type) {
case *ast.BranchStmt:
switch stmt.Tok {
case token.BREAK:
if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
unconditionalExit = stmt
}
case token.CONTINUE:
if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop {
unconditionalExit = nil
return false
}
}
case *ast.ReturnStmt:
unconditionalExit = stmt
case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt:
hasBranching = true
}
}
if unconditionalExit == nil || !hasBranching {
return false
}
ast.Inspect(body, func(node ast.Node) bool {
if branch, ok := node.(*ast.BranchStmt); ok {
switch branch.Tok {
case token.GOTO:
unconditionalExit = nil
return false
case token.CONTINUE:
if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop {
return true
}
unconditionalExit = nil
return false
}
}
return true
})
if unconditionalExit != nil {
report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated")
}
return true
})
}
code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4004/sa4004_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4004
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4004/testdata/go1.0/CheckIneffectiveLoop/CheckIneffectiveLoop.go
================================================
package pkg
func fn() {
for {
if true {
println()
}
break //@ diag(`the surrounding loop is unconditionally terminated`)
}
for {
if true {
break
} else {
break
}
}
for range ([]int)(nil) {
if true {
println()
}
break //@ diag(`the surrounding loop is unconditionally terminated`)
}
for range (map[int]int)(nil) {
if true {
println()
}
break
}
for {
if true {
goto Label
}
break
Label:
}
for {
if true {
continue
}
break
}
for {
if true {
continue
}
break
}
}
var z = func() {
for {
if true {
println()
}
break //@ diag(`the surrounding loop is unconditionally terminated`)
}
}
================================================
FILE: staticcheck/sa4004/testdata/go1.18/CheckIneffectiveLoop/CheckIneffectiveLoop_generics.go
================================================
package pkg
func _[T map[int]int]() {
for range (T)(nil) {
if true {
println()
}
break
}
}
func _[K comparable, V any, M ~map[K]V]() {
for range (M)(nil) {
if true {
println()
}
break
}
}
func _[T []int]() {
for range (T)(nil) {
if true {
println()
}
break //@ diag(`the surrounding loop is unconditionally terminated`)
}
}
func _[T any, S ~[]T](x S) {
for range x {
if true {
println()
}
break //@ diag(`the surrounding loop is unconditionally terminated`)
}
}
================================================
FILE: staticcheck/sa4005/sa4005.go
================================================
package sa4005
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4005",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`,
Since: "2021.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// The analysis only considers the receiver and its first level
// fields. It doesn't look at other parameters, nor at nested
// fields.
//
// The analysis does not detect all kinds of dead stores, only
// those of fields that are never read after the write. That is,
// we do not flag 'a.x = 1; a.x = 2; _ = a.x'. We might explore
// this again if we add support for SROA to go/ir and implement
// https://github.com/dominikh/go-tools/issues/191.
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
fnLoop:
for _, fn := range irpkg.SrcFuncs {
if recv := fn.Signature.Recv(); recv == nil {
continue
} else if _, ok := recv.Type().Underlying().(*types.Struct); !ok {
continue
}
recv := fn.Params[0]
refs := irutil.FilterDebug(*recv.Referrers())
if len(refs) != 1 {
continue
}
store, ok := refs[0].(*ir.Store)
if !ok {
continue
}
alloc, ok := store.Addr.(*ir.Alloc)
if !ok || alloc.Heap {
continue
}
reads := map[int][]ir.Instruction{}
writes := map[int][]ir.Instruction{}
for _, ref := range *alloc.Referrers() {
switch ref := ref.(type) {
case *ir.FieldAddr:
for _, refref := range *ref.Referrers() {
switch refref.(type) {
case *ir.Store:
writes[ref.Field] = append(writes[ref.Field], refref)
case *ir.Load:
reads[ref.Field] = append(reads[ref.Field], refref)
case *ir.DebugRef:
continue
default:
// this should be safe… if the field address
// escapes, then alloc.Heap will be true.
// there should be no instructions left that,
// given this FieldAddr, without escaping, can
// effect a load or store.
continue
}
}
case *ir.Store:
// we could treat this as a store to every field, but
// we don't want to decide the semantics of partial
// struct initializers. should `v = t{x: 1}` also mark
// v.y as being written to?
if ref != store {
continue fnLoop
}
case *ir.Load:
// a load of the entire struct loads every field
for i := 0; i < recv.Type().Underlying().(*types.Struct).NumFields(); i++ {
reads[i] = append(reads[i], ref)
}
case *ir.DebugRef:
continue
default:
continue fnLoop
}
}
offset := func(instr ir.Instruction) int {
for i, other := range instr.Block().Instrs {
if instr == other {
return i
}
}
panic("couldn't find instruction in its block")
}
for field, ws := range writes {
rs := reads[field]
wLoop:
for _, w := range ws {
for _, r := range rs {
if w.Block() == r.Block() {
if offset(r) > offset(w) {
// found a reachable read of our write
continue wLoop
}
} else if irutil.Reachable(w.Block(), r.Block()) {
// found a reachable read of our write
continue wLoop
}
}
fieldName := recv.Type().Underlying().(*types.Struct).Field(field).Name()
report.Report(pass, w, fmt.Sprintf("ineffective assignment to field %s.%s", recv.Type().(interface{ Obj() *types.TypeName }).Obj().Name(), fieldName))
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4005/sa4005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4005/testdata/go1.0/CheckIneffectiveFieldAssignments/CheckIneffectiveFieldAssignments.go
================================================
package pkg
type T1 struct {
T2 // make sure embedded fields don't throw off our numbering
x int
y int
z T2
}
type T2 struct {
x int
y int
z int
}
type T3 struct {
T2
}
func (v T1) fn1() {
v.x = 1
v.y = 1 //@ diag(`ineffective assignment to field T1.y`)
println(v.x)
}
func (v T1) fn2() {
println(v.x)
v.x = 1 //@ diag(`ineffective assignment to field T1.x`)
}
func (v T1) fn3() {
if true {
println(v.x)
}
v.x = 1 //@ diag(`ineffective assignment to field T1.x`)
}
func (v T1) fn10() {
v.x = 1
if true {
println(v.x)
}
}
func (v T1) fn4() {
v.x = 1
v.dump()
}
func (v T1) fn5() {
v.dump()
// This is currently broken because of our more aggressive lifting
v.x = 1
}
func (v T1) fn6() {
v.x = 1
v.y = 1
println(v.y)
println(v.x)
}
func (v T1) fn7() {
// not currently being flagged because it's a nested field
v.z.x = 1
}
func (v T1) fn8() {
v.x++ //@ diag(`ineffective assignment to field T1.x`)
}
func (v T1) fn9() {
v.x++
println(v.x)
}
func (v T1) fn11() {
v = T1{x: 42, y: 23} // not currently being flagged
}
func (v T1) fn12() {
v = T1{x: 42, y: 23} // not currently being flagged
println(v.y)
}
func (v T1) fn13() {
v = T1{x: 42}
v.y = 23 // not currently being flagged, we gave up when we saw the assignment to v
println(v.x)
}
func (v T1) fn14() {
v = T1{x: 42} // not currently being flagged
v.y = 23
println(v.y)
}
func (v T1) fn15() {
// not currently being flagged
v = T1{x: 42}
}
func (v T1) dump() {}
func (v T3) fn1() {
// not currently being flagged because it's a nested field (via
// embedding)
v.x = 1
}
================================================
FILE: staticcheck/sa4005/testdata/go1.0/CheckIneffectiveFieldAssignments/issue141.go
================================================
package pkg
import "fmt"
// T is t
type T struct {
X bool
F string
}
// Modify modifies T.F to say modified, then calls EchoF.
func (t T) Modify() {
if t.X {
t.X, t.F = true, "modified"
}
t.EchoF()
}
// EchoF prints F.
func (t T) EchoF() {
fmt.Println(t.F)
}
func main() {
t := T{X: true, F: "original"}
t.EchoF() // output: original
t.Modify() // output: modified
t.EchoF() // output: original
}
================================================
FILE: staticcheck/sa4006/sa4006.go
================================================
package sa4006
import (
"fmt"
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4006",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if irutil.IsExample(fn) {
continue
}
node := fn.Source()
if node == nil {
continue
}
if gen, ok := code.Generator(pass, node.Pos()); ok && gen == generated.Goyacc {
// Don't flag unused values in code generated by goyacc.
// There may be hundreds of those due to the way the state
// machine is constructed.
continue
}
switchTags := map[ir.Value]struct{}{}
ast.Inspect(node, func(node ast.Node) bool {
s, ok := node.(*ast.SwitchStmt)
if !ok {
return true
}
v, _ := fn.ValueForExpr(s.Tag)
switchTags[v] = struct{}{}
return true
})
// OPT(dh): don't use a map, possibly use a bitset
var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool
hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool {
if _, ok := seen[v]; ok {
return false
}
if _, ok := switchTags[v]; ok {
return true
}
refs := v.Referrers()
if refs == nil {
// TODO investigate why refs can be nil
return true
}
for _, ref := range *refs {
switch ref := ref.(type) {
case *ir.DebugRef:
case *ir.Sigma:
if seen == nil {
seen = map[ir.Value]struct{}{}
}
seen[v] = struct{}{}
if hasUse(ref, seen) {
return true
}
case *ir.Phi:
if seen == nil {
seen = map[ir.Value]struct{}{}
}
seen[v] = struct{}{}
if hasUse(ref, seen) {
return true
}
default:
return true
}
}
return false
}
ast.Inspect(node, func(node ast.Node) bool {
inc, ok := node.(*ast.IncDecStmt)
if ok {
val, _ := fn.ValueForExpr(inc.X)
if val == nil {
return true
}
if _, ok := val.(*ir.Const); ok {
// a zero-valued constant, for example in 'foo := []string(nil)'
return true
}
if !hasUse(val, nil) {
report.Report(pass, inc, fmt.Sprintf("this value of %s is never used", inc.X))
}
return true
}
assign, ok := node.(*ast.AssignStmt)
if !ok {
return true
}
if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 {
// Either a function call with multiple return values,
// or a comma-ok assignment
val, _ := fn.ValueForExpr(assign.Rhs[0])
if val == nil {
return true
}
refs := val.Referrers()
if refs == nil {
return true
}
for _, ref := range *refs {
ex, ok := ref.(*ir.Extract)
if !ok {
continue
}
if !hasUse(ex, nil) {
lhs := assign.Lhs[ex.Index]
if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
continue
}
report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
}
}
return true
}
for i, lhs := range assign.Lhs {
rhs := assign.Rhs[i]
if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" {
continue
}
val, _ := fn.ValueForExpr(rhs)
if val == nil {
if assign.Tok != token.ASSIGN { // +=, *=, etc.
val, _ = fn.ValueForExpr(lhs)
}
if val == nil {
continue
}
}
if _, ok := val.(*ir.Const); ok {
// a zero-valued constant, for example in 'foo := []string(nil)'
continue
}
if !hasUse(val, nil) {
report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs))
}
}
return true
})
}
return nil, nil
}
================================================
FILE: staticcheck/sa4006/sa4006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4006/testdata/go1.0/CheckUnreadVariableValues/CheckUnreadVariableValues.go
================================================
package pkg
import "fmt"
func fn1() {
var x int
x = gen() //@ diag(`this value of x is never used`)
x = gen()
println(x)
var y int
if true {
y = gen() //@ diag(`this value of y is never used`)
}
y = gen()
println(y)
}
func gen() int {
println() // make it unpure
return 0
}
func fn2() {
x, y := gen(), gen() //@ diag(`this value of x is never used`), diag(`this value of y is never used`)
x, y = gen(), gen()
println(x, y)
}
func fn3() {
x := uint32(0)
if true {
x = 1
} else {
x = 2
}
println(x)
}
func gen2() (int, int) {
println()
return 0, 0
}
func fn4() {
x, y := gen2() //@ diag(`this value of x is never used`)
println(y)
x, y = gen2() //@ diag(`this value of x is never used`), diag(`this value of y is never used`)
x, _ = gen2() //@ diag(`this value of x is never used`)
x, y = gen2()
println(x, y)
}
func fn5(m map[string]string) {
v, ok := m[""] //@ diag(`this value of v is never used`), diag(`this value of ok is never used`)
v, ok = m[""]
println(v, ok)
}
func fn6() {
x := gen()
// Do not report variables if they've been assigned to the blank identifier
_ = x
}
func fn7() {
func() {
var x int
x = gen() //@ diag(`this value of x is never used`)
x = gen()
println(x)
}()
}
func fn() int { println(); return 0 }
var y = func() {
v := fn() //@ diag(`never used`)
v = fn()
println(v)
}
func fn8() {
x := gen()
switch x {
}
y := gen() //@ diag(`this value of y is never used`)
y = gen()
switch y {
}
z, _ := gen2()
switch z {
}
_, a := gen2()
switch a {
}
b, c := gen2() //@ diag(`this value of b is never used`)
println(c)
b, c = gen2() //@ diag(`this value of c is never used`)
switch b {
}
}
func fn9() {
xs := []int{}
for _, x := range xs {
foo, err := work(x) //@ diag(`this value of foo is never used`)
if err != nil {
return
}
if !foo {
continue
}
}
}
func work(int) (bool, error) { return false, nil }
func resolveWeakTypes(types []int) {
for i := range types {
runEnd := findRunLimit(i)
if true {
_ = runEnd
}
i = runEnd //@ diag(`this value of i is never used`)
}
}
func findRunLimit(int) int { return 0 }
func fn10() {
slice := []string(nil)
if true {
slice = []string{"1", "2"}
} else {
slice = []string{"3", "4"}
}
fmt.Println(slice)
}
func issue1329() {
{
n := 1
n += 1 //@ diag(`this value of n is never used`)
}
{
n := 1
n ^= 1 //@ diag(`this value of n is never used`)
}
{
n := ""
n += "" //@ diag(`this value of n is never used`)
}
{
n := 1
n++ //@ diag(`this value of n is never used`)
}
{
n := 1
n += 1
fmt.Println(n)
}
{
n := 1
n ^= 1
fmt.Println(n)
}
{
n := ""
n += ""
fmt.Println(n)
}
{
n := 1
n++
fmt.Println(n)
}
}
================================================
FILE: staticcheck/sa4006/testdata/go1.0/CheckUnreadVariableValues/CheckUnreadVariableValues_test.go
================================================
package pkg
import "testing"
func TestFoo(t *testing.T) {
x := fn() //@ diag(`never used`)
x = fn()
println(x)
}
func ExampleFoo() {
x := fn()
x = fn()
println(x)
}
================================================
FILE: staticcheck/sa4008/sa4008.go
================================================
package sa4008
import (
"go/ast"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4008",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`,
Text: `For example:
for i := 0; i < 10; j++ { ... }
This may also occur when a loop can only execute once because of unconditional
control flow that terminates the loop. For example, when a loop body contains an
unconditional break, return, or panic:
func f() {
panic("oops")
}
func g() {
for i := 0; i < 10; i++ {
// f unconditionally calls panic, which means "i" is
// never incremented.
f()
}
}`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
cb := func(node ast.Node) bool {
loop, ok := node.(*ast.ForStmt)
if !ok {
return true
}
if loop.Init == nil || loop.Cond == nil || loop.Post == nil {
return true
}
init, ok := loop.Init.(*ast.AssignStmt)
if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 {
return true
}
cond, ok := loop.Cond.(*ast.BinaryExpr)
if !ok {
return true
}
x, ok := cond.X.(*ast.Ident)
if !ok {
return true
}
lhs, ok := init.Lhs[0].(*ast.Ident)
if !ok {
return true
}
if pass.TypesInfo.ObjectOf(x) != pass.TypesInfo.ObjectOf(lhs) {
return true
}
if _, ok := loop.Post.(*ast.IncDecStmt); !ok {
return true
}
v, isAddr := fn.ValueForExpr(cond.X)
if v == nil || isAddr {
return true
}
switch v := v.(type) {
case *ir.Phi:
ops := v.Operands(nil)
if len(ops) != 2 {
return true
}
_, ok := (*ops[0]).(*ir.Const)
if !ok {
return true
}
sigma, ok := (*ops[1]).(*ir.Sigma)
if !ok {
return true
}
if sigma.X != v {
return true
}
case *ir.Load:
return true
}
report.Report(pass, cond, "variable in loop condition never changes")
return true
}
if source := fn.Source(); source != nil {
ast.Inspect(source, cb)
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4008/sa4008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4008/testdata/go1.0/CheckLoopCondition/CheckLoopCondition.go
================================================
package pkg
func fn() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; i++ { //@ diag(`variable in loop condition never changes`)
}
}
counter := 0
for i := 0; i < 10; i++ {
for j := 0; j < 10; counter++ {
x := &j
*x++
}
}
}
================================================
FILE: staticcheck/sa4009/sa4009.go
================================================
package sa4009
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4009",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `A function argument is overwritten before its first use`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
cb := func(node ast.Node) bool {
var typ *ast.FuncType
var body *ast.BlockStmt
switch fn := node.(type) {
case *ast.FuncDecl:
typ = fn.Type
body = fn.Body
case *ast.FuncLit:
typ = fn.Type
body = fn.Body
}
if body == nil {
return true
}
if len(typ.Params.List) == 0 {
return true
}
for _, field := range typ.Params.List {
for _, arg := range field.Names {
obj := pass.TypesInfo.ObjectOf(arg)
var irobj *ir.Parameter
for _, param := range fn.Params {
if param.Object() == obj {
irobj = param
break
}
}
if irobj == nil {
continue
}
refs := irobj.Referrers()
if refs == nil {
continue
}
if len(irutil.FilterDebug(*refs)) != 0 {
continue
}
var assignment ast.Node
ast.Inspect(body, func(node ast.Node) bool {
if assignment != nil {
return false
}
assign, ok := node.(*ast.AssignStmt)
if !ok {
return true
}
for _, lhs := range assign.Lhs {
ident, ok := lhs.(*ast.Ident)
if !ok {
continue
}
if pass.TypesInfo.ObjectOf(ident) == obj {
assignment = assign
return false
}
}
return true
})
if assignment != nil {
report.Report(pass, arg, fmt.Sprintf("argument %s is overwritten before first use", arg),
report.Related(assignment, fmt.Sprintf("assignment to %s", arg)))
}
}
}
return true
}
if source := fn.Source(); source != nil {
ast.Inspect(source, cb)
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4009/sa4009_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4009
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4009/testdata/go1.0/CheckArgOverwritten/CheckArgOverwritten.go
================================================
package pkg
var x = func(arg int) { //@ diag(`overwritten`)
arg = 1
println(arg)
}
================================================
FILE: staticcheck/sa4010/sa4010.go
================================================
package sa4010
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4010",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `The result of \'append\' will never be observed anywhere`,
Text: `Calls to \'append\' produce a new slice value. When the result of
\'append\' is assigned to a variable that is never subsequently read, the
append operation may have an unintended effect.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
isAppend := func(ins ir.Value) bool {
call, ok := ins.(*ir.Call)
if !ok {
return false
}
if call.Call.IsInvoke() {
return false
}
if builtin, ok := call.Call.Value.(*ir.Builtin); !ok || builtin.Name() != "append" {
return false
}
return true
}
// We have to be careful about aliasing.
// Multiple slices may refer to the same backing array,
// making appends observable even when we don't see the result of append be used anywhere.
//
// We will have to restrict ourselves to slices that have been allocated within the function,
// haven't been sliced,
// and haven't been passed anywhere that could retain them (such as function calls or memory stores).
//
// We check whether an append should be flagged in two steps.
//
// In the first step, we look at the data flow graph, starting in reverse from the argument to append, till we reach the root.
// This graph must only consist of the following instructions:
//
// - phi
// - sigma
// - slice
// - const nil
// - MakeSlice
// - Alloc
// - calls to append
//
// If this step succeeds, we look at all referrers of the values found in the first step, recursively.
// These referrers must either be in the set of values found in the first step,
// be DebugRefs,
// or fulfill the same type requirements as step 1, with the exception of appends, which are forbidden.
//
// If both steps succeed then we know that the backing array hasn't been aliased in an observable manner.
//
// We could relax these restrictions by making use of additional information:
// - if we passed the slice to a function that doesn't retain the slice then we can still flag it
// - if a slice has been sliced but is dead afterwards, we can flag appends to the new slice
// OPT(dh): We could cache the results of both validate functions.
// However, we only use these functions on values that we otherwise want to flag, which are very few.
// Not caching values hasn't increased the runtimes for the standard library nor k8s.
var validateArgument func(v ir.Value, seen map[ir.Value]struct{}) bool
validateArgument = func(v ir.Value, seen map[ir.Value]struct{}) bool {
if _, ok := seen[v]; ok {
// break cycle
return true
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.Phi:
for _, edge := range v.Edges {
if !validateArgument(edge, seen) {
return false
}
}
return true
case *ir.Sigma:
return validateArgument(v.X, seen)
case *ir.Slice:
return validateArgument(v.X, seen)
case *ir.Const:
return true
case *ir.MakeSlice:
return true
case *ir.Alloc:
return true
case *ir.Call:
if isAppend(v) {
return validateArgument(v.Call.Args[0], seen)
}
return false
default:
return false
}
}
var validateReferrers func(v ir.Value, seen map[ir.Instruction]struct{}) bool
validateReferrers = func(v ir.Value, seen map[ir.Instruction]struct{}) bool {
for _, ref := range *v.Referrers() {
if _, ok := seen[ref]; ok {
continue
}
seen[ref] = struct{}{}
switch ref.(type) {
case *ir.Phi:
case *ir.Sigma:
case *ir.Slice:
case *ir.Const:
case *ir.MakeSlice:
case *ir.Alloc:
case *ir.DebugRef:
default:
return false
}
if ref, ok := ref.(ir.Value); ok {
if !validateReferrers(ref, seen) {
return false
}
}
}
return true
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
val, ok := ins.(ir.Value)
if !ok || !isAppend(val) {
continue
}
isUsed := false
visited := map[ir.Instruction]bool{}
var walkRefs func(refs []ir.Instruction)
walkRefs = func(refs []ir.Instruction) {
loop:
for _, ref := range refs {
if visited[ref] {
continue
}
visited[ref] = true
if _, ok := ref.(*ir.DebugRef); ok {
continue
}
switch ref := ref.(type) {
case *ir.Phi:
walkRefs(*ref.Referrers())
case *ir.Sigma:
walkRefs(*ref.Referrers())
case ir.Value:
if !isAppend(ref) {
isUsed = true
} else {
walkRefs(*ref.Referrers())
}
case ir.Instruction:
isUsed = true
break loop
}
}
}
refs := val.Referrers()
if refs == nil {
continue
}
walkRefs(*refs)
if isUsed {
continue
}
seen := map[ir.Value]struct{}{}
if !validateArgument(ins.(*ir.Call).Call.Args[0], seen) {
continue
}
seen2 := map[ir.Instruction]struct{}{}
for k := range seen {
// the only values we allow are also instructions, so this type assertion cannot fail
seen2[k.(ir.Instruction)] = struct{}{}
}
seen2[ins] = struct{}{}
failed := false
for v := range seen {
if !validateReferrers(v, seen2) {
failed = true
break
}
}
if !failed {
report.Report(pass, ins, "this result of append is never used, except maybe in other appends")
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4010/sa4010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4010/testdata/go1.0/CheckIneffectiveAppend/CheckIneffectiveAppend.go
================================================
package pkg
import "fmt"
func fn1() {
var s []int
s = append(s, 1) //@ diag(`this result of append is never used`)
s = append(s, 1) //@ diag(`this result of append is never used`)
}
func fn2() (named []int) {
named = append(named, 1)
return
}
func fn3() {
s := make([]int, 0)
s = append(s, 1) //@ diag(`this result of append is never used`)
}
func fn3_1(n int) {
s := make([]int, n)
s = append(s, 1) //@ diag(`this result of append is never used`)
}
func fn4() []int {
var s []int
s = append(s, 1)
return s
}
func fn5() {
var s []int
s = append(s, 1)
fn6(s)
}
func fn6([]int) {}
func fn7() {
var s []int
fn8(&s)
s = append(s, 1)
}
func fn8(*[]int) {}
func fn9() {
var s []int
s = append(s, 1)
fmt.Println(s)
s = append(s, 1)
}
func fn10() {
var s []int
return
s = append(s, 1)
}
func fn11() {
var s []int
for x := 0; x < 10; x++ {
s = append(s, 1) //@ diag(`this result of append is never used`)
}
}
func fn12(a []int) {
b := a[:0]
for _, x := range a {
if true {
b = append(b, x)
}
}
}
func fn13() []byte {
a := make([]byte, 10)
b := a[:5]
for i := 0; i < 2; i++ {
a = append(a, 1)
}
return b
}
func fn14() []byte {
a := make([]byte, 10)
b := a[:5]
for i := 0; i < 2; i++ {
b = append(b, 1)
}
return a
}
func fn15() {
s := make([]byte, 0, 1)
retain(s)
s = append(s, 1)
}
func fn16(s []byte) {
for i := 0; i < 2; i++ {
s = append(s, 1)
}
}
func fn17(x *[5]byte) {
s := x[:0]
for i := 0; i < 2; i++ {
s = append(s, 1)
}
}
func fn18() {
var x [4]byte
s := x[:0]
for i := 0; i < 2; i++ {
s = append(s, 1)
}
_ = x
}
func fn19() [4]int {
var x [4]int
s := x[:]
s = append(s, 1)
return x
}
func fn20() {
var x [4]int
s := x[:]
s = append(s, 1) //@ diag(`this result of append is never used`)
}
func fn21() {
var x []byte
x = append(x, 1)
retain(x)
x = append(x, 2)
}
func fn22() {
// we should probably flag this, but we currently don't
var x1 []byte
x2 := append(x1, 1)
x2 = append(x2, 2)
x3 := append(x1, 3)
x3 = append(x3, 4)
}
func fn23(n int) []int {
s := make([]int, 0, n)
s2 := append(s, 1, 2, 3) // this can be observed by extending the capacity of x
s2 = append(s2, 4)
x := append(s, 2)
return x
}
func fn24() []byte {
x := make([]byte, 0, 24)
s1 := append(x, 1)
s2 := append(s1, 2)
s2 = append(s2, 3)
s3 := append(s1, 4)
return s3
}
func fn25() {
var s []byte
if true {
s = append(s, 1)
}
s = append(s, 2) //@ diag(`this result of append is never used`)
}
func fn26() {
var s []byte
if true {
s = append(s, 1)
retain(s)
}
s = append(s, 2)
}
func fn27() {
var s []byte
if true {
s = make([]byte, 0, 1)
} else {
s = make([]byte, 0, 2)
}
s = append(s, 1) //@ diag(`this result of append is never used`)
}
func fn28() {
var s []byte
if true {
s = make([]byte, 0, 1)
} else {
s = make([]byte, 0, 2)
retain(s)
}
s = append(s, 1)
}
func fn29() {
x := gen()
x = append(x, 1)
}
func fn30(x T) {
s := x.s
s = append(s, 1)
}
var Global []int
func fn31() {
Global = append(Global, 1)
}
type T struct {
s []byte
}
func gen() []byte { return nil }
func retain([]byte) {}
================================================
FILE: staticcheck/sa4011/sa4011.go
================================================
package sa4011
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4011",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Break statement with no effect. Did you mean to break out of an outer loop?`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
var body *ast.BlockStmt
switch node := node.(type) {
case *ast.ForStmt:
body = node.Body
case *ast.RangeStmt:
body = node.Body
default:
lint.ExhaustiveTypeSwitch(node)
}
for _, stmt := range body.List {
var blocks [][]ast.Stmt
switch stmt := stmt.(type) {
case *ast.SwitchStmt:
for _, c := range stmt.Body.List {
blocks = append(blocks, c.(*ast.CaseClause).Body)
}
case *ast.SelectStmt:
for _, c := range stmt.Body.List {
blocks = append(blocks, c.(*ast.CommClause).Body)
}
default:
continue
}
for _, body := range blocks {
if len(body) == 0 {
continue
}
lasts := []ast.Stmt{body[len(body)-1]}
// TODO(dh): unfold all levels of nested block
// statements, not just a single level if statement
if ifs, ok := lasts[0].(*ast.IfStmt); ok {
if len(ifs.Body.List) == 0 {
continue
}
lasts[0] = ifs.Body.List[len(ifs.Body.List)-1]
if block, ok := ifs.Else.(*ast.BlockStmt); ok {
if len(block.List) != 0 {
lasts = append(lasts, block.List[len(block.List)-1])
}
}
}
for _, last := range lasts {
branch, ok := last.(*ast.BranchStmt)
if !ok || branch.Tok != token.BREAK || branch.Label != nil {
continue
}
report.Report(pass, branch, "ineffective break statement. Did you mean to break out of the outer loop?")
}
}
}
}
code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4011/sa4011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4011/testdata/go1.0/CheckScopedBreak/CheckScopedBreak.go
================================================
package pkg
func fn() {
var ch chan int
for {
switch {
case true:
break //@ diag(`ineffective break statement`)
default:
break //@ diag(`ineffective break statement`)
}
}
for {
select {
case <-ch:
break //@ diag(`ineffective break statement`)
}
}
for {
switch {
case true:
}
switch {
case true:
break //@ diag(`ineffective break statement`)
}
switch {
case true:
}
}
for {
switch {
case true:
if true {
break //@ diag(`ineffective break statement`)
} else {
break //@ diag(`ineffective break statement`)
}
}
}
for {
switch {
case true:
if true {
break
}
println("do work")
}
}
label:
for {
switch {
case true:
break label
}
}
for range ([]int)(nil) {
switch {
default:
break //@ diag(`ineffective break statement`)
}
}
}
================================================
FILE: staticcheck/sa4012/sa4012.go
================================================
package sa4012
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4012",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Comparing a value against NaN even though no value is equal to NaN`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
isNaN := func(v ir.Value) bool {
call, ok := v.(*ir.Call)
if !ok {
return false
}
return irutil.IsCallTo(call.Common(), "math.NaN")
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, block := range fn.Blocks {
for _, ins := range block.Instrs {
ins, ok := ins.(*ir.BinOp)
if !ok {
continue
}
if isNaN(irutil.Flatten(ins.X)) || isNaN(irutil.Flatten(ins.Y)) {
report.Report(pass, ins, "no value is equal to NaN, not even NaN itself")
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4012/sa4012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4012/testdata/go1.0/CheckNaNComparison/CheckNaNComparison.go
================================================
package pkg
import "math"
func fn(f float64) {
_ = f == math.NaN() //@ diag(`no value is equal to NaN`)
_ = f > math.NaN() //@ diag(`no value is equal to NaN`)
_ = f != math.NaN() //@ diag(`no value is equal to NaN`)
}
func fn2(f float64) {
x := math.NaN()
if true {
if f == x { //@ diag(`no value is equal to NaN`)
}
}
}
================================================
FILE: staticcheck/sa4013/sa4013.go
================================================
package sa4013
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4013",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Negating a boolean twice (\'!!b\') is the same as writing \'b\'. This is either redundant, or a typo.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkDoubleNegationQ = pattern.MustParse(`(UnaryExpr "!" single@(UnaryExpr "!" x))`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkDoubleNegationQ) {
report.Report(pass, node, "negating a boolean twice has no effect; is this a typo?", report.Fixes(
edit.Fix("Turn into single negation", edit.ReplaceWithNode(pass.Fset, node, m.State["single"].(ast.Node))),
edit.Fix("Remove double negation", edit.ReplaceWithNode(pass.Fset, node, m.State["x"].(ast.Node)))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa4013/sa4013_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4013
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4013/testdata/go1.0/CheckDoubleNegation/CheckDoubleNegation.go
================================================
package pkg
func fn(b1, b2 bool) {
if !!b1 { //@ diag(`negating a boolean twice`)
println()
}
if b1 && !!b2 { //@ diag(`negating a boolean twice`)
println()
}
if !(!b1) { //@ diag(`negating a boolean twice`)
println()
}
if !b1 {
println()
}
if !b1 && !b2 {
println()
}
}
================================================
FILE: staticcheck/sa4013/testdata/go1.0/CheckDoubleNegation/CheckDoubleNegation.go.golden
================================================
-- Turn into single negation --
package pkg
func fn(b1, b2 bool) {
if !b1 { //@ diag(`negating a boolean twice`)
println()
}
if b1 && !b2 { //@ diag(`negating a boolean twice`)
println()
}
if !b1 { //@ diag(`negating a boolean twice`)
println()
}
if !b1 {
println()
}
if !b1 && !b2 {
println()
}
}
-- Remove double negation --
package pkg
func fn(b1, b2 bool) {
if b1 { //@ diag(`negating a boolean twice`)
println()
}
if b1 && b2 { //@ diag(`negating a boolean twice`)
println()
}
if b1 { //@ diag(`negating a boolean twice`)
println()
}
if !b1 {
println()
}
if !b1 && !b2 {
println()
}
}
================================================
FILE: staticcheck/sa4014/sa4014.go
================================================
package sa4014
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4014",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
seen := map[ast.Node]bool{}
var collectConds func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool)
collectConds = func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) {
seen[ifstmt] = true
// Bail if any if-statement has an Init statement or side effects in its condition
if ifstmt.Init != nil {
return nil, false
}
if code.MayHaveSideEffects(pass, ifstmt.Cond, nil) {
return nil, false
}
conds = append(conds, ifstmt.Cond)
if elsestmt, ok := ifstmt.Else.(*ast.IfStmt); ok {
return collectConds(elsestmt, conds)
}
return conds, true
}
fn := func(node ast.Node) {
ifstmt := node.(*ast.IfStmt)
if seen[ifstmt] {
// this if-statement is part of an if/else-if chain that we've already processed
return
}
if ifstmt.Else == nil {
// there can be at most one condition
return
}
conds, ok := collectConds(ifstmt, nil)
if !ok {
return
}
if len(conds) < 2 {
return
}
counts := map[string]int{}
for _, cond := range conds {
s := report.Render(pass, cond)
counts[s]++
if counts[s] == 2 {
report.Report(pass, cond, "this condition occurs multiple times in this if/else if chain")
}
}
}
code.Preorder(pass, fn, (*ast.IfStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4014/sa4014_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4014
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4014/testdata/go1.0/CheckRepeatedIfElse/CheckRepeatedIfElse.go
================================================
package pkg
func fn1(b1, b2 bool) {
if b1 && !b2 {
} else if b1 {
} else if b1 && !b2 { //@ diag(`condition occurs multiple times`)
} else if b1 { //@ diag(`condition occurs multiple times`)
} else {
println()
}
}
func fn2(b1, b2 bool, ch chan string) {
if b1 && !b2 {
} else if b1 {
} else if <-ch == "" {
} else if <-ch == "" {
} else {
println()
}
}
func fn3() {
if gen() {
println()
} else if gen() {
println()
}
}
func fn4() {
if s := gen2(); s == "" {
} else if s := gen2(); s == "" {
println()
}
}
func fn5() {
var s string
if s = gen2(); s == "" {
} else if s != "foo" {
} else if s = gen2(); s == "" {
} else if s != "foo" {
println()
}
}
func fn6() {
if true {
} else {
}
}
func gen() bool { return false }
func gen2() string { return "" }
================================================
FILE: staticcheck/sa4015/sa4015.go
================================================
package sa4015
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4015",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(checkMathIntRules),
},
Doc: &lint.RawDocumentation{
Title: `Calling functions like \'math.Ceil\' on floats converted from integers doesn't do anything useful`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkMathIntRules = map[string]callcheck.Check{
"math.Ceil": pointlessIntMath,
"math.Floor": pointlessIntMath,
"math.IsNaN": pointlessIntMath,
"math.Trunc": pointlessIntMath,
"math.IsInf": pointlessIntMath,
}
func pointlessIntMath(call *callcheck.Call) {
if ConvertedFromInt(call.Args[0].Value) {
call.Invalid(fmt.Sprintf("calling %s on a converted integer is pointless", irutil.CallName(call.Instr.Common())))
}
}
func ConvertedFromInt(v callcheck.Value) bool {
conv, ok := v.Value.(*ir.Convert)
if !ok {
return false
}
return typeutil.NewTypeSet(conv.X.Type()).All(func(t *types.Term) bool {
b, ok := t.Type().Underlying().(*types.Basic)
return ok && b.Info()&types.IsInteger != 0
})
}
================================================
FILE: staticcheck/sa4015/sa4015_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4015
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4015/testdata/go1.0/CheckMathInt/CheckMathInt.go
================================================
package pkg
import "math"
func fn(x int) {
math.Ceil(float64(x)) //@ diag(`on a converted integer is pointless`)
math.Floor(float64(x * 2)) //@ diag(`on a converted integer is pointless`)
}
================================================
FILE: staticcheck/sa4015/testdata/go1.18/CheckMathInt/CheckMathInt.go
================================================
package pkg
import "math"
func fn3[S int8 | int16](x S) {
math.Ceil(float64(x)) //@ diag(`on a converted integer is pointless`)
}
func fn4[S int8 | int16 | float32](x S) {
math.Ceil(float64(x))
}
================================================
FILE: staticcheck/sa4016/sa4016.go
================================================
package sa4016
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4016",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Certain bitwise operations, such as \'x ^ 0\', do not do anything useful`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
binop := node.(*ast.BinaryExpr)
if !typeutil.All(pass.TypesInfo.TypeOf(binop), func(term *types.Term) bool {
b, ok := term.Type().Underlying().(*types.Basic)
if !ok {
return false
}
return (b.Info() & types.IsInteger) != 0
}) {
return
}
switch binop.Op {
case token.AND, token.OR, token.XOR:
default:
// we do not flag shifts because too often, x<<0 is part
// of a pattern, x<<0, x<<8, x<<16, ...
return
}
if y, ok := binop.Y.(*ast.Ident); ok {
obj, ok := pass.TypesInfo.ObjectOf(y).(*types.Const)
if !ok {
return
}
if obj.Pkg() != pass.Pkg {
// identifier was dot-imported
return
}
if v, _ := constant.Int64Val(obj.Val()); v != 0 {
return
}
path, _ := astutil.PathEnclosingInterval(code.File(pass, obj), obj.Pos(), obj.Pos())
if len(path) < 2 {
return
}
spec, ok := path[1].(*ast.ValueSpec)
if !ok {
return
}
if len(spec.Names) != 1 || len(spec.Values) != 1 {
// TODO(dh): we could support this
return
}
ident, ok := spec.Values[0].(*ast.Ident)
if !ok {
return
}
if !isIota(pass.TypesInfo.ObjectOf(ident)) {
return
}
switch binop.Op {
case token.AND:
report.Report(pass, node,
fmt.Sprintf("%s always equals 0; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
case token.OR, token.XOR:
report.Report(pass, node,
fmt.Sprintf("%s always equals %s; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.X), report.Render(pass, binop.Y), report.Render(pass, binop.Y)))
}
} else if code.IsIntegerLiteral(pass, binop.Y, constant.MakeInt64(0)) {
switch binop.Op {
case token.AND:
report.Report(pass, node, fmt.Sprintf("%s always equals 0", report.Render(pass, binop)))
case token.OR, token.XOR:
report.Report(pass, node, fmt.Sprintf("%s always equals %s", report.Render(pass, binop), report.Render(pass, binop.X)))
}
}
}
code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
return nil, nil
}
func isIota(obj types.Object) bool {
if obj.Name() != "iota" {
return false
}
c, ok := obj.(*types.Const)
if !ok {
return false
}
return c.Pkg() == nil
}
================================================
FILE: staticcheck/sa4016/sa4016_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4016
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4016/testdata/go1.0/CheckSillyBitwiseOps/CheckSillyBitwiseOps.go
================================================
package pkg
const a = 0
const (
b = iota
c
)
const (
y = 42
d = iota
)
func fn(x int) {
println(x | 0) //@ diag(`x | 0 always equals x`)
println(x & 0) //@ diag(`x & 0 always equals 0`)
println(x ^ 0) //@ diag(`x ^ 0 always equals x`)
println((x << 5) | 0) //@ diag(`(x << 5) | 0 always equals (x << 5)`)
println(x | 1)
println(x << 0)
println(x | a)
println(x | b) //@ diag(`x | b always equals x; b is defined as iota`)
println(x & b) //@ diag(`x & b always equals 0; b is defined as iota`)
println(x | c)
// d is iota, but its value is 1
println(x | d)
}
================================================
FILE: staticcheck/sa4016/testdata/go1.0/CheckSillyBitwiseOps_dotImport/foo.go
================================================
package foo
const X = 0
================================================
FILE: staticcheck/sa4016/testdata/go1.0/CheckSillyBitwiseOps_dotImport/foo_test.go
================================================
package foo_test
import . "example.com/CheckSillyBitwiseOps_dotImport"
var _ = 1 | X
================================================
FILE: staticcheck/sa4016/testdata/go1.0/CheckSillyBitwiseOps_shadowedIota/shadowed.go
================================================
package pkg
const iota = 0
const (
a = iota
)
func fn(x int) {
_ = x | a
}
================================================
FILE: staticcheck/sa4016/testdata/go1.18/CheckSillyBitwiseOps/generics.go
================================================
package pkg
func tpfn[T int](x T) {
_ = x & 0 //@ diag(`always equals 0`)
}
================================================
FILE: staticcheck/sa4017/sa4017.go
================================================
package sa4017
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/purity"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4017",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, purity.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Discarding the return values of a function without side effects, making the call pointless`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
pure := pass.ResultOf[purity.Analyzer].(purity.Result)
fnLoop:
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if code.IsInTest(pass, fn) {
params := fn.Signature.Params()
for param := range params.Variables() {
if typeutil.IsPointerToTypeWithName(param.Type(), "testing.B") {
// Ignore discarded pure functions in code related
// to benchmarks. Instead of matching BenchmarkFoo
// functions, we match any function accepting a
// *testing.B. Benchmarks sometimes call generic
// functions for doing the actual work, and
// checking for the parameter is a lot easier and
// faster than analyzing call trees.
continue fnLoop
}
}
}
for _, b := range fn.Blocks {
for _, ins := range b.Instrs {
ins, ok := ins.(*ir.Call)
if !ok {
continue
}
refs := ins.Referrers()
if refs == nil || len(irutil.FilterDebug(*refs)) > 0 {
continue
}
callee := ins.Common().StaticCallee()
if callee == nil {
continue
}
if callee.Object() == nil {
// TODO(dh): support anonymous functions
continue
}
if _, ok := pure[callee.Object().(*types.Func)]; ok {
if pass.Pkg.Path() == "fmt_test" && callee.Object().(*types.Func).FullName() == "fmt.Sprintf" {
// special case for benchmarks in the fmt package
continue
}
report.Report(pass, ins, fmt.Sprintf("%s doesn't have side effects and its return value is ignored", callee.Object().Name()))
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4017/sa4017_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4017
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4017/testdata/go1.0/CheckSideEffectFreeCalls/CheckSideEffectFreeCalls.go
================================================
package pkg
import (
"context"
"fmt"
"net/http"
"strings"
)
func fn1() {
strings.Replace("", "", "", 1) //@ diag(`doesn't have side effects`)
foo(1, 2) //@ diag(`doesn't have side effects`)
baz(1, 2) //@ diag(`doesn't have side effects`)
_, x := baz(1, 2)
_ = x
bar(1, 2)
}
func fn2() {
r, _ := http.NewRequest("GET", "/", nil)
r.WithContext(context.Background()) //@ diag(`doesn't have side effects`)
}
func foo(a, b int) int { return a + b }
func baz(a, b int) (int, int) { return a + b, a + b }
func bar(a, b int) int {
println(a + b)
return a + b
}
func empty() {}
func stubPointer() *int { return nil }
func stubInt() int { return 0 }
func fn3() {
empty()
stubPointer()
stubInt()
}
func fn4() error {
// Test for https://github.com/dominikh/go-tools/issues/949
if true {
return fmt.Errorf("")
}
for {
}
}
================================================
FILE: staticcheck/sa4017/testdata/go1.0/CheckSideEffectFreeCalls/CheckSideEffectFreeCalls_test.go
================================================
package pkg
import (
"strings"
"testing"
)
func TestFoo(t *testing.T) {
strings.Replace("", "", "", 1) //@ diag(`doesn't have side effects`)
}
func BenchmarkFoo(b *testing.B) {
strings.Replace("", "", "", 1)
}
func doBenchmark(s string, b *testing.B) {
strings.Replace("", "", "", 1)
}
func BenchmarkBar(b *testing.B) {
doBenchmark("", b)
}
================================================
FILE: staticcheck/sa4018/sa4018.go
================================================
package sa4018
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/facts/purity"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4018",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, purity.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Self-assignment of variables`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
pure := pass.ResultOf[purity.Analyzer].(purity.Result)
fn := func(node ast.Node) {
assign := node.(*ast.AssignStmt)
if assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
return
}
for i, lhs := range assign.Lhs {
rhs := assign.Rhs[i]
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
continue
}
if code.MayHaveSideEffects(pass, lhs, pure) || code.MayHaveSideEffects(pass, rhs, pure) {
continue
}
rlh := report.Render(pass, lhs)
rrh := report.Render(pass, rhs)
if rlh == rrh {
report.Report(pass, assign, fmt.Sprintf("self-assignment of %s to %s", rrh, rlh), report.FilterGenerated())
}
}
}
code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4018/sa4018_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4018
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4018/testdata/go1.0/CheckSelfAssignment/CheckSelfAssignment.go
================================================
package pkg
func fn(x int) {
var z int
var y int
x = x //@ diag(`self-assignment`)
y = y //@ diag(`self-assignment`)
y, x, z = y, x, 1 //@ diag(`self-assignment of y to y`), diag(`self-assignment of x to x`)
y = x
_ = y
_ = x
_ = z
func() {
x := x
println(x)
}()
}
func fn1() {
var (
x []byte
ch chan int
)
x[42] = x[42] //@ diag(`self-assignment`)
x[pure(42)] = x[pure(42)] //@ diag(`self-assignment`)
x[pure(pure(42))] = x[pure(pure(42))] //@ diag(`self-assignment`)
x[impure(42)] = x[impure(42)]
x[impure(pure(42))] = x[impure(pure(42))]
x[pure(impure(42))] = x[pure(impure(42))]
x[pure(<-ch)] = x[pure(<-ch)]
x[pure(pure(<-ch))] = x[pure(pure(<-ch))]
x[<-ch] = x[<-ch]
type T struct {
x []int
}
var ts []T
ts[impure(42)].x = ts[impure(42)].x
m := map[*int]int{}
m[ptr1()] = m[ptr1()]
m[ptr2()] = m[ptr2()]
m[new(int)] = m[new(int)]
m2 := map[int]int{}
m2[len(x)] = m2[len(x)] //@ diag(`self-assignment`)
gen1()[0] = gen1()[0]
gen2(0)[0] = gen2(0)[0] //@ diag(`self-assignment`)
gen3(0)[0] = gen3(0)[0]
}
func ptr1() *int {
return new(int)
}
func ptr2() *int {
x := 0
return &x
}
func gen1() []int {
return nil
}
func gen2(x int) []int {
return nil
}
func gen3(x int) []int {
return make([]int, 0)
}
func pure(n int) int {
return n
}
func impure(n int) int {
println(n)
return n
}
================================================
FILE: staticcheck/sa4019/sa4019.go
================================================
package sa4019
import (
"fmt"
"go/ast"
"sort"
"strings"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4019",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Multiple, identical build constraints in the same file`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func buildTagsIdentical(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
s1s := make([]string, len(s1))
copy(s1s, s1)
sort.Strings(s1s)
s2s := make([]string, len(s2))
copy(s2s, s2)
sort.Strings(s2s)
for i, s := range s1s {
if s != s2s[i] {
return false
}
}
return true
}
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
constraints := buildTags(f)
for i, constraint1 := range constraints {
for j, constraint2 := range constraints {
if i >= j {
continue
}
if buildTagsIdentical(constraint1, constraint2) {
msg := fmt.Sprintf("identical build constraints %q and %q",
strings.Join(constraint1, " "),
strings.Join(constraint2, " "))
report.Report(pass, f, msg, report.FilterGenerated(), report.ShortRange())
}
}
}
}
return nil, nil
}
func buildTags(f *ast.File) [][]string {
var out [][]string
for line := range strings.SplitSeq(astutil.Preamble(f), "\n") {
if !strings.HasPrefix(line, "+build ") {
continue
}
line = strings.TrimSpace(strings.TrimPrefix(line, "+build "))
fields := strings.Fields(line)
out = append(out, fields)
}
return out
}
================================================
FILE: staticcheck/sa4019/sa4019_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4019
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4019/testdata/go1.1/CheckDuplicateBuildConstraints/CheckDuplicateBuildConstraints.go
================================================
//go:build (one || two || three || go1.1) && (three || one || two || go1.1)
// +build one two three go1.1
// +build three one two go1.1
package pkg //@ diag(`identical build constraints`)
================================================
FILE: staticcheck/sa4020/sa4020.go
================================================
package sa4020
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/exp/typeparams"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4020",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Unreachable case clause in a type switch`,
Text: `In a type switch like the following
type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
var v any = T{}
switch v.(type) {
case io.Reader:
// ...
case T:
// unreachable
}
the second case clause can never be reached because \'T\' implements
\'io.Reader\' and case clauses are evaluated in source order.
Another example:
type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
func (T) Close() error { return nil }
var v any = T{}
switch v.(type) {
case io.Reader:
// ...
case io.ReadCloser:
// unreachable
}
Even though \'T\' has a \'Close\' method and thus implements \'io.ReadCloser\',
\'io.Reader\' will always match first. The method set of \'io.Reader\' is a
subset of \'io.ReadCloser\'. Thus it is impossible to match the second
case without matching the first case.
Structurally equivalent interfaces
A special case of the previous example are structurally identical
interfaces. Given these declarations
type T error
type V error
func doSomething() error {
err, ok := doAnotherThing()
if ok {
return T(err)
}
return U(err)
}
the following type switch will have an unreachable case clause:
switch doSomething().(type) {
case T:
// ...
case V:
// unreachable
}
\'T\' will always match before V because they are structurally equivalent
and therefore \'doSomething()\''s return value implements both.`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// Check if T subsumes V in a type switch. T subsumes V if T is an interface and T's method set is a subset of V's method set.
subsumes := func(T, V types.Type) bool {
if typeparams.IsTypeParam(T) {
return false
}
tIface, ok := T.Underlying().(*types.Interface)
if !ok {
return false
}
return types.Implements(V, tIface)
}
subsumesAny := func(Ts, Vs []types.Type) (types.Type, types.Type, bool) {
for _, T := range Ts {
for _, V := range Vs {
if subsumes(T, V) {
return T, V, true
}
}
}
return nil, nil, false
}
fn := func(node ast.Node) {
tsStmt := node.(*ast.TypeSwitchStmt)
type ccAndTypes struct {
cc *ast.CaseClause
types []types.Type
}
// All asserted types in the order of case clauses.
ccs := make([]ccAndTypes, 0, len(tsStmt.Body.List))
for _, stmt := range tsStmt.Body.List {
cc, _ := stmt.(*ast.CaseClause)
// Exclude the 'default' case.
if len(cc.List) == 0 {
continue
}
Ts := make([]types.Type, 0, len(cc.List))
for _, expr := range cc.List {
// Exclude the 'nil' value from any 'case' statement (it is always reachable).
if typ := pass.TypesInfo.TypeOf(expr); typ != types.Typ[types.UntypedNil] {
Ts = append(Ts, typ)
}
}
ccs = append(ccs, ccAndTypes{cc: cc, types: Ts})
}
if len(ccs) <= 1 {
// Zero or one case clauses, nothing to check.
return
}
// Check if case clauses following cc have types that are subsumed by cc.
for i, cc := range ccs[:len(ccs)-1] {
for _, next := range ccs[i+1:] {
if T, V, yes := subsumesAny(cc.types, next.types); yes {
report.Report(pass, next.cc, fmt.Sprintf("unreachable case clause: %s will always match before %s", T.String(), V.String()),
report.ShortRange())
}
}
}
}
code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4020/sa4020_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4020
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4020/testdata/go1.0/CheckUnreachableTypeCases/CheckUnreachableTypeCases.go
================================================
package pkg
import "io"
type T struct{}
func (T) Read(b []byte) (int, error) { return 0, nil }
func (T) something() string { return "non-exported method" }
type V error
type U error
func fn1() {
var (
v interface{}
err error
)
switch v.(type) {
case io.Reader:
println("io.Reader")
case io.ReadCloser: //@ diag(`unreachable case clause: io.Reader will always match before io.ReadCloser`)
println("io.ReadCloser")
}
switch v.(type) {
case io.Reader:
println("io.Reader")
case T: //@ diag(`unreachable case clause: io.Reader will always match before example.com/CheckUnreachableTypeCases.T`)
println("T")
}
switch v.(type) {
case io.Reader:
println("io.Reader")
case io.ReadCloser: //@ diag(`unreachable case clause: io.Reader will always match before io.ReadCloser`)
println("io.ReadCloser")
case T: //@ diag(`unreachable case clause: io.Reader will always match before example.com/CheckUnreachableTypeCases.T`)
println("T")
}
switch v.(type) {
case io.Reader:
println("io.Reader")
case io.ReadCloser, T: //@ diag(`unreachable case clause: io.Reader will always match before io.ReadCloser`)
println("io.ReadCloser or T")
}
switch v.(type) {
case io.ReadCloser, io.Reader:
println("io.ReadCloser or io.Reader")
case T: //@ diag(`unreachable case clause: io.Reader will always match before example.com/CheckUnreachableTypeCases.T`)
println("T")
}
switch v.(type) {
default:
println("something else")
case io.Reader:
println("io.Reader")
case T: //@ diag(`unreachable case clause: io.Reader will always match before example.com/CheckUnreachableTypeCases.T`)
println("T")
}
switch v.(type) {
case interface{}:
println("interface{}")
case nil, T: //@ diag(`unreachable case clause: interface{} will always match before example.com/CheckUnreachableTypeCases.T`)
println("nil or T")
}
switch err.(type) {
case V:
println("V")
case U: //@ diag(`unreachable case clause: example.com/CheckUnreachableTypeCases.V will always match before example.com/CheckUnreachableTypeCases.U`)
println("U")
}
switch err.(type) {
case U:
println("U")
case V: //@ diag(`unreachable case clause: example.com/CheckUnreachableTypeCases.U will always match before example.com/CheckUnreachableTypeCases.V`)
println("V")
}
}
func fn3() {
var (
v interface{}
err error
)
switch v.(type) {
case T:
println("T")
case io.Reader:
println("io.Reader")
}
switch v.(type) {
case io.ReadCloser:
println("io.ReadCloser")
case T:
println("T")
}
switch v.(type) {
case io.ReadCloser:
println("io.ReadCloser")
case io.Reader:
println("io.Reader")
}
switch v.(type) {
case T:
println("T")
}
switch err.(type) {
case V, U:
println("V or U")
case io.Reader:
println("io.Reader")
}
switch v.(type) {
default:
println("something")
}
switch v.(type) {
case interface{}:
println("interface{}")
case nil:
println("nil")
}
}
================================================
FILE: staticcheck/sa4020/testdata/go1.18/CheckUnreachableTypeCases/typeparams.go
================================================
package pkg
func tp1[T any](x interface{}) {
switch x.(type) {
case T:
case int:
}
}
================================================
FILE: staticcheck/sa4021/sa4021.go
================================================
package sa4021
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4021",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `\"x = append(y)\" is equivalent to \"x = y\"`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var checkSingleArgAppendQ = pattern.MustParse(`(CallExpr (Builtin "append") [_])`)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, checkSingleArgAppendQ) {
report.Report(pass, node, "x = append(y) is equivalent to x = y", report.FilterGenerated())
}
return nil, nil
}
================================================
FILE: staticcheck/sa4021/sa4021_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4021
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4021/testdata/go1.0/CheckSingleArgAppend/CheckSingleArgAppend.go
================================================
package pkg
func fn(arg []int) {
x := append(arg) //@ diag(`x = append(y) is equivalent to x = y`)
_ = x
y := append(arg, 1)
_ = y
arg = append(arg) //@ diag(`x = append(y) is equivalent to x = y`)
arg = append(arg, 1, 2, 3)
var nilly []int
arg = append(arg, nilly...)
arg = append(arg, arg...)
append := func([]int) []int { return nil }
arg = append(arg)
}
================================================
FILE: staticcheck/sa4022/sa4022.go
================================================
package sa4022
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4022",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Comparing the address of a variable against nil`,
Text: `Code such as \"if &x == nil\" is meaningless, because taking the address of a variable always yields a non-nil pointer.`,
Since: "2020.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var CheckAddressIsNilQ = pattern.MustParse(
`(BinaryExpr
(UnaryExpr "&" _)
(Or "==" "!=")
(Builtin "nil"))`)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, CheckAddressIsNilQ) {
report.Report(pass, node, "the address of a variable cannot be nil")
}
return nil, nil
}
================================================
FILE: staticcheck/sa4022/sa4022_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4022
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4022/testdata/go1.0/CheckAddressIsNil/CheckAddressIsNil.go
================================================
package pkg
func fn(x int, y *int) {
_ = &x == nil //@ diag(`the address of a variable cannot be nil`)
_ = &y != nil //@ diag(`the address of a variable cannot be nil`)
if &x != nil { //@ diag(`the address of a variable cannot be nil`)
println("obviously.")
}
if y == nil {
}
}
================================================
FILE: staticcheck/sa4023/sa4023.go
================================================
package sa4023
import (
"fmt"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/nilness"
"honnef.co/go/tools/analysis/facts/typedness"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/exp/typeparams"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4023",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, typedness.Analysis, nilness.Analysis},
},
Doc: &lint.RawDocumentation{
Title: `Impossible comparison of interface value with untyped nil`,
Text: `Under the covers, interfaces are implemented as two elements, a
type T and a value V. V is a concrete value such as an int,
struct or pointer, never an interface itself, and has type T. For
instance, if we store the int value 3 in an interface, the
resulting interface value has, schematically, (T=int, V=3). The
value V is also known as the interface's dynamic value, since a
given interface variable might hold different values V (and
corresponding types T) during the execution of the program.
An interface value is nil only if the V and T are both
unset, (T=nil, V is not set), In particular, a nil interface will
always hold a nil type. If we store a nil pointer of type *int
inside an interface value, the inner type will be *int regardless
of the value of the pointer: (T=*int, V=nil). Such an interface
value will therefore be non-nil even when the pointer value V
inside is nil.
This situation can be confusing, and arises when a nil value is
stored inside an interface value such as an error return:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
If all goes well, the function returns a nil p, so the return
value is an error interface value holding (T=*MyError, V=nil).
This means that if the caller compares the returned error to nil,
it will always look as if there was an error even if nothing bad
happened. To return a proper nil error to the caller, the
function must return an explicit nil:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
It's a good idea for functions that return errors always to use
the error type in their signature (as we did above) rather than a
concrete type such as \'*MyError\', to help guarantee the error is
created correctly. As an example, \'os.Open\' returns an error even
though, if not nil, it's always of concrete type *os.PathError.
Similar situations to those described here can arise whenever
interfaces are used. Just keep in mind that if any concrete value
has been stored in the interface, the interface will not be nil.
For more information, see The Laws of
Reflection at https://golang.org/doc/articles/laws_of_reflection.html.
This text has been copied from
https://golang.org/doc/faq#nil_error, licensed under the Creative
Commons Attribution 3.0 License.`,
Since: "2020.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny, // TODO should this be MergeIfAll?
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// The comparison 'fn() == nil' can never be true if fn() returns
// an interface value and only returns typed nils. This is usually
// a mistake in the function itself, but all we can say for
// certain is that the comparison is pointless.
//
// Flag results if no untyped nils are being returned, but either
// known typed nils, or typed unknown nilness are being returned.
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR)
typedness := pass.ResultOf[typedness.Analysis].(*typedness.Result)
nilness := pass.ResultOf[nilness.Analysis].(*nilness.Result)
for _, fn := range irpkg.SrcFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
binop, ok := instr.(*ir.BinOp)
if !ok || !(binop.Op == token.EQL || binop.Op == token.NEQ) {
continue
}
if _, ok := binop.X.Type().Underlying().(*types.Interface); !ok || typeparams.IsTypeParam(binop.X.Type()) {
// TODO support swapped X and Y
continue
}
k, ok := binop.Y.(*ir.Const)
if !ok || !k.IsNil() {
// if binop.X is an interface, then binop.Y can
// only be a Const if its untyped. A typed nil
// constant would first be passed to
// MakeInterface.
continue
}
var idx int
var obj *types.Func
switch x := irutil.Flatten(binop.X).(type) {
case *ir.Call:
callee := x.Call.StaticCallee()
if callee == nil {
continue
}
obj, _ = callee.Object().(*types.Func)
idx = 0
case *ir.Extract:
call, ok := irutil.Flatten(x.Tuple).(*ir.Call)
if !ok {
continue
}
callee := call.Call.StaticCallee()
if callee == nil {
continue
}
obj, _ = callee.Object().(*types.Func)
idx = x.Index
case *ir.MakeInterface:
var qualifier string
switch binop.Op {
case token.EQL:
qualifier = "never"
case token.NEQ:
qualifier = "always"
default:
panic("unreachable")
}
terms, err := typeparams.NormalTerms(x.X.Type())
if len(terms) == 0 || err != nil {
// Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type
// _can_ be nil when put in an interface value.
continue
}
if report.HasRange(x.X) {
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier),
report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type"))
} else {
// we can't generate related information for this, so make the diagnostic itself slightly more useful
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", qualifier))
}
continue
}
if obj == nil {
continue
}
isNil, onlyGlobal := nilness.MayReturnNil(obj, idx)
if typedness.MustReturnTyped(obj, idx) && isNil && !onlyGlobal && !code.IsInTest(pass, binop) {
// Don't flag these comparisons in tests. Tests
// may be explicitly enforcing the invariant that
// a value isn't nil.
var qualifier string
switch binop.Op {
case token.EQL:
qualifier = "never"
case token.NEQ:
qualifier = "always"
default:
panic("unreachable")
}
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier),
// TODO support swapped X and Y
report.Related(binop.X, fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", report.Ordinal(idx+1))),
report.Related(obj, fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj))))
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4023/sa4023_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4023
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/CheckTypedNilInterface/CheckTypedNilInterface.go
================================================
package pkg
import (
"errors"
"os/exec"
)
type T struct{ x *int }
func fn1() *int { return nil }
func fn2() (int, *int, int) { return 0, nil, 0 }
func fn3() (int, error) { return 0, nil }
func fn4() error { return nil }
func gen1() interface{} {
// don't flag, returning a concrete value
return 0
}
func gen2() interface{} {
// don't flag, returning a concrete value
return &T{}
}
func gen3() interface{} {
// flag, always returns a typed value
m := map[int]*int{}
return m[0]
}
func gen4() (int, interface{}, *int) {
// flag ret[1], always a typed value
m := map[int]*int{}
return 0, m[0], nil
}
func gen5() interface{} {
// flag, propagate gen3
return gen3()
}
func gen6(b bool) interface{} {
// don't flag, sometimes returns untyped nil
if b {
m := map[int]*int{}
return m[0]
} else {
return nil
}
}
func gen7() interface{} {
// flag, always returns a typed value
return fn1()
}
func gen8(x *int) interface{} {
// flag
if x == nil {
return x
}
return x
}
func gen9() interface{} {
// flag
var x *int
return x
}
func gen10() interface{} {
// flag
var x *int
if x == nil {
return x
}
return errors.New("")
// This is a tricky one. we should flag this, because it never
// returns a nil error, but if errors.New could return untyped
// nils, then we shouldn't flag it. we need to consider the
// implementation of the called function.
}
func gen11() interface{} {
// don't flag, we sometimes return untyped nil
if true {
return nil
} else {
return (*int)(nil)
}
}
func gen12(b bool) interface{} {
// flag, all branches return typed nils
var x interface{}
if b {
x = (*int)(nil)
} else {
x = (*string)(nil)
}
return x
}
func gen13() interface{} {
// flag, always returns a typed value
_, x, _ := fn2()
return x
}
func gen14(ch chan *int) interface{} {
// flag
return <-ch
}
func gen15() interface{} {
// flag
t := &T{}
return t.x
}
var g *int = new(int)
func gen16() interface{} {
// don't flag. returning a global is akin to returning &T{}.
return g
}
func gen17(x interface{}) interface{} {
// don't flag
if x != nil {
return x
}
return x
}
func gen18() (int, error) {
// don't flag
_, err := fn3()
if err != nil {
return 0, errors.New("yo")
}
return 0, err
}
func gen19() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen20() (out interface{}) {
// don't flag
if true {
return (*int)(nil)
}
return
}
func gen21() error {
if false {
return (*exec.Error)(nil)
}
return fn4()
}
func gen22() interface{} {
if true {
return g
}
return (*int)(nil)
}
func test() {
_ = gen1() == nil
_ = gen2() == nil
_ = gen3() == nil //@ diag(`never true`)
{
_, r2, r3 := gen4()
_ = r2 == nil //@ diag(`never true`)
_ = r3 == nil
}
_ = gen5() == nil //@ diag(`never true`)
_ = gen6(false) == nil
_ = gen7() == nil //@ diag(`never true`)
_ = gen8(nil) == nil //@ diag(`never true`)
_ = gen9() == nil //@ diag(`never true`)
_ = gen10() == nil //@ diag(`never true`)
_ = gen11() == nil
_ = gen12(true) == nil //@ diag(`never true`)
_ = gen13() == nil //@ diag(`never true`)
_ = gen14(nil) == nil //@ diag(`never true`)
_ = gen15() == nil //@ diag(`never true`)
_ = gen16() == nil
_ = gen17(nil) == nil
{
_, r2 := gen18()
_ = r2 == nil
}
_ = gen19() == nil
_ = gen20() == nil
_ = gen21() == nil
_ = gen22() == nil //@ diag(`never true`)
var v1 interface{} = 0
_ = v1 == nil //@ diag(`never true`)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/CheckTypedNilInterface/real.go
================================================
package pkg
import "log"
type iface interface{ m() }
type t1 struct{ int }
func (t *t1) m() { log.Println(t.int) }
type internalMessage struct{ v *t1 }
func f(msg chan internalMessage, input int) {
k := &t1{input}
if input > 2 {
k = nil
}
msg <- internalMessage{k}
}
func SyncPublicMethod(input int) iface {
ch := make(chan internalMessage)
go f(ch, input)
answer := <-ch
// Problem: if answer.v == nil then this will created typed nil iface return value
return answer.v
}
func main() {
for i := 0; i < 10; i++ {
k := SyncPublicMethod(i)
if k == nil { //@ diag(`this comparison is never true`)
log.Println("never printed")
return
}
// Will panic.
k.m()
}
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i26000/26000.go
================================================
package main
import (
"fmt"
"os"
)
type CustomError struct {
Err string
}
func (ce CustomError) Error() string {
return ce.Err
}
func SomeFunc() (string, *CustomError) {
return "hello", nil
}
func main() {
// Do something that creates a variable err of type error
_, err := os.Open("/")
if err != nil {
panic(err)
}
// Then replace the err type with *CustomError
val, err := SomeFunc()
if err != nil { //@ diag(`this comparison is always true`)
panic(err)
}
fmt.Println("No problem", val)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i27815/27815.go
================================================
package main
import (
"fmt"
)
type MyError struct {
x string
}
func (e *MyError) Error() string {
return e.x
}
func f() *MyError {
return nil
}
func main() {
var e error
e = f()
// e should be nil ?
if e != nil { //@ diag(`this comparison is always true`)
fmt.Println("NOT NIL")
} else {
fmt.Println("NIL")
}
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i28241/28241.go
================================================
package main
import (
"fmt"
"reflect"
)
type Nil interface {
String() string
}
func MakeNil() Nil {
var n *NilStruct
return n
}
type NilStruct struct {
Data string
}
func (n *NilStruct) String() string {
return n.Data
}
func main() {
var n *NilStruct
fmt.Printf("%t %#v %s %t\n",
n == nil,
n,
reflect.ValueOf(n).Kind(),
reflect.ValueOf(n).IsNil())
n2 := MakeNil()
fmt.Printf("%t %#v %s %t\n",
n2 == nil, //@ diag(`this comparison is never true`)
n2,
reflect.ValueOf(n2).Kind(),
reflect.ValueOf(n2).IsNil())
fmt.Println(n2.String())
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i31873/31873.go
================================================
package main
import "fmt"
type S struct{}
func (s *S) Error() string {
return "error for S"
}
func structNil() *S {
return nil
}
func errorNil() error {
return nil
}
func main() {
err := errorNil()
fmt.Println(err != nil)
err = structNil()
fmt.Println(err != nil) //@ diag(`this comparison is always true`)
err = errorNil()
fmt.Println(err != nil)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i33965/33965.go
================================================
package pkg
import (
"fmt"
"testing"
)
type customError struct {
}
func (p *customError) Error() string {
return "custom error"
}
func getNilCustomError() *customError {
return nil
}
func TestWebSocketClient_basic(t *testing.T) {
err1 := getNilCustomError()
fmt.Println(err1 == nil) // ok is true
err2 := error(nil)
err2 = getNilCustomError()
fmt.Println(err2 == nil) //@ diag(`this comparison is never true`)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i33994/33994.go
================================================
package main
import (
"errors"
"fmt"
)
func main() {
var err = errors.New("errors msg")
name, err := GetName()
if err != nil { //@ diag(`this comparison is always true`)
fmt.Println(err)
} else {
fmt.Println(name)
}
}
type Error struct {
Message string
}
func (e *Error) Error() string {
if e == nil {
return "Error is nil"
}
return e.Message
}
func GetName() (string, *Error) {
var err = &Error{
Message: "error msg",
}
err = nil
return "yixinin", err
}
================================================
FILE: staticcheck/sa4023/testdata/go1.0/i35217/35217.go
================================================
package main
import (
"errors"
"fmt"
)
type someError struct {
Msg string
}
func (e *someError) Error() string {
return "someError: " + e.Msg
}
func calculate() (int, *someError) {
return 42, nil
}
func main() {
err := errors.New("ERROR")
num, err := calculate()
fmt.Println(num, err, err == nil) //@ diag(`this comparison is never true`)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.18/CheckTypedNilInterface/generics.go
================================================
package pkg
func tpgen1[T *int,]() T {
return (T)(nil)
}
func bar() {
if tpgen1() == nil {
}
}
func tpfn1[T any](x T) {
if any(x) == nil {
// this is entirely possible if baz is instantiated with an interface type for T. For example: baz[error](nil)
}
}
func tpfn2[T ~int](x T) {
if any(x) == nil { //@ diag(`this comparison is never true`)
// this is not possible, because T only accepts concrete types
}
}
func tpgen3[T any](x T) any {
return any(x)
}
func tpgen4[T ~*int](x T) any {
return any(x)
}
func tptest() {
_ = tpgen1() == nil
_ = tpgen3[error](nil) == nil
// ideally we'd flag this, but the analysis is generic-insensitive at the moment.
_ = tpgen3[*int](nil) == nil
_ = tpgen4[*int](nil) == nil //@ diag(`never true`)
}
================================================
FILE: staticcheck/sa4023/testdata/go1.9/CheckTypedNilInterface/CheckTypedNilInterface.go
================================================
package pkg
type any = interface{}
func test() {
_ = any((*int)(nil)) == nil //@ diag(`never true`)
_ = any((error)(nil)) == nil
}
================================================
FILE: staticcheck/sa4024/sa4024.go
================================================
package sa4024
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4024",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Checking for impossible return value from a builtin function`,
Text: `Return values of the \'len\' and \'cap\' builtins cannot be negative.
See https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.
Example:
if len(slice) < 0 {
fmt.Println("unreachable code")
}`,
Since: "2021.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var builtinLessThanZeroQ = pattern.MustParse(`
(Or
(BinaryExpr
(IntegerLiteral "0")
">"
(CallExpr builtin@(Builtin (Or "len" "cap")) _))
(BinaryExpr
(CallExpr builtin@(Builtin (Or "len" "cap")) _)
"<"
(IntegerLiteral "0")))
`)
func run(pass *analysis.Pass) (any, error) {
for node, matcher := range code.Matches(pass, builtinLessThanZeroQ) {
builtin := matcher.State["builtin"].(*ast.Ident)
report.Report(pass, node, fmt.Sprintf("builtin function %s does not return negative values", builtin.Name))
}
return nil, nil
}
================================================
FILE: staticcheck/sa4024/sa4024_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4024
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4024/testdata/go1.0/CheckBuiltinZeroComparison/CheckBuiltinZeroComparison.go
================================================
package pkg
func fn1() {
var foo []int
if len(foo) < 0 { //@ diag(`len does not return negative values`)
println("test")
}
switch {
case len(foo) < 0: //@ diag(`negative`)
println("test")
}
for len(foo) < 0 { //@ diag(`negative`)
println("test")
}
println(len(foo) < 0) //@ diag(`negative`)
if 0 > cap(foo) { //@ diag(`cap does not return negative values`)
println("test")
}
switch {
case 0 > cap(foo): //@ diag(`negative`)
println("test")
}
for 0 > cap(foo) { //@ diag(`negative`)
println("test")
}
println(0 > cap(foo)) //@ diag(`negative`)
}
func fn2() {
const zero = 0
var foo []int
println(len(foo) < zero)
println(len(foo) < 1)
}
================================================
FILE: staticcheck/sa4025/sa4025.go
================================================
package sa4025
import (
"fmt"
"go/ast"
"go/constant"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4025",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: "Integer division of literals that results in zero",
Text: `When dividing two integer constants, the result will
also be an integer. Thus, a division such as \'2 / 3\' results in \'0\'.
This is true for all of the following examples:
_ = 2 / 3
const _ = 2 / 3
const _ float64 = 2 / 3
_ = float64(2 / 3)
Staticcheck will flag such divisions if both sides of the division are
integer literals, as it is highly unlikely that the division was
intended to truncate to zero. Staticcheck will not flag integer
division involving named constants, to avoid noisy positives.
`,
Since: "2021.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var integerDivisionQ = pattern.MustParse(`(BinaryExpr (IntegerLiteral _) "/" (IntegerLiteral _))`)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, integerDivisionQ) {
val := constant.ToInt(pass.TypesInfo.Types[node.(ast.Expr)].Value)
if v, ok := constant.Uint64Val(val); ok && v == 0 {
report.Report(pass, node, fmt.Sprintf("the integer division '%s' results in zero", report.Render(pass, node)))
}
// TODO: we could offer a suggested fix here, but I am not
// sure what it should be. There are many options to choose
// from.
// Note: we experimented with flagging divisions that truncate
// (e.g. 4 / 3), but it ran into false positives in Go's
// 'time' package, which does this, deliberately:
//
// unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
//
// The check also found a real bug in other code, but I don't
// think we can outright ban this kind of division.
}
return nil, nil
}
================================================
FILE: staticcheck/sa4025/sa4025_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4025
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4025/testdata/go1.0/CheckIntegerDivisionEqualsZero/CheckIntegerDivisionEqualsZero.go
================================================
package pkg
func foo(x float64) {}
func fn() {
_ = 2 / 3 //@ diag(`results in zero`)
_ = 4 / 2
_ = 4 / 3
_ = 0 / 2 //@ diag(`results in zero`)
_ = 2 / 3.
_ = 2 / 3.0
_ = 2.0 / 3
const _ = 2 / 3 //@ diag(`results in zero`)
const _ float64 = 2 / 3 //@ diag(`results in zero`)
_ = float64(2 / 3) //@ diag(`results in zero`)
foo(1 / 2) //@ diag(`results in zero`)
}
================================================
FILE: staticcheck/sa4026/sa4026.go
================================================
package sa4026
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4026",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: "Go constants cannot express negative zero",
Text: `In IEEE 754 floating point math, zero has a sign and can be positive
or negative. This can be useful in certain numerical code.
Go constants, however, cannot express negative zero. This means that
the literals \'-0.0\' and \'0.0\' have the same ideal value (zero) and
will both represent positive zero at runtime.
To explicitly and reliably create a negative zero, you can use the
\'math.Copysign\' function: \'math.Copysign(0, -1)\'.`,
Since: "2021.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var negativeZeroFloatQ = pattern.MustParse(`
(Or
(UnaryExpr
"-"
(BasicLit "FLOAT" "0.0"))
(UnaryExpr
"-"
(CallExpr conv@(Object (Or "float32" "float64")) lit@(Or (BasicLit "INT" "0") (BasicLit "FLOAT" "0.0"))))
(CallExpr
conv@(Object (Or "float32" "float64"))
(UnaryExpr "-" lit@(BasicLit "INT" "0"))))`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, negativeZeroFloatQ) {
if conv, ok := m.State["conv"].(*types.TypeName); ok {
var replacement string
// TODO(dh): how does this handle type aliases?
if conv.Name() == "float32" {
replacement = `float32(math.Copysign(0, -1))`
} else {
replacement = `math.Copysign(0, -1)`
}
report.Report(pass, node,
fmt.Sprintf("in Go, the floating-point expression '%s' is the same as '%s(%s)', it does not produce a negative zero",
report.Render(pass, node),
conv.Name(),
report.Render(pass, m.State["lit"])),
report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
} else {
const replacement = `math.Copysign(0, -1)`
report.Report(pass, node,
"in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero",
report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement))))
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa4026/sa4026_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4026
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go
================================================
package pkg
const x = 0.0
func fn() {
_ = -0.0 //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = float32(-0.0) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = float64(-0.0) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = -float32(0) //@ diag(`in Go, the floating-point expression '-float32(0)' is the same as 'float32(0)', it does not produce a negative zero`)
_ = -float64(0) //@ diag(`in Go, the floating-point expression '-float64(0)' is the same as 'float64(0)', it does not produce a negative zero`)
_ = -float32(0.0) //@ diag(`in Go, the floating-point expression '-float32(0.0)' is the same as 'float32(0.0)', it does not produce a negative zero`)
_ = -float64(0.0) //@ diag(`in Go, the floating-point expression '-float64(0.0)' is the same as 'float64(0.0)', it does not produce a negative zero`)
// intentionally not flagged
_ = -x
}
================================================
FILE: staticcheck/sa4026/testdata/go1.0/CheckNegativeZeroFloat/CheckNegativeZeroFloat.go.golden
================================================
package pkg
const x = 0.0
func fn() {
_ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = float64(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero`)
_ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0)' is the same as 'float32(0)', it does not produce a negative zero`)
_ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0)' is the same as 'float64(0)', it does not produce a negative zero`)
_ = float32(math.Copysign(0, -1)) //@ diag(`in Go, the floating-point expression '-float32(0.0)' is the same as 'float32(0.0)', it does not produce a negative zero`)
_ = math.Copysign(0, -1) //@ diag(`in Go, the floating-point expression '-float64(0.0)' is the same as 'float64(0.0)', it does not produce a negative zero`)
// intentionally not flagged
_ = -x
}
================================================
FILE: staticcheck/sa4027/sa4027.go
================================================
package sa4027
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4027",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `\'(*net/url.URL).Query\' returns a copy, modifying it doesn't change the URL`,
Text: `\'(*net/url.URL).Query\' parses the current value of \'net/url.URL.RawQuery\'
and returns it as a map of type \'net/url.Values\'. Subsequent changes to
this map will not affect the URL unless the map gets encoded and
assigned to the URL's \'RawQuery\'.
As a consequence, the following code pattern is an expensive no-op:
\'u.Query().Add(key, value)\'.`,
Since: "2021.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var ineffectiveURLQueryAddQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (SelectorExpr recv (Ident "Query")) []) (Ident meth)) _)`)
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): We could make this check more complex and detect
// pointless modifications of net/url.Values in general, but that
// requires us to get the state machine correct, else we'll cause
// false positives.
for node, m := range code.Matches(pass, ineffectiveURLQueryAddQ) {
if !code.IsOfPointerToTypeWithName(pass, m.State["recv"].(ast.Expr), "net/url.URL") {
continue
}
switch m.State["meth"].(string) {
case "Add", "Del", "Set":
default:
continue
}
report.Report(pass, node, "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL")
}
return nil, nil
}
================================================
FILE: staticcheck/sa4027/sa4027_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4027
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4027/testdata/go1.0/CheckIneffectiveURLQueryModification/CheckIneffectiveURLQueryModification.go
================================================
package pkg
import "net/url"
func fn(u *url.URL) {
u.Query().Add("", "") //@ diag(`returns a copy`)
u.Query().Set("", "") //@ diag(`returns a copy`)
u.Query().Del("") //@ diag(`returns a copy`)
u.Query().Encode()
var t T
t.Query().Add("", "")
}
type T struct{}
func (v T) Query() T { return v }
func (v T) Add(arg1, arg2 string) {}
================================================
FILE: staticcheck/sa4028/sa4028.go
================================================
package sa4028
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4028",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `\'x % 1\' is always zero`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants
},
})
var Analyzer = SCAnalyzer.Analyzer
var moduloOneQ = pattern.MustParse(`(BinaryExpr _ "%" (IntegerLiteral "1"))`)
func run(pass *analysis.Pass) (any, error) {
for node := range code.Matches(pass, moduloOneQ) {
report.Report(pass, node, "x % 1 is always zero")
}
return nil, nil
}
================================================
FILE: staticcheck/sa4028/sa4028_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4028
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4028/testdata/go1.0/CheckModuloOne/CheckModuloOne.go
================================================
package pkg
func fn() {
var a int
var b uint
_ = a % 1 //@ diag(`x % 1 is always zero`)
_ = a % 2
_ = b % 1 //@ diag(`x % 1 is always zero`)
}
================================================
FILE: staticcheck/sa4029/sa4029.go
================================================
package sa4029
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4029",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: "Ineffective attempt at sorting slice",
Text: `
\'sort.Float64Slice\', \'sort.IntSlice\', and \'sort.StringSlice\' are
types, not functions. Doing \'x = sort.StringSlice(x)\' does nothing,
especially not sort any values. The correct usage is
\'sort.Sort(sort.StringSlice(x))\' or \'sort.StringSlice(x).Sort()\',
but there are more convenient helpers, namely \'sort.Float64s\',
\'sort.Ints\', and \'sort.Strings\'.
`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var ineffectiveSortQ = pattern.MustParse(`(AssignStmt target@(Ident _) "=" (CallExpr typ@(Symbol (Or "sort.Float64Slice" "sort.IntSlice" "sort.StringSlice")) [target]))`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, ineffectiveSortQ) {
_, ok := types.Unalias(pass.TypesInfo.TypeOf(m.State["target"].(ast.Expr))).(*types.Slice)
if !ok {
// Avoid flagging 'x = sort.StringSlice(x)' where TypeOf(x) == sort.StringSlice
continue
}
var alternative string
typeName := types.TypeString(types.Unalias(m.State["typ"].(*types.TypeName).Type()), nil)
switch typeName {
case "sort.Float64Slice":
alternative = "Float64s"
case "sort.IntSlice":
alternative = "Ints"
case "sort.StringSlice":
alternative = "Strings"
default:
panic(fmt.Sprintf("unreachable: %q", typeName))
}
r := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "sort"},
Sel: &ast.Ident{Name: alternative},
},
Args: []ast.Expr{m.State["target"].(ast.Expr)},
}
report.Report(pass, node,
fmt.Sprintf("%s is a type, not a function, and %s doesn't sort your values; consider using sort.%s instead",
typeName,
report.Render(pass, node.(*ast.AssignStmt).Rhs[0]),
alternative),
report.Fixes(edit.Fix(fmt.Sprintf("Replace with call to sort.%s", alternative), edit.ReplaceWithNode(pass.Fset, node, r))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa4029/sa4029_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4029
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4029/testdata/go1.0/CheckIneffectiveSort/CheckIneffectiveSort.go
================================================
package pkg
import "sort"
func fn() {
var a []string
var b []float64
var c sort.StringSlice
a = sort.StringSlice(a) //@ diag(re`sort\.StringSlice is a type.+consider using sort\.Strings instead`)
b = sort.Float64Slice(b) //@ diag(re`sort\.Float64Slice is a type.+consider using sort\.Float64s instead`)
c = sort.StringSlice(c)
}
================================================
FILE: staticcheck/sa4029/testdata/go1.0/CheckIneffectiveSort/CheckIneffectiveSort.go.golden
================================================
package pkg
import "sort"
func fn() {
var a []string
var b []float64
var c sort.StringSlice
sort.Strings(a) //@ diag(re`sort\.StringSlice is a type.+consider using sort\.Strings instead`)
sort.Float64s(b) //@ diag(re`sort\.Float64Slice is a type.+consider using sort\.Float64s instead`)
c = sort.StringSlice(c)
}
================================================
FILE: staticcheck/sa4029/testdata/go1.9/CheckIneffectiveSort/CheckIneffectiveSort.go
================================================
package pkg
import "sort"
func fn() {
type Strings = []string
var d Strings
d = sort.StringSlice(d) //@ diag(re`sort\.StringSlice is a type.+consider using sort\.Strings instead`)
}
================================================
FILE: staticcheck/sa4029/testdata/go1.9/CheckIneffectiveSort/CheckIneffectiveSort.go.golden
================================================
package pkg
import "sort"
func fn() {
type Strings = []string
var d Strings
sort.Strings(d) //@ diag(re`sort\.StringSlice is a type.+consider using sort\.Strings instead`)
}
================================================
FILE: staticcheck/sa4030/sa4030.go
================================================
package sa4030
import (
"fmt"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4030",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: "Ineffective attempt at generating random number",
Text: `
Functions in the \'math/rand\' package that accept upper limits, such
as \'Intn\', generate random numbers in the half-open interval [0,n). In
other words, the generated numbers will be \'>= 0\' and \'< n\' – they
don't include \'n\'. \'rand.Intn(1)\' therefore doesn't generate \'0\'
or \'1\', it always generates \'0\'.`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var ineffectiveRandIntQ = pattern.MustParse(`
(CallExpr
(Symbol
name@(Or
"math/rand.Int31n"
"math/rand.Int63n"
"math/rand.Intn"
"(*math/rand.Rand).Int31n"
"(*math/rand.Rand).Int63n"
"(*math/rand.Rand).Intn"
"math/rand/v2.Int32N"
"math/rand/v2.Int64N"
"math/rand/v2.IntN"
"math/rand/v2.N"
"math/rand/v2.Uint32N"
"math/rand/v2.Uint64N"
"math/rand/v2.UintN"
"(*math/rand/v2.Rand).Int32N"
"(*math/rand/v2.Rand).Int64N"
"(*math/rand/v2.Rand).IntN"
"(*math/rand/v2.Rand).Uint32N"
"(*math/rand/v2.Rand).Uint64N"
"(*math/rand/v2.Rand).UintN"))
[(IntegerLiteral "1")])`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, ineffectiveRandIntQ) {
report.Report(pass, node,
fmt.Sprintf("%s(n) generates a random value 0 <= x < n; that is, the generated values don't include n; %s therefore always returns 0",
m.State["name"], report.Render(pass, node)))
}
return nil, nil
}
================================================
FILE: staticcheck/sa4030/sa4030_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4030
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4030/testdata/go1.0/CheckIneffectiveRandInt/CheckIneffectiveRandInt.go
================================================
package pkg
import "math/rand"
func fn() {
type T struct {
rng rand.Rand
}
_ = rand.Intn(1) //@ diag(re`rand\.Intn\(n\) generates.+rand\.Intn\(1\) therefore`)
_ = rand.Int63n(1) //@ diag(re`rand\.Int63n\(n\) generates.+rand\.Int63n\(1\) therefore`)
var t T
_ = t.rng.Intn(1) //@ diag(re`\(\*math/rand\.Rand\)\.Intn\(n\) generates.+t\.rng\.Intn\(1\) therefore`)
_ = rand.Intn(2)
}
================================================
FILE: staticcheck/sa4030/testdata/go1.22/CheckIneffectiveRandInt/CheckIneffectiveRandInt.go
================================================
package pkg
import "math/rand/v2"
func fn() {
type T struct {
rng rand.Rand
}
_ = rand.IntN(1) //@ diag(re`rand/v2\.IntN\(n\) generates.+rand\.IntN\(1\) therefore`)
_ = rand.Int64N(1) //@ diag(re`rand/v2\.Int64N\(n\) generates.+rand\.Int64N\(1\) therefore`)
_ = rand.N(1) //@ diag(re`rand/v2\.N\(n\) generates.+rand\.N\(1\) therefore`)
var t T
_ = t.rng.IntN(1) //@ diag(re`\(\*math/rand/v2\.Rand\)\.IntN\(n\) generates.+t\.rng\.IntN\(1\) therefore`)
_ = rand.IntN(2)
}
================================================
FILE: staticcheck/sa4031/sa4031.go
================================================
package sa4031
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/pattern"
"honnef.co/go/tools/staticcheck/sa4022"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4031",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Checking never-nil value against nil`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var allocationNilCheckQ = pattern.MustParse(`(IfStmt _ cond@(BinaryExpr lhs op@(Or "==" "!=") (Builtin "nil")) _ _)`)
func run(pass *analysis.Pass) (any, error) {
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
var path []ast.Node
fn := func(node ast.Node, stack []ast.Node) {
m, ok := code.Match(pass, allocationNilCheckQ, node)
if !ok {
return
}
cond := m.State["cond"].(ast.Node)
if _, ok := code.Match(pass, sa4022.CheckAddressIsNilQ, cond); ok {
// Don't duplicate diagnostics reported by SA4022
return
}
lhs := m.State["lhs"].(ast.Expr)
path = path[:0]
for i := len(stack) - 1; i >= 0; i-- {
path = append(path, stack[i])
}
irfn := ir.EnclosingFunction(irpkg, path)
if irfn == nil {
// For example for functions named "_", because we don't generate IR for them.
return
}
v, isAddr := irfn.ValueForExpr(lhs)
if isAddr {
return
}
seen := map[ir.Value]struct{}{}
var values []ir.Value
var neverNil func(v ir.Value, track bool) bool
neverNil = func(v ir.Value, track bool) bool {
if _, ok := seen[v]; ok {
return true
}
seen[v] = struct{}{}
switch v := v.(type) {
case *ir.MakeClosure, *ir.Function:
if track {
values = append(values, v)
}
return true
case *ir.MakeChan, *ir.MakeMap, *ir.MakeSlice, *ir.Alloc:
if track {
values = append(values, v)
}
return true
case *ir.Slice:
if track {
values = append(values, v)
}
return neverNil(v.X, false)
case *ir.FieldAddr:
if track {
values = append(values, v)
}
return neverNil(v.X, false)
case *ir.Sigma:
return neverNil(v.X, true)
case *ir.Phi:
for _, e := range v.Edges {
if !neverNil(e, true) {
return false
}
}
return true
default:
return false
}
}
if !neverNil(v, true) {
return
}
var qualifier string
if op := m.State["op"].(token.Token); op == token.EQL {
qualifier = "never"
} else {
qualifier = "always"
}
fallback := fmt.Sprintf("this nil check is %s true", qualifier)
sort.Slice(values, func(i, j int) bool { return values[i].Pos() < values[j].Pos() })
if ident, ok := m.State["lhs"].(*ast.Ident); ok {
if _, ok := pass.TypesInfo.ObjectOf(ident).(*types.Var); ok {
var opts []report.Option
if v.Parent() == irfn {
if len(values) == 1 {
opts = append(opts, report.Related(values[0], fmt.Sprintf("this is the value of %s", ident.Name)))
} else {
for _, vv := range values {
opts = append(opts, report.Related(vv, fmt.Sprintf("this is one of the value of %s", ident.Name)))
}
}
}
switch v.(type) {
case *ir.MakeClosure, *ir.Function:
report.Report(pass, cond, "the checked variable contains a function and is never nil; did you mean to call it?", opts...)
default:
report.Report(pass, cond, fallback, opts...)
}
} else {
if _, ok := v.(*ir.Function); ok {
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
} else {
report.Report(pass, cond, fallback)
}
}
} else {
if _, ok := v.(*ir.Function); ok {
report.Report(pass, cond, "functions are never nil; did you mean to call it?")
} else {
report.Report(pass, cond, fallback)
}
}
}
code.PreorderStack(pass, fn, (*ast.IfStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa4031/sa4031_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa4031
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa4031/testdata/go1.0/CheckAllocationNilCheck/CheckAllocationNilCheck.go
================================================
package pkg
type T struct {
Field func()
}
func (T) Method() {}
func gen() *int { return nil }
func fn1() {
var a *int
b := new(int)
c := make([]byte, 0)
var t T
var pt *T
d := t.Field
e := t.Method
f := &t.Field
g := fn1
h := &T{}
i := gen()
j := func() {}
k := make(map[string]int)
var slice []byte
l := slice[:0]
var m []byte
if true {
m = []byte{}
} else {
m = []byte{}
}
n := m[:0]
o := &pt.Field
if a != nil {
}
if b != nil { //@ diag(`always true`)
}
if b == nil { //@ diag(`never true`)
}
if c != nil { //@ diag(`always true`)
}
if d != nil { // field value could be anything
}
if e != nil { //@ diag(`contains a function`)
}
if f != nil { //@ diag(`always true`)
}
if g != nil { //@ diag(`contains a function`)
}
if h != nil { //@ diag(`always true`)
}
if &a != nil { // already flagged by SA4022
}
if (&T{}).Method != nil { //@ diag(`always true`)
}
if (&T{}) != nil { // already flagged by SA4022
}
if i != nil { // just a function return value
}
if fn1 != nil { //@ diag(`functions are never nil`)
}
if j != nil { //@ diag(`contains a function`)
}
if k != nil { //@ diag(`always true`)
}
if l != nil { // slicing a nil slice yields nil
}
if m != nil { //@ diag(`always true`)
}
if n != nil { //@ diag(`always true`)
}
if o != nil { // in &pt.Field, pt might be nil
}
}
func fn2() {
x := new(int)
if true {
x = nil
}
if x != nil {
}
}
================================================
FILE: staticcheck/sa4032/sa4032.go
================================================
package sa4032
import (
"fmt"
"go/ast"
"go/build/constraint"
"go/constant"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/pattern"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA4032",
Run: CheckImpossibleGOOSGOARCH,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Comparing \'runtime.GOOS\' or \'runtime.GOARCH\' against impossible value`,
Since: "2024.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
goosComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOOS") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`)
goarchComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOARCH") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`)
)
func CheckImpossibleGOOSGOARCH(pass *analysis.Pass) (any, error) {
// TODO(dh): validate GOOS and GOARCH together. that is,
// given '(linux && amd64) || (windows && mips)',
// flag 'if runtime.GOOS == "linux" && runtime.GOARCH == "mips"'
//
// We can't use our IR for the control flow graph, because go/types constant folds constant comparisons, so
// 'runtime.GOOS == "windows"' will just become 'false'. We can't use the AST-based CFG builder from x/tools,
// because it doesn't model branch conditions.
if !code.CouldMatchAny(pass, goarchComparisonQ, goosComparisonQ) {
return nil, nil
}
for _, f := range pass.Files {
expr, ok := code.BuildConstraints(pass, f)
if !ok {
continue
}
ast.Inspect(f, func(node ast.Node) bool {
if m, ok := code.Match(pass, goosComparisonQ, node); ok {
tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)]
goos := constant.StringVal(tv.Value)
if _, ok := knowledge.KnownGOOS[goos]; !ok {
// Don't try to reason about GOOS values we don't know about. Maybe the user is using a newer
// version of Go that supports a new target, or maybe they run a fork of Go.
return true
}
sat, ok := validateGOOSComparison(expr, goos)
if !ok {
return true
}
if !sat {
// Note that we do not have to worry about constraints that can never be satisfied, such as 'linux
// && windows'. Packages with such files will not be passed to Staticcheck in the first place,
// precisely because the constraints aren't satisfiable.
report.Report(pass, node,
fmt.Sprintf("due to the file's build constraints, runtime.GOOS will never equal %q", goos))
}
} else if m, ok := code.Match(pass, goarchComparisonQ, node); ok {
tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)]
goarch := constant.StringVal(tv.Value)
if _, ok := knowledge.KnownGOARCH[goarch]; !ok {
// Don't try to reason about GOARCH values we don't know about. Maybe the user is using a newer
// version of Go that supports a new target, or maybe they run a fork of Go.
return true
}
sat, ok := validateGOARCHComparison(expr, goarch)
if !ok {
return true
}
if !sat {
// Note that we do not have to worry about constraints that can never be satisfied, such as 'amd64
// && mips'. Packages with such files will not be passed to Staticcheck in the first place,
// precisely because the constraints aren't satisfiable.
report.Report(pass, node,
fmt.Sprintf("due to the file's build constraints, runtime.GOARCH will never equal %q", goarch))
}
}
return true
})
}
return nil, nil
}
func validateGOOSComparison(expr constraint.Expr, goos string) (sat bool, didCheck bool) {
matchGoosTag := func(tag string, goos string) (ok bool, goosTag bool) {
switch tag {
case "aix",
"android",
"dragonfly",
"freebsd",
"hurd",
"illumos",
"ios",
"js",
"netbsd",
"openbsd",
"plan9",
"wasip1",
"windows":
return goos == tag, true
case "darwin":
return (goos == "darwin" || goos == "ios"), true
case "linux":
return (goos == "linux" || goos == "android"), true
case "solaris":
return (goos == "solaris" || goos == "illumos"), true
case "unix":
return (goos == "aix" ||
goos == "android" ||
goos == "darwin" ||
goos == "dragonfly" ||
goos == "freebsd" ||
goos == "hurd" ||
goos == "illumos" ||
goos == "ios" ||
goos == "linux" ||
goos == "netbsd" ||
goos == "openbsd" ||
goos == "solaris"), true
default:
return false, false
}
}
return validateTagComparison(expr, func(tag string) (matched bool, special bool) {
return matchGoosTag(tag, goos)
})
}
func validateGOARCHComparison(expr constraint.Expr, goarch string) (sat bool, didCheck bool) {
matchGoarchTag := func(tag string, goarch string) (ok bool, goosTag bool) {
switch tag {
case "386",
"amd64",
"arm",
"arm64",
"loong64",
"mips",
"mipsle",
"mips64",
"mips64le",
"ppc64",
"ppc64le",
"riscv64",
"s390x",
"sparc64",
"wasm":
return goarch == tag, true
default:
return false, false
}
}
return validateTagComparison(expr, func(tag string) (matched bool, special bool) {
return matchGoarchTag(tag, goarch)
})
}
func validateTagComparison(expr constraint.Expr, matchSpecialTag func(tag string) (matched bool, special bool)) (sat bool, didCheck bool) {
otherTags := map[string]int{}
// Collect all tags that aren't known architecture-based tags
b := expr.Eval(func(tag string) bool {
ok, special := matchSpecialTag(tag)
if !special {
// Assign an ID to this tag, but only if we haven't seen it before. For the expression 'foo && foo', this
// callback will be called twice for the 'foo' tag.
if _, ok := otherTags[tag]; !ok {
otherTags[tag] = len(otherTags)
}
}
return ok
})
if b || len(otherTags) == 0 {
// We're done. Either the formula can be satisfied regardless of the values of non-special tags, if any,
// or there aren't any non-special tags and the formula cannot be satisfied.
return b, true
}
if len(otherTags) > 10 {
// We have to try 2**len(otherTags) combinations of tags. 2**10 is about the worst we're willing to try.
return false, false
}
// Try all permutations of otherTags. If any evaluates to true, then the expression is satisfiable.
for bits := 0; bits < 1< 10 {
return true
}
return fn2(x + 1)
}
func fn3(x int) bool {
println(x)
if x > 10 {
goto l1
}
return fn3(x + 1)
l1:
println(x)
return true
}
func fn4(p *int, n int) {
if n == 0 {
return
}
x := 0
fn4(&x, n-1)
if x != n {
panic("stack is corrupted")
}
}
func fn5(p *int, n int) {
x := 0
fn5(&x, n-1) //@ diag(`infinite recursive call`)
if x != n {
panic("stack is corrupted")
}
}
func fn6() {
go fn6()
}
type T struct {
n int
}
func (t T) Fn1() {
t.Fn1() //@ diag(`infinite recursive call`)
}
func (t T) Fn2() {
x := T{}
x.Fn2() //@ diag(`infinite recursive call`)
}
func (t T) Fn3() {
if t.n == 0 {
return
}
t.Fn1()
}
================================================
FILE: staticcheck/sa5008/jsonv2.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// This file is a modified copy of Go's encoding/json/v2/field.go
package sa5008
import (
"fmt"
"go/ast"
"go/types"
"io"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
)
func validateJSONTag(pass *analysis.Pass, field *ast.Field, tag string) {
hasTag := tag != ""
tagOrig := tag
// Check whether this field is explicitly ignored.
if tag == "-" {
return
}
// Check whether this field is unexported and not embedded,
// which Go reflection cannot mutate for the sake of serialization.
//
// An embedded field of an unexported type is still capable of
// forwarding exported fields, which may be JSON serialized.
// This technically operates on the edge of what is permissible by
// the Go language, but the most recent decision is to permit this.
//
// See https://go.dev/issue/24153 and https://go.dev/issue/32772.
anonymous := len(field.Names) == 0
if !anonymous && !field.Names[0].IsExported() {
// Tag options specified on an unexported field suggests user error.
if hasTag {
report.Report(pass, field.Tag,
fmt.Sprintf("unexported struct field cannot have non-ignored `json:%q` tag", tag))
}
return
}
if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
// For better compatibility with v1, accept almost any unescaped name.
n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes
}))
name := tag[:n]
// If the next character is not a comma, then the name is either
// malformed (if n > 0) or a single-quoted name.
// In either case, call consumeTagOption to handle it further.
var err error
if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
name, n, err = consumeTagOption(tag)
if err != nil {
report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err))
}
}
if !utf8.ValidString(name) {
report.Report(pass, field.Tag,
fmt.Sprintf("invalid UTF-8 in JSON object name %q", name))
name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError
}
if name == "-" && tag[0] == '-' {
// TODO(dh): offer automatic fix
report.Report(pass, field.Tag,
fmt.Sprintf("should encoding/json ignore this field or name it \"-\"? Either use `json:\"-\"` to ignore the field or use `json:\"'-'%s` to specify %q as the name",
strings.TrimPrefix(strconv.Quote(tagOrig), `"-`), name))
}
tag = tag[n:]
}
// Handle any additional tag options (if any).
var wasFormat bool
seenOpts := make(map[string]bool)
for len(tag) > 0 {
// Consume comma delimiter.
if tag[0] != ',' {
report.Report(pass, field.Tag,
fmt.Sprintf("malformed `json` tag: invalid character %q before next option (expecting ',')",
tag[0]))
} else {
tag = tag[len(","):]
if len(tag) == 0 {
report.Report(pass, field.Tag, "malformed `json` tag: invalid trailing ',' character")
break
}
}
// Consume and process the tag option.
opt, n, err := consumeTagOption(tag)
if err != nil {
report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err))
}
rawOpt := tag[:n]
tag = tag[n:]
switch {
case wasFormat:
report.Report(pass, field.Tag, "`format` tag option was not specified last")
case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
// TODO(dh): offer automatic fix
report.Report(pass, field.Tag,
fmt.Sprintf("unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", rawOpt, opt))
}
switch opt {
case "case":
if !strings.HasPrefix(tag, ":") {
// TODO(dh): offer automatic fix
report.Report(pass, field.Tag,
"missing value for `case` tag option; specify `case:ignore` or `case:strict` instead")
break
}
tag = tag[len(":"):]
opt, n, err := consumeTagOption(tag)
if err != nil {
report.Report(pass, field.Tag,
fmt.Sprintf("malformed value for `case` tag option: %v", err))
break
}
rawOpt := tag[:n]
tag = tag[n:]
if strings.HasPrefix(rawOpt, "'") {
// TODO(dh): offer automatic fix
report.Report(pass, field.Tag,
fmt.Sprintf("unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead",
rawOpt, opt))
}
switch opt {
case "ignore":
case "strict":
default:
report.Report(pass, field.Tag,
fmt.Sprintf("invalid appearance of unknown `case:%s` tag value", rawOpt))
}
case "inline":
case "unknown":
case "omitzero":
case "omitempty":
case "string":
const msg = "invalid appearance of `string` tag option; it is only intended for fields of numeric types or pointers to those"
tset := typeutil.NewTypeSet(pass.TypesInfo.TypeOf(field.Type))
if len(tset.Terms) == 0 {
// TODO(dh): improve message, call out the use of type parameters
report.Report(pass, field.Tag, msg)
continue
}
for _, term := range tset.Terms {
T := typeutil.Dereference(term.Type().Underlying())
for _, term2 := range typeutil.NewTypeSet(T).Terms {
basic, ok := term2.Type().Underlying().(*types.Basic)
// We accept bools and strings because v1 of encoding/json
// supports those. We don't mention that in the message,
// however, because their support is accidental, and v2
// doesn't support it.
if !ok || (basic.Info()&(types.IsBoolean|types.IsInteger|types.IsFloat|types.IsString)) == 0 {
// TODO(dh): improve message, show how we arrived at the type
report.Report(pass, field.Tag, msg)
}
}
}
case "format":
if !strings.HasPrefix(tag, ":") {
report.Report(pass, field.Tag, "missing value for `format` tag option")
break
}
tag = tag[len(":"):]
_, n, err := consumeTagOption(tag)
if err != nil {
report.Report(pass, field.Tag,
fmt.Sprintf("malformed value for `format` tag option: %v", err))
break
}
tag = tag[n:]
wasFormat = true
default:
// Reject keys that resemble one of the supported options.
// This catches invalid mutants such as "omitEmpty" or "omit_empty".
normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
switch normOpt {
case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
report.Report(pass, field.Tag,
fmt.Sprintf("invalid appearance of `%s` tag option; specify `%s` instead",
opt, normOpt))
default:
report.Report(pass, field.Tag,
fmt.Sprintf("invalid appearance of unknown `%s` tag option", opt))
}
}
// Reject duplicates.
if seenOpts[opt] {
report.Report(pass, field.Tag,
fmt.Sprintf("duplicate appearance of `%s` tag option", rawOpt))
}
seenOpts[opt] = true
}
if seenOpts["inline"] && seenOpts["unknown"] {
report.Report(pass, field.Tag,
"field cannot have both `inline` and `unknown` specified")
}
// TODO(dh): implement more restrictions for types of inlined and unknown
// fields, including recursive restrictions:
//
// - Go struct field %s cannot have any options other than `inline` or `unknown` specified
// - inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value
// - inlined Go struct field %s is not exported
// - inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods
// - inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value
}
// consumeTagOption consumes the next option,
// which is either a Go identifier or a single-quoted string.
// If the next option is invalid, it returns all of in until the next comma,
// and reports an error.
func consumeTagOption(in string) (string, int, error) {
// For legacy compatibility with v1, assume options are comma-separated.
i := strings.IndexByte(in, ',')
if i < 0 {
i = len(in)
}
switch r, _ := utf8.DecodeRuneInString(in); {
// Option as a Go identifier.
case r == '_' || unicode.IsLetter(r):
n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
return in[:n], n, nil
// Option as a single-quoted string.
case r == '\'':
// The grammar is nearly identical to a double-quoted Go string literal,
// but uses single quotes as the terminators. The reason for a custom
// grammar is because both backtick and double quotes cannot be used
// verbatim in a struct tag.
//
// Convert a single-quoted string to a double-quote string and rely on
// strconv.Unquote to handle the rest.
var inEscape bool
b := []byte{'"'}
n := len(`'`)
for len(in) > n {
r, rn := utf8.DecodeRuneInString(in[n:])
switch {
case inEscape:
if r == '\'' {
b = b[:len(b)-1] // remove escape character: `\'` => `'`
}
inEscape = false
case r == '\\':
inEscape = true
case r == '"':
b = append(b, '\\') // insert escape character: `"` => `\"`
case r == '\'':
b = append(b, '"')
n += len(`'`)
out, err := strconv.Unquote(string(b))
if err != nil {
return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n])
}
return out, n, nil
}
b = append(b, in[n:][:rn]...)
n += rn
}
if n > 10 {
n = 10 // limit the amount of context printed in the error
}
//lint:ignore ST1005 The ellipsis denotes truncated text
return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
case len(in) == 0:
return in[:i], i, io.ErrUnexpectedEOF
default:
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
}
}
func isLetterOrDigit(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
}
================================================
FILE: staticcheck/sa5008/sa5008.go
================================================
package sa5008
import (
"fmt"
"go/ast"
"go/types"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/staticcheck/fakereflect"
"honnef.co/go/tools/staticcheck/fakexml"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA5008",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Invalid struct tag`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
importsGoFlags := false
// we use the AST instead of (*types.Package).Imports to work
// around vendored packages in GOPATH mode. A vendored package's
// path will include the vendoring subtree as a prefix.
for _, f := range pass.Files {
for _, imp := range f.Imports {
v := imp.Path.Value
if v[1:len(v)-1] == "github.com/jessevdk/go-flags" {
importsGoFlags = true
break
}
}
}
fn := func(node ast.Node) {
structNode := node.(*ast.StructType)
T := pass.TypesInfo.Types[structNode].Type.(*types.Struct)
rt := fakereflect.TypeAndCanAddr{
Type: T,
}
for i, field := range structNode.Fields.List {
if field.Tag == nil {
continue
}
tags, err := parseStructTag(field.Tag.Value[1 : len(field.Tag.Value)-1])
if err != nil {
report.Report(pass, field.Tag, fmt.Sprintf("unparseable struct tag: %s", err))
continue
}
for k, v := range tags {
if len(v) > 1 {
isGoFlagsTag := importsGoFlags &&
(k == "choice" || k == "optional-value" || k == "default")
if !isGoFlagsTag {
report.Report(pass, field.Tag, fmt.Sprintf("duplicate struct tag %q", k))
}
}
switch k {
case "json":
checkJSONTag(pass, field, v[0])
case "xml":
if _, err := fakexml.StructFieldInfo(rt.Field(i)); err != nil {
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: %s", err))
}
checkXMLTag(pass, field, v[0])
}
}
}
}
code.Preorder(pass, fn, (*ast.StructType)(nil))
return nil, nil
}
func checkJSONTag(pass *analysis.Pass, field *ast.Field, tag string) {
if pass.Pkg.Path() == "encoding/json" ||
pass.Pkg.Path() == "encoding/json_test" ||
pass.Pkg.Path() == "encoding/json/v2" ||
pass.Pkg.Path() == "encoding/json/v2_test" {
// don't flag malformed JSON tags in the encoding/json
// package; it knows what it is doing, and it is testing
// itself.
return
}
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
if len(tag) == 0 {
}
validateJSONTag(pass, field, tag)
}
func checkXMLTag(pass *analysis.Pass, field *ast.Field, tag string) {
//lint:ignore SA9003 TODO(dh): should we flag empty tags?
if len(tag) == 0 {
}
fields := strings.Split(tag, ",")
counts := map[string]int{}
for _, s := range fields[1:] {
switch s {
case "attr", "chardata", "cdata", "innerxml", "comment":
counts[s]++
case "omitempty", "any":
counts[s]++
case "":
default:
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: unknown option %q", s))
}
}
for k, v := range counts {
if v > 1 {
report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: duplicate option %q", k))
}
}
}
================================================
FILE: staticcheck/sa5008/sa5008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa5008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa5008/structtag.go
================================================
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2019 Dominik Honnef. All rights reserved.
package sa5008
import "strconv"
func parseStructTag(tag string) (map[string][]string, error) {
// FIXME(dh): detect missing closing quote
out := map[string][]string{}
for tag != "" {
// Skip leading space.
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// Scan to colon. A space, a quote or a control character is a syntax error.
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
// as it is simpler to inspect the tag's bytes than the tag's runes.
i = 0
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
i++
}
if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
break
}
name := string(tag[:i])
tag = tag[i+1:]
// Scan quoted string to find value.
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
break
}
qvalue := string(tag[:i+1])
tag = tag[i+1:]
value, err := strconv.Unquote(qvalue)
if err != nil {
return nil, err
}
out[name] = append(out[name], value)
}
return out, nil
}
================================================
FILE: staticcheck/sa5008/testdata/go1.0/CheckStructTags/CheckStructTags.go
================================================
package pkg
import (
"encoding/xml"
_ "github.com/jessevdk/go-flags"
)
type T1 struct {
B int `foo:"" foo:""` //@ diag(`duplicate struct tag`)
C int `foo:"" bar:""`
D int `json:"-"`
E int `json:"\\"` //@ diag("malformed `json` tag")
F int `json:",omitempty,omitempty"` //@ diag("duplicate appearance of `omitempty` tag option")
G int `json:",omitempty,string"`
H int `json:",string,omitempty,string"` //@ diag("duplicate appearance of `string` tag option")
I int `json:",foreign"` //@ diag("invalid appearance of unknown `foreign` tag option")
J int `json:",string"`
K *int `json:",string"`
L **int `json:",string"` //@ diag("invalid appearance of `string` tag option")
M complex128 `json:",string"` //@ diag("invalid appearance of `string` tag option")
N int `json:"some-name"`
O int `json:"some-name,omitzero,omitempty,format:'something,with,commas'"`
P string `json:"-,omitempty"` //@ diag(`should encoding/json ignore this field or name it`)
Q string `json:"'-',omitempty"`
R map[string]interface{} `json:"unknown"`
S map[string]interface{} `json:"inline"`
T2 `json:",omitzero"`
T3 `json:"bar,omitzero"`
}
type T2 struct {
A int `xml:",attr"`
B int `xml:",chardata"`
C int `xml:",cdata"`
D int `xml:",innerxml"`
E int `xml:",comment"`
F int `xml:",omitempty"`
G int `xml:",any"`
H int `xml:",unknown"` //@ diag(`unknown option`)
I int `xml:",any,any"` //@ diag(`duplicate option`)
J int `xml:"a>b>c,"`
}
type T3 struct {
A int `json:",omitempty" xml:",attr"`
B int `json:",foreign" xml:",attr"` //@ diag("invalid appearance of unknown `foreign` tag option")
}
type T4 struct {
A int `choice:"foo" choice:"bar"`
B []int `optional-value:"foo" optional-value:"bar"`
C []int `default:"foo" default:"bar"`
D int `json:"foo" json:"bar"` //@ diag(`duplicate struct tag`)
}
func xmlTags() {
type T1 struct {
A int `xml:",attr,innerxml"` //@ diag(`invalid combination of options: ",attr,innerxml"`)
XMLName xml.Name `xml:"ns "` //@ diag(`namespace without name: "ns "`)
B int `xml:"a>"` //@ diag(`trailing '>'`)
C int `xml:"a>b,attr"` //@ diag(`a>b chain not valid with attr flag`)
}
type T6 struct {
XMLName xml.Name `xml:"foo"`
}
type T5 struct {
F T6 `xml:"f"` //@ diag(`name "f" conflicts with name "foo" in example.com/CheckStructTags.T6.XMLName`)
}
}
================================================
FILE: staticcheck/sa5008/testdata/go1.0/CheckStructTags2/CheckStructTags2.go
================================================
package pkg
type T5 struct {
A int `choice:"foo" choice:"bar"` //@ diag(`duplicate struct tag`)
B []int `optional-value:"foo" optional-value:"bar"` //@ diag(`duplicate struct tag`)
C []int `default:"foo" default:"bar"` //@ diag(`duplicate struct tag`)
}
================================================
FILE: staticcheck/sa5008/testdata/go1.0/vendor/github.com/jessevdk/go-flags/pkg.go
================================================
package pkg
================================================
FILE: staticcheck/sa5008/testdata/go1.18/CheckStructTags/generics.go
================================================
package pkg
type S1[T any] struct {
// flag, 'any' is too permissive
F T `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S2[T int | string] struct {
// don't flag, all types in T are okay
F T `json:",string"`
}
type S3[T int | complex128] struct {
// flag, can't use ,string on complex128
F T `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S4[T int | string] struct {
// don't flag, pointers to stringable types are also stringable
F *T `json:",string"`
}
type S5[T ~int | ~string, PT ~int | ~string | ~*T] struct {
// don't flag, pointers to stringable types are also stringable
F PT `json:",string"`
}
type S6[T int | complex128] struct {
// flag, pointers to non-stringable types aren't stringable, either
F *T `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S7[T int | complex128, PT *T] struct {
// flag, pointers to non-stringable types aren't stringable, either
F PT `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S8[T int, PT *T | complex128] struct {
// do flag, variation of S7
F PT `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S9[T int | *bool, PT *T | float64, PPT *PT | string] struct {
// do flag, multiple levels of pointers aren't allowed
F PPT `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S10[T1 *T2, T2 *T1] struct {
// do flag, don't get stuck in an infinite loop
F T1 `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
type S11[E ~int | ~complex128, T ~*E] struct {
F T `json:",string"` //@ diag("invalid appearance of `string` tag option")
}
================================================
FILE: staticcheck/sa5009/sa5009.go
================================================
package sa5009
import (
"fmt"
"go/constant"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"honnef.co/go/tools/printf"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA5009",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Invalid Printf call`,
Since: "2019.2",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
// TODO(dh): detect printf wrappers
var rules = map[string]callcheck.Check{
"fmt.Errorf": func(call *callcheck.Call) { check(call, 0, 1) },
"fmt.Printf": func(call *callcheck.Call) { check(call, 0, 1) },
"fmt.Sprintf": func(call *callcheck.Call) { check(call, 0, 1) },
"fmt.Fprintf": func(call *callcheck.Call) { check(call, 1, 2) },
"golang.org/x/xerrors.Errorf": func(call *callcheck.Call) { check(call, 0, 1) },
}
type verbFlag int
const (
isInt verbFlag = 1 << iota
isBool
isFP
isString
isPointer
// Verbs that accept "pseudo pointers" will sometimes dereference
// non-nil pointers. For example, %x on a non-nil *struct will print the
// individual fields, but on a nil pointer it will print the address.
isPseudoPointer
isSlice
isAny
noRecurse
)
var verbs = [...]verbFlag{
'b': isPseudoPointer | isInt | isFP,
'c': isInt,
'd': isPseudoPointer | isInt,
'e': isFP,
'E': isFP,
'f': isFP,
'F': isFP,
'g': isFP,
'G': isFP,
'o': isPseudoPointer | isInt,
'O': isPseudoPointer | isInt,
'p': isSlice | isPointer | noRecurse,
'q': isInt | isString,
's': isString,
't': isBool,
'T': isAny,
'U': isInt,
'v': isAny,
'X': isPseudoPointer | isInt | isFP | isString,
'x': isPseudoPointer | isInt | isFP | isString,
}
func check(call *callcheck.Call, fIdx, vIdx int) {
f := call.Args[fIdx]
var args []ir.Value
switch v := call.Args[vIdx].Value.Value.(type) {
case *ir.Slice:
var ok bool
args, ok = irutil.Vararg(v)
if !ok {
// We don't know what the actual arguments to the function are
return
}
case *ir.Const:
// nil, i.e. no arguments
default:
// We don't know what the actual arguments to the function are
return
}
checkImpl(f, f.Value.Value, args)
}
func checkImpl(carg *callcheck.Argument, f ir.Value, args []ir.Value) {
var msCache *typeutil.MethodSetCache
if f.Parent() != nil {
msCache = &f.Parent().Prog.MethodSets
}
elem := func(T types.Type, verb rune) ([]types.Type, bool) {
if verbs[verb]&noRecurse != 0 {
return []types.Type{T}, false
}
switch T := T.(type) {
case *types.Slice:
if verbs[verb]&isSlice != 0 {
return []types.Type{T}, false
}
if verbs[verb]&isString != 0 && types.Identical(T.Elem().Underlying(), types.Typ[types.Byte]) {
return []types.Type{T}, false
}
return []types.Type{T.Elem()}, true
case *types.Map:
key := T.Key()
val := T.Elem()
return []types.Type{key, val}, true
case *types.Struct:
out := make([]types.Type, 0, T.NumFields())
for field := range T.Fields() {
out = append(out, field.Type())
}
return out, true
case *types.Array:
return []types.Type{T.Elem()}, true
default:
return []types.Type{T}, false
}
}
isInfo := func(T types.Type, info types.BasicInfo) bool {
basic, ok := T.Underlying().(*types.Basic)
return ok && basic.Info()&info != 0
}
isFormatter := func(T types.Type, ms *types.MethodSet) bool {
sel := ms.Lookup(nil, "Format")
if sel == nil {
return false
}
fn, ok := sel.Obj().(*types.Func)
if !ok {
// should be unreachable
return false
}
sig := fn.Type().(*types.Signature)
if sig.Params().Len() != 2 {
return false
}
// TODO(dh): check the types of the arguments for more
// precision
if sig.Results().Len() != 0 {
return false
}
return true
}
var seen typeutil.Map[struct{}]
var checkType func(verb rune, T types.Type, top bool) bool
checkType = func(verb rune, T types.Type, top bool) bool {
if top {
seen = typeutil.Map[struct{}]{}
}
if _, ok := seen.At(T); ok {
return true
}
seen.Set(T, struct{}{})
if int(verb) >= len(verbs) {
// Unknown verb
return true
}
flags := verbs[verb]
if flags == 0 {
// Unknown verb
return true
}
ms := msCache.MethodSet(T)
if isFormatter(T, ms) {
// the value is responsible for formatting itself
return true
}
if flags&isString != 0 && (types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"])) {
// Check for stringer early because we're about to dereference
return true
}
T = T.Underlying()
if flags&(isPointer|isPseudoPointer) == 0 && top {
T = typeutil.Dereference(T)
}
if flags&isPseudoPointer != 0 && top {
t := typeutil.Dereference(T)
if _, ok := t.Underlying().(*types.Struct); ok {
T = t
}
}
if _, ok := T.(*types.Interface); ok {
// We don't know what's in the interface
return true
}
var info types.BasicInfo
if flags&isInt != 0 {
info |= types.IsInteger
}
if flags&isBool != 0 {
info |= types.IsBoolean
}
if flags&isFP != 0 {
info |= types.IsFloat | types.IsComplex
}
if flags&isString != 0 {
info |= types.IsString
}
if info != 0 && isInfo(T, info) {
return true
}
if flags&isString != 0 {
isStringyElem := func(typ types.Type) bool {
if typ, ok := typ.Underlying().(*types.Basic); ok {
return typ.Kind() == types.Byte
}
return false
}
switch T := T.(type) {
case *types.Slice:
if isStringyElem(T.Elem()) {
return true
}
case *types.Array:
if isStringyElem(T.Elem()) {
return true
}
}
if types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"]) {
return true
}
}
if flags&isPointer != 0 && typeutil.IsPointerLike(T) {
return true
}
if flags&isPseudoPointer != 0 {
switch U := T.Underlying().(type) {
case *types.Pointer:
if !top {
return true
}
if _, ok := U.Elem().Underlying().(*types.Struct); !ok {
// TODO(dh): can this condition ever be false? For
// *T, if T is a struct, we'll already have
// dereferenced it, meaning the *types.Pointer
// branch couldn't have been taken. For T that
// aren't structs, this condition will always
// evaluate to true.
return true
}
case *types.Chan, *types.Signature:
// Channels and functions are always treated as
// pointers and never recursed into.
return true
case *types.Basic:
if U.Kind() == types.UnsafePointer {
return true
}
case *types.Interface:
// we will already have bailed if the type is an
// interface.
panic("unreachable")
default:
// other pointer-like types, such as maps or slices,
// will be printed element-wise.
}
}
if flags&isSlice != 0 {
if _, ok := T.(*types.Slice); ok {
return true
}
}
if flags&isAny != 0 {
return true
}
elems, ok := elem(T.Underlying(), verb)
if !ok {
return false
}
for _, elem := range elems {
if !checkType(verb, elem, false) {
return false
}
}
return true
}
k, ok := irutil.Flatten(f).(*ir.Const)
if !ok {
return
}
actions, err := printf.Parse(constant.StringVal(k.Value))
if err != nil {
carg.Invalid("couldn't parse format string")
return
}
ptr := 1
hasExplicit := false
checkStar := func(verb printf.Verb, star printf.Argument) bool {
if star, ok := star.(printf.Star); ok {
idx := 0
if star.Index == -1 {
idx = ptr
ptr++
} else {
hasExplicit = true
idx = star.Index
ptr = star.Index + 1
}
if idx == 0 {
carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
return false
}
if idx > len(args) {
carg.Invalid(
fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
verb.Raw, idx, len(args)))
return false
}
if arg, ok := args[idx-1].(*ir.MakeInterface); ok {
if !isInfo(arg.X.Type(), types.IsInteger) {
carg.Invalid(fmt.Sprintf("Printf format %s reads non-int arg #%d as argument of *", verb.Raw, idx))
}
}
}
return true
}
// We only report one problem per format string. Making a
// mistake with an index tends to invalidate all future
// implicit indices.
for _, action := range actions {
verb, ok := action.(printf.Verb)
if !ok {
continue
}
if !checkStar(verb, verb.Width) || !checkStar(verb, verb.Precision) {
return
}
off := ptr
if verb.Value != -1 {
hasExplicit = true
off = verb.Value
}
if off > len(args) {
carg.Invalid(
fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args",
verb.Raw, off, len(args)))
return
} else if verb.Value == 0 && verb.Letter != '%' {
carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw))
return
} else if off != 0 {
arg, ok := args[off-1].(*ir.MakeInterface)
if ok {
if !checkType(verb.Letter, arg.X.Type(), true) {
carg.Invalid(fmt.Sprintf("Printf format %s has arg #%d of wrong type %s",
verb.Raw, ptr, args[ptr-1].(*ir.MakeInterface).X.Type()))
return
}
}
}
switch verb.Value {
case -1:
// Consume next argument
ptr++
case 0:
// Don't consume any arguments
default:
ptr = verb.Value + 1
}
}
if !hasExplicit && ptr <= len(args) {
carg.Invalid(fmt.Sprintf("Printf call needs %d args but has %d args", ptr-1, len(args)))
}
}
================================================
FILE: staticcheck/sa5009/sa5009_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa5009
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa5009/testdata/go1.0/CheckPrintf/CheckPrintf.go
================================================
// Package pkg is amazing.
package pkg
import (
"fmt"
"math/big"
"os"
"unsafe"
)
type Error int
func (Error) Error() string { return "" }
func fn() {
var b bool
var i int
var r rune
var s string
var x float64
var p *int
var imap map[int]int
var fslice []float64
var c complex64
// Some good format/argtypes
fmt.Printf("")
fmt.Printf("%b %b %b", 3, i, x)
fmt.Printf("%c %c %c %c", 3, i, 'x', r)
fmt.Printf("%d %d %d", 3, i, imap)
fmt.Printf("%e %e %e %e", 3e9, x, fslice, c)
fmt.Printf("%E %E %E %E", 3e9, x, fslice, c)
fmt.Printf("%f %f %f %f", 3e9, x, fslice, c)
fmt.Printf("%F %F %F %F", 3e9, x, fslice, c)
fmt.Printf("%g %g %g %g", 3e9, x, fslice, c)
fmt.Printf("%G %G %G %G", 3e9, x, fslice, c)
fmt.Printf("%b %b %b %b", 3e9, x, fslice, c)
fmt.Printf("%o %o", 3, i)
fmt.Printf("%p", p)
fmt.Printf("%q %q %q %q", 3, i, 'x', r)
fmt.Printf("%s %s %s", "hi", s, []byte{65})
fmt.Printf("%t %t", true, b)
fmt.Printf("%T %T", 3, i)
fmt.Printf("%U %U", 3, i)
fmt.Printf("%v %v", 3, i)
fmt.Printf("%x %x %x %x", 3, i, "hi", s)
fmt.Printf("%X %X %X %X", 3, i, "hi", s)
fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3)
fmt.Printf("%s", &stringerv)
fmt.Printf("%v", &stringerv)
fmt.Printf("%T", &stringerv)
fmt.Printf("%s", &embeddedStringerv)
fmt.Printf("%v", &embeddedStringerv)
fmt.Printf("%T", &embeddedStringerv)
fmt.Printf("%v", notstringerv)
fmt.Printf("%T", notstringerv)
fmt.Printf("%q", stringerarrayv)
fmt.Printf("%v", stringerarrayv)
fmt.Printf("%s", stringerarrayv)
fmt.Printf("%v", notstringerarrayv)
fmt.Printf("%T", notstringerarrayv)
fmt.Printf("%d", new(fmt.Formatter))
fmt.Printf("%f", new(big.Float))
fmt.Printf("%*%", 2) // Ridiculous but allowed.
fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say.
fmt.Printf("%g", 1+2i)
fmt.Printf("%#e %#E %#f %#F %#g %#G", 1.2, 1.2, 1.2, 1.2, 1.2, 1.2) // OK since Go 1.9
// Some bad format/argTypes
fmt.Printf("%b", "hi") //@ diag(`Printf format %b has arg #1 of wrong type string`)
_ = fmt.Sprintf("%b", "hi") //@ diag(`Printf format %b has arg #1 of wrong type string`)
fmt.Fprintf(os.Stdout, "%b", "hi") //@ diag(`Printf format %b has arg #1 of wrong type string`)
fmt.Printf("%t", c) //@ diag(`Printf format %t has arg #1 of wrong type complex64`)
fmt.Printf("%t", 1+2i) //@ diag(`Printf format %t has arg #1 of wrong type complex128`)
fmt.Printf("%c", 2.3) //@ diag(`Printf format %c has arg #1 of wrong type float64`)
fmt.Printf("%d", 2.3) //@ diag(`Printf format %d has arg #1 of wrong type float64`)
fmt.Printf("%e", "hi") //@ diag(`Printf format %e has arg #1 of wrong type string`)
fmt.Printf("%E", true) //@ diag(`Printf format %E has arg #1 of wrong type bool`)
fmt.Printf("%f", "hi") //@ diag(`Printf format %f has arg #1 of wrong type string`)
fmt.Printf("%F", 'x') //@ diag(`Printf format %F has arg #1 of wrong type rune`)
fmt.Printf("%g", "hi") //@ diag(`Printf format %g has arg #1 of wrong type string`)
fmt.Printf("%g", imap) //@ diag(`Printf format %g has arg #1 of wrong type map[int]int`)
fmt.Printf("%G", i) //@ diag(`Printf format %G has arg #1 of wrong type int`)
fmt.Printf("%o", x) //@ diag(`Printf format %o has arg #1 of wrong type float64`)
fmt.Printf("%p", 23) //@ diag(`Printf format %p has arg #1 of wrong type int`)
fmt.Printf("%q", x) //@ diag(`Printf format %q has arg #1 of wrong type float64`)
fmt.Printf("%s", b) //@ diag(`Printf format %s has arg #1 of wrong type bool`)
fmt.Printf("%s", byte(65)) //@ diag(`Printf format %s has arg #1 of wrong type byte`)
fmt.Printf("%t", 23) //@ diag(`Printf format %t has arg #1 of wrong type int`)
fmt.Printf("%U", x) //@ diag(`Printf format %U has arg #1 of wrong type float64`)
fmt.Printf("%X", 2.3)
fmt.Printf("%X", 2+3i)
fmt.Printf("%s", stringerv) //@ diag(`Printf format %s has arg #1 of wrong type example.com/CheckPrintf.ptrStringer`)
fmt.Printf("%t", stringerv) //@ diag(`Printf format %t has arg #1 of wrong type example.com/CheckPrintf.ptrStringer`)
fmt.Printf("%s", embeddedStringerv) //@ diag(`Printf format %s has arg #1 of wrong type example.com/CheckPrintf.embeddedStringer`)
fmt.Printf("%t", embeddedStringerv) //@ diag(`Printf format %t has arg #1 of wrong type example.com/CheckPrintf.embeddedStringer`)
fmt.Printf("%q", notstringerv) //@ diag(`Printf format %q has arg #1 of wrong type example.com/CheckPrintf.notstringer`)
fmt.Printf("%t", notstringerv) //@ diag(`Printf format %t has arg #1 of wrong type example.com/CheckPrintf.notstringer`)
fmt.Printf("%t", stringerarrayv) //@ diag(`Printf format %t has arg #1 of wrong type example.com/CheckPrintf.stringerarray`)
fmt.Printf("%t", notstringerarrayv) //@ diag(`Printf format %t has arg #1 of wrong type example.com/CheckPrintf.notstringerarray`)
fmt.Printf("%q", notstringerarrayv) //@ diag(`Printf format %q has arg #1 of wrong type example.com/CheckPrintf.notstringerarray`)
fmt.Printf("%d", BoolFormatter(true)) //@ diag(`Printf format %d has arg #1 of wrong type example.com/CheckPrintf.BoolFormatter`)
fmt.Printf("%z", FormatterVal(true)) // correct (the type is responsible for formatting)
fmt.Printf("%d", FormatterVal(true)) // correct (the type is responsible for formatting)
fmt.Printf("%s", nonemptyinterface) // correct (the type is responsible for formatting)
fmt.Printf("%.*s %d %6g", 3, "hi", 23, 'x') //@ diag(`Printf format %6g has arg #4 of wrong type rune`)
fmt.Printf("%s", "hi", 3) //@ diag(`Printf call needs 1 args but has 2 args`)
fmt.Printf("%"+("s"), "hi", 3) //@ diag(`Printf call needs 1 args but has 2 args`)
fmt.Printf("%s%%%d", "hi", 3) // correct
fmt.Printf("%08s", "woo") // correct
fmt.Printf("% 8s", "woo") // correct
fmt.Printf("%.*d", 3, 3) // correct
fmt.Printf("%.*d x", 3, 3, 3, 3) //@ diag(`Printf call needs 2 args but has 4 args`)
fmt.Printf("%.*d x", "hi", 3) //@ diag(`Printf format %.*d reads non-int arg #1 as argument of *`)
fmt.Printf("%.*d x", i, 3) // correct
fmt.Printf("%.*d x", s, 3) //@ diag(`Printf format %.*d reads non-int arg #1 as argument of *`)
fmt.Printf("%*% x", 0.22) //@ diag(`Printf format %*% reads non-int arg #1 as argument of *`)
fmt.Printf("%q %q", multi()...) // ok
fmt.Printf("%#q", `blah`) // ok
const format = "%s %s\n"
fmt.Printf(format, "hi", "there")
fmt.Printf(format, "hi") //@ diag(`Printf format %s reads arg #2, but call has only 1 args`)
fmt.Printf("%s %d %.3v %q", "str", 4) //@ diag(`Printf format %.3v reads arg #3, but call has only 2 args`)
fmt.Printf("%#s", FormatterVal(true)) // correct (the type is responsible for formatting)
fmt.Printf("d%", 2) //@ diag(`couldn't parse format string`)
fmt.Printf("%d", percentDV)
fmt.Printf("%d", &percentDV)
fmt.Printf("%d", notPercentDV) //@ diag(`Printf format %d has arg #1 of wrong type example.com/CheckPrintf.notPercentDStruct`)
fmt.Printf("%d", ¬PercentDV) //@ diag(`Printf format %d has arg #1 of wrong type *example.com/CheckPrintf.notPercentDStruct`)
fmt.Printf("%p", ¬PercentDV) // Works regardless: we print it as a pointer.
fmt.Printf("%q", &percentDV) //@ diag(`Printf format %q has arg #1 of wrong type *example.com/CheckPrintf.percentDStruct`)
fmt.Printf("%s", percentSV)
fmt.Printf("%s", &percentSV)
// Good argument reorderings.
fmt.Printf("%[1]d", 3)
fmt.Printf("%[1]*d", 3, 1)
fmt.Printf("%[2]*[1]d", 1, 3)
fmt.Printf("%[2]*.[1]*[3]d", 2, 3, 4)
fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly.
// Bad argument reorderings.
fmt.Printf("%[xd", 3) //@ diag(`couldn't parse format string`)
fmt.Printf("%[x]d x", 3) //@ diag(`couldn't parse format string`)
fmt.Printf("%[3]*s x", "hi", 2) //@ diag(`Printf format %[3]*s reads arg #3, but call has only 2 args`)
fmt.Printf("%[3]d x", 2) //@ diag(`Printf format %[3]d reads arg #3, but call has only 1 args`)
fmt.Printf("%[2]*.[1]*[3]d x", 2, "hi", 4) //@ diag(`Printf format %[2]*.[1]*[3]d reads non-int arg #2 as argument of *`)
fmt.Printf("%[0]s x", "arg1") //@ diag(`Printf format %[0]s reads invalid arg 0; indices are 1-based`)
fmt.Printf("%[0]d x", 1) //@ diag(`Printf format %[0]d reads invalid arg 0; indices are 1-based`)
// Interfaces can be used with any verb.
var iface interface {
ToTheMadness() bool // Method ToTheMadness usually returns false
}
fmt.Printf("%f", iface) // ok: fmt treats interfaces as transparent and iface may well have a float concrete type
// Can print functions in many ways
fmt.Printf("%s", someFunction) //@ diag(`Printf format %s has arg #1 of wrong type func()`)
fmt.Printf("%d", someFunction) // ok: maybe someone wants to see the pointer
fmt.Printf("%v", someFunction) // ok: maybe someone wants to see the pointer in decimal
fmt.Printf("%p", someFunction) // ok: maybe someone wants to see the pointer
fmt.Printf("%T", someFunction) // ok: maybe someone wants to see the type
// Bug: used to recur forever.
fmt.Printf("%p %x", recursiveStructV, recursiveStructV.next)
fmt.Printf("%p %x", recursiveStruct1V, recursiveStruct1V.next)
fmt.Printf("%p %x", recursiveSliceV, recursiveSliceV)
//fmt.Printf("%p %x", recursiveMapV, recursiveMapV)
// indexed arguments
fmt.Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4) // OK
fmt.Printf("%d %[0]d %d %[2]d x", 1, 2, 3, 4) //@ diag(`Printf format %[0]d reads invalid arg 0; indices are 1-based`)
fmt.Printf("%d %[3]d %d %[-2]d x", 1, 2, 3, 4) //@ diag(`couldn't parse format string`)
fmt.Printf("%d %[3]d %d %[5]d x", 1, 2, 3, 4) //@ diag(`Printf format %[5]d reads arg #5, but call has only 4 args`)
fmt.Printf("%d %[3]d %-10d %[2]d x", 1, 2, 3) //@ diag(`Printf format %-10d reads arg #4, but call has only 3 args`)
fmt.Printf("%[1][3]d x", 1, 2) //@ diag(`couldn't parse format string`)
fmt.Printf("%[1]d x", 1, 2) // OK
fmt.Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4, 5) // OK
fmt.Printf(someString(), "hello") // OK
// d accepts pointers as long as they're not to structs.
// pointers to structs are dereferenced and walked.
fmt.Printf("%d", &s)
// staticcheck's own checks, based on bugs in go vet; see https://github.com/golang/go/issues/27672
{
type T2 struct {
X string
}
type T1 struct {
X *T2
}
x1 := []string{"hi"}
t1 := T1{&T2{"hi"}}
fmt.Printf("%s\n", &x1)
fmt.Printf("%s\n", t1) //@ diag(`Printf format %s has arg #1 of wrong type example.com/CheckPrintf.T1`)
var x2 struct{ A *int }
fmt.Printf("%p\n", x2) //@ diag(`Printf format %p has arg #1 of wrong type struct{A *int}`)
var x3 [2]int
fmt.Printf("%p", x3) //@ diag(`Printf format %p has arg #1 of wrong type [2]int`)
ue := unexportedError{nil}
fmt.Printf("%s", ue)
}
// staticcheck's own checks, based on our own bugs
fmt.Printf("%s", Error(0))
fmt.Printf("%x", unsafe.Pointer(uintptr(0)))
}
func someString() string { return "X" }
// A function we use as a function value; it has no other purpose.
func someFunction() {}
// multi is used by the test.
func multi() []interface{} {
return nil
}
type stringer int
func (stringer) String() string { return "string" }
type ptrStringer float64
var stringerv ptrStringer
func (*ptrStringer) String() string {
return "string"
}
type embeddedStringer struct {
foo string
ptrStringer
bar int
}
var embeddedStringerv embeddedStringer
type notstringer struct {
f float64
}
var notstringerv notstringer
type stringerarray [4]float64
func (stringerarray) String() string {
return "string"
}
var stringerarrayv stringerarray
type notstringerarray [4]float64
var notstringerarrayv notstringerarray
var nonemptyinterface = interface {
f()
}(nil)
// A data type we can print with "%d".
type percentDStruct struct {
a int
b []byte
c *float64
}
var percentDV percentDStruct
// A data type we cannot print correctly with "%d".
type notPercentDStruct struct {
a int
b []byte
c bool
}
var notPercentDV notPercentDStruct
// A data type we can print with "%s".
type percentSStruct struct {
a string
b []byte
C stringerarray
}
var percentSV percentSStruct
type BoolFormatter bool
func (*BoolFormatter) Format(fmt.State, rune) {
}
// Formatter with value receiver
type FormatterVal bool
func (FormatterVal) Format(fmt.State, rune) {
}
type RecursiveSlice []RecursiveSlice
var recursiveSliceV = &RecursiveSlice{}
type RecursiveMap map[int]RecursiveMap
var recursiveMapV = make(RecursiveMap)
type RecursiveStruct struct {
next *RecursiveStruct
}
var recursiveStructV = &RecursiveStruct{}
type RecursiveStruct1 struct {
next *RecursiveStruct2
}
type RecursiveStruct2 struct {
next *RecursiveStruct1
}
var recursiveStruct1V = &RecursiveStruct1{}
type unexportedInterface struct {
f interface{}
}
// Issue 17798: unexported ptrStringer cannot be formatted.
type unexportedStringer struct {
t ptrStringer
}
type unexportedStringerOtherFields struct {
s string
t ptrStringer
S string
}
// Issue 17798: unexported error cannot be formatted.
type unexportedError struct {
e error
}
type unexportedErrorOtherFields struct {
s string
e error
S string
}
type errorer struct{}
func (e errorer) Error() string { return "errorer" }
type unexportedCustomError struct {
e errorer
}
type errorInterface interface {
error
ExtraMethod()
}
type unexportedErrorInterface struct {
e errorInterface
}
func UnexportedStringerOrError() {
fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo}
fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem
us := unexportedStringer{}
fmt.Printf("%s", us) //@ diag(`Printf format %s has arg #1 of wrong type example.com/CheckPrintf.unexportedStringer`)
fmt.Printf("%s", &us) //@ diag(`Printf format %s has arg #1 of wrong type *example.com/CheckPrintf.unexportedStringer`)
usf := unexportedStringerOtherFields{
s: "foo",
S: "bar",
}
fmt.Printf("%s", usf) //@ diag(`Printf format %s has arg #1 of wrong type example.com/CheckPrintf.unexportedStringerOtherFields`)
fmt.Printf("%s", &usf) //@ diag(`Printf format %s has arg #1 of wrong type *example.com/CheckPrintf.unexportedStringerOtherFields`)
intSlice := []int{3, 4}
fmt.Printf("%s", intSlice) //@ diag(`Printf format %s has arg #1 of wrong type []int`)
nonStringerArray := [1]unexportedStringer{{}}
fmt.Printf("%s", nonStringerArray) //@ diag(`Printf format %s has arg #1 of wrong type [1]example.com/CheckPrintf.unexportedStringer`)
fmt.Printf("%s", []stringer{3, 4}) // not an error
fmt.Printf("%s", [2]stringer{3, 4}) // not an error
}
// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
// See issues 23598 and 23605.
func DisableErrorForFlag0() {
fmt.Printf("%0t", true)
}
// Issue 26486.
func dbg(format string, args ...interface{}) {
if format == "" {
format = "%v"
}
fmt.Printf(format, args...)
}
// https://github.com/dominikh/go-tools/issues/714
func fn2() {
type String string
type Byte byte
var a string = "a"
var b []byte = []byte{'b'}
var c [1]byte = [1]byte{'c'}
var d String = "d"
var e []uint8 = []uint8{'e'}
var f []Byte = []Byte{'h'}
fmt.Printf("%s %s %s %s %s %s %s", a, b, c, &c, d, e, f)
}
func fn3() {
s := "%d"
if true {
fmt.Printf(s, "") //@ diag(`Printf format`)
} else {
_ = s
}
}
================================================
FILE: staticcheck/sa5010/sa5010.go
================================================
package sa5010
import (
"fmt"
"go/types"
"strings"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA5010",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Impossible type assertion`,
Text: `Some type assertions can be statically proven to be
impossible. This is the case when the method sets of both
arguments of the type assertion conflict with each other, for
example by containing the same method with different
signatures.
The Go compiler already applies this check when asserting from an
interface value to a concrete type. If the concrete type misses
methods from the interface, or if function signatures don't match,
then the type assertion can never succeed.
This check applies the same logic when asserting from one interface to
another. If both interface types contain the same method but with
different signatures, then the type assertion can never succeed,
either.`,
Since: "2020.1",
Severity: lint.SeverityWarning,
// Technically this should be MergeIfAll, but the Go compiler
// already flags some impossible type assertions, so
// MergeIfAny is consistent with the compiler.
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
type entry struct {
l, r *types.Func
}
msc := &pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg.Prog.MethodSets
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, b := range fn.Blocks {
instrLoop:
for _, instr := range b.Instrs {
assert, ok := instr.(*ir.TypeAssert)
if !ok {
continue
}
var wrong []entry
left := assert.X.Type()
right := assert.AssertedType
righti, ok := right.Underlying().(*types.Interface)
if !ok {
// We only care about interface->interface
// assertions. The Go compiler already catches
// impossible interface->concrete assertions.
continue
}
ms := msc.MethodSet(left)
for mr := range righti.Methods() {
sel := ms.Lookup(mr.Pkg(), mr.Name())
if sel == nil {
continue
}
ml := sel.Obj().(*types.Func)
if ml.Origin() != ml || mr.Origin() != mr {
// Give up when we see generics.
//
// TODO(dh): support generics once go/types gets an
// exported API for type unification.
continue instrLoop
}
if types.AssignableTo(ml.Type(), mr.Type()) {
continue
}
wrong = append(wrong, entry{ml, mr})
}
if len(wrong) != 0 {
var s strings.Builder
s.WriteString(fmt.Sprintf("impossible type assertion; %s and %s contradict each other:",
types.TypeString(left, types.RelativeTo(pass.Pkg)),
types.TypeString(right, types.RelativeTo(pass.Pkg))))
for _, e := range wrong {
s.WriteString(fmt.Sprintf("\n\twrong type for %s method", e.l.Name()))
s.WriteString(fmt.Sprintf("\n\t\thave %s", e.l.Type()))
s.WriteString(fmt.Sprintf("\n\t\twant %s", e.r.Type()))
}
report.Report(pass, assert, s.String())
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa5010/sa5010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa5010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa5010/testdata/go1.0/CheckImpossibleTypeAssertion/CheckImpossibleTypeAssertion.go
================================================
package pkg
import "fmt"
type i1 interface {
String() int
}
type i2 interface {
String() string
}
type i3 interface {
bar() int
}
type i4 interface {
String() int
bar() int
}
func fn() {
var v1 i1
_ = v1.(i2) //@ diag(`impossible type assertion; i1 and i2 contradict each other`)
_ = v1.(i3)
_ = v1.(i4)
_ = v1.(fmt.Stringer) //@ diag(`impossible type assertion; i1 and fmt.Stringer contradict each other`)
_ = v1.(interface { //@ diag(re`i1 and.+String.+contradict each other`)
String() string
})
}
================================================
FILE: staticcheck/sa5010/testdata/go1.18/CheckImpossibleTypeAssertion/CheckImpossibleTypeAssertion.go
================================================
package pkg
type ExampleType[T uint32 | uint64] interface {
SomeMethod() T
}
func Fn1[T uint32 | uint64]() {
var iface ExampleType[uint32]
_ = iface.(ExampleType[T])
}
func Fn2[T uint64]() {
var iface ExampleType[uint32]
// TODO(dh): once we support generics, flag this
_ = iface.(ExampleType[T])
}
type I1[E any] interface {
Do(E)
Moo(E)
}
type I2[E any] interface {
Do(E)
Moo(E)
}
type I3[E any] interface {
Do(E)
Moo()
}
func New[T any]() {
var x I1[T]
_ = x.(I2[T])
// TODO(dh): once we support generics, flag this
_ = x.(I3[T])
}
================================================
FILE: staticcheck/sa5011/sa5011.go
================================================
package sa5011
import (
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA5011",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Possible nil pointer dereference`,
Text: `A pointer is being dereferenced unconditionally, while
also being checked against nil in another place. This suggests that
the pointer may be nil and dereferencing it may panic. This is
commonly a result of improperly ordered code or missing return
statements. Consider the following examples:
func fn(x *int) {
fmt.Println(*x)
// This nil check is equally important for the previous dereference
if x != nil {
foo(*x)
}
}
func TestFoo(t *testing.T) {
x := compute()
if x == nil {
t.Errorf("nil pointer received")
}
// t.Errorf does not abort the test, so if x is nil, the next line will panic.
foo(*x)
}
Staticcheck tries to deduce which functions abort control flow.
For example, it is aware that a function will not continue
execution after a call to \'panic\' or \'log.Fatal\'. However, sometimes
this detection fails, in particular in the presence of
conditionals. Consider the following example:
func Log(msg string, level int) {
fmt.Println(msg)
if level == levelFatal {
os.Exit(1)
}
}
func Fatal(msg string) {
Log(msg, levelFatal)
}
func fn(x *int) {
if x == nil {
Fatal("unexpected nil pointer")
}
fmt.Println(*x)
}
Staticcheck will flag the dereference of \'x\', even though it is perfectly
safe. Staticcheck is not able to deduce that a call to
Fatal will exit the program. For the time being, the easiest
workaround is to modify the definition of Fatal like so:
func Fatal(msg string) {
Log(msg, levelFatal)
panic("unreachable")
}
We also hard-code functions from common logging packages such as
logrus. Please file an issue if we're missing support for a
popular package.`,
Since: "2020.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// This is an extremely trivial check that doesn't try to reason
// about control flow. That is, phis and sigmas do not propagate
// any information. As such, we can flag this:
//
// _ = *x
// if x == nil { return }
//
// but we cannot flag this:
//
// if x == nil { println(x) }
// _ = *x
//
// but we can flag this, because the if's body doesn't use x:
//
// if x == nil { println("this is bad") }
// _ = *x
//
// nor many other variations of conditional uses of or assignments to x.
//
// However, even this trivial implementation finds plenty of
// real-world bugs, such as dereference before nil pointer check,
// or using t.Error instead of t.Fatal when encountering nil
// pointers.
//
// On the flip side, our naive implementation avoids false positives in branches, such as
//
// if x != nil { _ = *x }
//
// due to the same lack of propagating information through sigma
// nodes. x inside the branch will be independent of the x in the
// nil pointer check.
//
//
// We could implement a more powerful check, but then we'd be
// getting false positives instead of false negatives because
// we're incapable of deducing relationships between variables.
// For example, a function might return a pointer and an error,
// and the error being nil guarantees that the pointer is not nil.
// Depending on the surrounding code, the pointer may still end up
// being checked against nil in one place, and guarded by a check
// on the error in another, which would lead to us marking some
// loads as unsafe.
//
// Unfortunately, simply hard-coding the relationship between
// return values wouldn't eliminate all false positives, either.
// Many other more subtle relationships exist. An abridged example
// from real code:
//
// if a == nil && b == nil { return }
// c := fn(a)
// if c != "" { _ = *a }
//
// where `fn` is guaranteed to return a non-empty string if a
// isn't nil.
//
// We choose to err on the side of false negatives.
isNilConst := func(v ir.Value) bool {
if typeutil.IsPointerLike(v.Type()) {
if k, ok := v.(*ir.Const); ok {
return k.IsNil()
}
}
return false
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
maybeNil := map[ir.Value]ir.Instruction{}
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
// Originally we looked at all ir.BinOp, but that would lead to calls like 'assert(x != nil)' causing false positives.
// Restrict ourselves to actual if statements, as these are more likely to affect control flow in a way we can observe.
if instr, ok := instr.(*ir.If); ok {
if cond, ok := instr.Cond.(*ir.BinOp); ok {
if isNilConst(cond.X) {
maybeNil[cond.Y] = cond
}
if isNilConst(cond.Y) {
maybeNil[cond.X] = cond
}
}
}
}
}
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
var ptr ir.Value
switch instr := instr.(type) {
case *ir.Load:
ptr = instr.X
case *ir.Store:
ptr = instr.Addr
case *ir.IndexAddr:
ptr = instr.X
if typeutil.All(ptr.Type(), func(term *types.Term) bool {
if _, ok := term.Type().Underlying().(*types.Slice); ok {
return true
}
return false
}) {
// indexing a nil slice does not cause a nil pointer panic
//
// Note: This also works around the bad lowering of range loops over slices
// (https://github.com/dominikh/go-tools/issues/1053)
continue
}
case *ir.FieldAddr:
ptr = instr.X
}
if ptr != nil {
switch ptr.(type) {
case *ir.Alloc, *ir.FieldAddr, *ir.IndexAddr:
// these cannot be nil
continue
}
if r, ok := maybeNil[ptr]; ok {
report.Report(pass, instr, "possible nil pointer dereference",
report.Related(r, "this check suggests that the pointer can be nil"))
}
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa5011/sa5011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa5011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa5011/testdata/go1.0/CheckMaybeNil/CheckMaybeNil.go
================================================
package pkg
import (
"os"
"syscall"
)
func fn1(x *int) {
_ = *x //@ diag(`possible nil pointer dereference`)
if x != nil {
return
}
println()
}
func fn1_1(x *int) {
// this doesn't get flagged because the conditional return gets optimized away
_ = *x
if x != nil {
return
}
}
func fn2(x *int) {
if x == nil {
println("we should return")
}
_ = *x //@ diag(`possible nil pointer dereference`)
}
func fn3(x *int) {
if x != nil {
_ = *x
}
}
func fn4(x *int) {
if x == nil {
x = gen()
}
_ = *x
}
func fn5(x *int) {
if x == nil {
x = gen()
}
_ = *x //@ diag(`possible nil pointer dereference`)
if x == nil {
println("we should return")
}
}
func fn6() {
x := new(int)
if x == nil {
println("we should return")
}
// x can't be nil
_ = *x
}
func fn7() {
var x int
y := &x
if y == nil {
println("we should return")
}
// y can't be nil
_ = *y
}
func fn8(x *int) {
if x == nil {
return
}
// x can't be nil
_ = *x
}
func fn9(x *int) {
if x != nil {
return
}
// TODO(dh): not currently supported
_ = *x
}
func gen() *int { return nil }
func die1(b bool) {
if b {
println("yay")
os.Exit(0)
} else {
println("nay")
os.Exit(1)
}
}
func die2(b bool) {
if b {
println("yay")
os.Exit(0)
}
}
func fn10(x *int) {
if x == nil {
die1(true)
}
_ = *x
}
func fn11(x *int) {
if x == nil {
die2(true)
}
_ = *x //@ diag(`possible nil pointer dereference`)
}
func doPanic() { panic("") }
func doExit() { syscall.Exit(1) }
func fn12(arg bool) {
if arg {
doPanic()
} else {
doExit()
}
}
func fn13(arg bool) {
fn12(arg)
}
func fn14(x *int) {
if x == nil {
fn13(true)
}
_ = *x
}
func assert(b bool) {
if b {
panic("meh")
}
}
func fn15(x *int) {
assert(x != nil)
_ = *x
}
func fn16() {
var xs []int
if xs == nil {
println()
}
for _, x := range xs {
_ = x
}
var xs2 *[1]int
if xs2 == nil {
println()
}
// this used to get flagged, but now that we correctly insert sigma nodes for range loops, we can no longer flag this
for _, x := range xs2 {
_ = x
}
var xs3 *[]int
if xs3 == nil {
println()
}
for _, x := range *xs3 { //@ diag(`possible nil pointer dereference`)
_ = x
}
var xs4 []int
if xs4 == nil {
println()
}
_ = xs4[0]
}
================================================
FILE: staticcheck/sa5011/testdata/go1.18/CheckMaybeNil/generics.go
================================================
package pkg
func tpfn1[T []int](x T) {
// don't flag, T is a slice
_ = x[0]
if x == nil {
return
}
println()
}
func tpfn2[T *int,](x T) {
_ = *x //@ diag(`possible nil pointer dereference`)
if x == nil {
return
}
println()
}
================================================
FILE: staticcheck/sa5012/sa5012.go
================================================
package sa5012
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA5012",
Run: run,
FactTypes: []analysis.Fact{new(evenElements)},
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Passing odd-sized slice to function expecting even size",
Text: `Some functions that take slices as parameters expect the slices to have an even number of elements.
Often, these functions treat elements in a slice as pairs.
For example, \'strings.NewReplacer\' takes pairs of old and new strings,
and calling it with an odd number of elements would be an error.`,
Since: "2020.2",
Severity: lint.SeverityError,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
type evenElements struct{}
func (evenElements) AFact() {}
func (evenElements) String() string { return "needs even elements" }
func findSliceLength(v ir.Value) int {
// TODO(dh): VRP would help here
v = irutil.Flatten(v)
val := func(v ir.Value) int {
if v, ok := v.(*ir.Const); ok {
return int(v.Int64())
}
return -1
}
switch v := v.(type) {
case *ir.Slice:
low := 0
high := -1
if v.Low != nil {
low = val(v.Low)
}
if v.High != nil {
high = val(v.High)
} else {
switch vv := v.X.(type) {
case *ir.Alloc:
high = int(typeutil.Dereference(vv.Type()).Underlying().(*types.Array).Len())
case *ir.Slice:
high = findSliceLength(vv)
}
}
if low == -1 || high == -1 {
return -1
}
return high - low
default:
return -1
}
}
func flagSliceLens(pass *analysis.Pass) {
var tag evenElements
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
call, ok := instr.(ir.CallInstruction)
if !ok {
continue
}
callee := call.Common().StaticCallee()
if callee == nil {
continue
}
for argi, arg := range call.Common().Args {
if callee.Signature.Recv() != nil {
if argi == 0 {
continue
}
argi--
}
_, ok := arg.Type().Underlying().(*types.Slice)
if !ok {
continue
}
param := callee.Signature.Params().At(argi)
if !pass.ImportObjectFact(param, &tag) {
continue
}
// TODO handle stubs
// we know the argument has to have even length.
// now let's try to find its length
if n := findSliceLength(arg); n > -1 && n%2 != 0 {
src := call.Source().(*ast.CallExpr).Args[argi]
sig := call.Common().Signature()
var label string
if argi == sig.Params().Len()-1 && sig.Variadic() {
label = "variadic argument"
} else {
label = "argument"
}
// Note that param.Name() is guaranteed to not
// be empty, otherwise the function couldn't
// have enforced its length.
report.Report(pass, src, fmt.Sprintf("%s %q is expected to have even number of elements, but has %d elements", label, param.Name(), n))
}
}
}
}
}
}
func findSliceLenChecks(pass *analysis.Pass) {
// mark all function parameters that have to be of even length
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, b := range fn.Blocks {
// all paths go through this block
if !b.Dominates(fn.Exit) {
continue
}
// if foo % 2 != 0
ifi, ok := b.Control().(*ir.If)
if !ok {
continue
}
cmp, ok := ifi.Cond.(*ir.BinOp)
if !ok {
continue
}
var needle uint64
switch cmp.Op {
case token.NEQ:
// look for != 0
needle = 0
case token.EQL:
// look for == 1
needle = 1
default:
continue
}
rem, ok1 := cmp.X.(*ir.BinOp)
k, ok2 := cmp.Y.(*ir.Const)
if ok1 != ok2 {
continue
}
if !ok1 {
rem, ok1 = cmp.Y.(*ir.BinOp)
k, ok2 = cmp.X.(*ir.Const)
}
if !ok1 || !ok2 || rem.Op != token.REM || k.Value.Kind() != constant.Int || k.Uint64() != needle {
continue
}
k, ok = rem.Y.(*ir.Const)
if !ok || k.Value.Kind() != constant.Int || k.Uint64() != 2 {
continue
}
// if len(foo) % 2 != 0
call, ok := rem.X.(*ir.Call)
if !ok || !irutil.IsCallTo(call.Common(), "len") {
continue
}
// we're checking the length of a parameter that is a slice
// TODO(dh): support parameters that have flown through sigmas and phis
param, ok := call.Call.Args[0].(*ir.Parameter)
if !ok {
continue
}
if !typeutil.All(param.Type(), typeutil.IsSlice) {
continue
}
// if len(foo) % 2 != 0 then panic
if _, ok := b.Succs[0].Control().(*ir.Panic); !ok {
continue
}
pass.ExportObjectFact(param.Object(), new(evenElements))
}
}
}
func findIndirectSliceLenChecks(pass *analysis.Pass) {
seen := map[*ir.Function]struct{}{}
var doFunction func(fn *ir.Function)
doFunction = func(fn *ir.Function) {
if _, ok := seen[fn]; ok {
return
}
seen[fn] = struct{}{}
for _, b := range fn.Blocks {
// all paths go through this block
if !b.Dominates(fn.Exit) {
continue
}
for _, instr := range b.Instrs {
call, ok := instr.(*ir.Call)
if !ok {
continue
}
callee := call.Call.StaticCallee()
if callee == nil {
continue
}
if callee.Pkg == fn.Pkg || callee.Pkg == nil {
doFunction(callee)
}
for argi, arg := range call.Call.Args {
if callee.Signature.Recv() != nil {
if argi == 0 {
continue
}
argi--
}
// TODO(dh): support parameters that have flown through length-preserving instructions
param, ok := arg.(*ir.Parameter)
if !ok {
continue
}
if !typeutil.All(param.Type(), typeutil.IsSlice) {
continue
}
// We can't use callee.Params to look up the
// parameter, because Params is not populated for
// external functions. In our modular analysis.
// any function in any package that isn't the
// current package is considered "external", as it
// has been loaded from export data only.
sigParams := callee.Signature.Params()
if !pass.ImportObjectFact(sigParams.At(argi), new(evenElements)) {
continue
}
pass.ExportObjectFact(param.Object(), new(evenElements))
}
}
}
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
doFunction(fn)
}
}
func run(pass *analysis.Pass) (any, error) {
findSliceLenChecks(pass)
findIndirectSliceLenChecks(pass)
flagSliceLens(pass)
return nil, nil
}
================================================
FILE: staticcheck/sa5012/sa5012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa5012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa5012/testdata/go1.0/CheckEvenSliceLength/CheckEvenSliceLength.go
================================================
package pkg
import "strings"
func fnVariadic(s string, args ...interface{}) { //@ fact(args, "needs even elements")
if len(args)%2 != 0 {
panic("I'm one of those annoying logging APIs")
}
}
func fnSlice(s string, args []interface{}) { //@ fact(args, "needs even elements")
if len(args)%2 != 0 {
panic("I'm one of those annoying logging APIs")
}
}
func fnIndirect(s string, args ...interface{}) { //@ fact(args, "needs even elements")
fnSlice(s, args)
}
func fn2(bleh []interface{}, arr1 [3]interface{}) { //@ fact(bleh, "needs even elements")
fnVariadic("%s", 1, 2, 3) //@ diag(re`variadic argument "args".+ but has 3 elements`)
args := []interface{}{1, 2, 3}
fnVariadic("", args...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnVariadic("", args[:1]...) //@ diag(re`variadic argument "args".+ but has 1 elements`)
fnVariadic("", args[:2]...)
fnVariadic("", args[0:1]...) //@ diag(re`variadic argument "args".+ but has 1 elements`)
fnVariadic("", args[0:]...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnVariadic("", args[:]...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnVariadic("", bleh...)
fnVariadic("", bleh[:1]...) //@ diag(re`variadic argument "args".+ but has 1 elements`)
fnVariadic("", bleh[0:1]...) //@ diag(re`variadic argument "args".+ but has 1 elements`)
fnVariadic("", bleh[0:]...)
fnVariadic("", bleh[:]...)
fnVariadic("", bleh) //@ diag(re`variadic argument "args".+ but has 1 elements`)
fnVariadic("", make([]interface{}, 3)...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnVariadic("", make([]interface{}, 4)...)
var arr2 [3]interface{}
fnVariadic("", arr1[:]...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnVariadic("", arr2[:]...) //@ diag(re`variadic argument "args".+ but has 3 elements`)
fnSlice("", []interface{}{1, 2, 3}) //@ diag(re`argument "args".+ but has 3 elements`)
fnSlice("", []interface{}{1, 2, 3, 4})
fnIndirect("%s", 1, 2, 3) //@ diag(re`argument "args".+ but has 3 elements`)
fnIndirect("%s", 1, 2)
strings.NewReplacer("one") //@ diag(re`variadic argument "oldnew".+ but has 1 elements`)
strings.NewReplacer("one", "two")
}
func fn3() {
args := []interface{}{""}
if true {
fnSlice("", args) //@ diag(`but has 1 element`)
}
}
================================================
FILE: staticcheck/sa6000/sa6000.go
================================================
package sa6000
import (
"fmt"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6000",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Using \'regexp.Match\' or related in a loop, should use \'regexp.Compile\'`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"regexp.Match": check("regexp.Match"),
"regexp.MatchReader": check("regexp.MatchReader"),
"regexp.MatchString": check("regexp.MatchString"),
}
func check(name string) callcheck.Check {
return func(call *callcheck.Call) {
if callcheck.ExtractConst(call.Args[0].Value) == nil {
return
}
if !isInLoop(call.Instr.Block()) {
return
}
call.Invalid(fmt.Sprintf("calling %s in a loop has poor performance, consider using regexp.Compile", name))
}
}
func isInLoop(b *ir.BasicBlock) bool {
sets := irutil.FindLoops(b.Parent())
for _, set := range sets {
if set.Has(b) {
return true
}
}
return false
}
================================================
FILE: staticcheck/sa6000/sa6000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6000/testdata/go1.0/CheckRegexpMatchLoop/CheckRegexpMatchLoop.go
================================================
package pkg
import "regexp"
func fn() {
regexp.Match(".", nil)
regexp.MatchString(".", "")
regexp.MatchReader(".", nil)
for {
regexp.Match(".", nil) //@ diag(`calling regexp.Match in a loop has poor performance`)
regexp.MatchString(".", "") //@ diag(`calling regexp.MatchString in a loop has poor performance`)
regexp.MatchReader(".", nil) //@ diag(`calling regexp.MatchReader in a loop has poor performance`)
}
}
================================================
FILE: staticcheck/sa6001/sa6001.go
================================================
package sa6001
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6001",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Missing an optimization opportunity when indexing maps by byte slices`,
Text: `Map keys must be comparable, which precludes the use of byte slices.
This usually leads to using string keys and converting byte slices to
strings.
Normally, a conversion of a byte slice to a string needs to copy the data and
causes allocations. The compiler, however, recognizes \'m[string(b)]\' and
uses the data of \'b\' directly, without copying it, because it knows that
the data can't change during the map lookup. This leads to the
counter-intuitive situation that
k := string(b)
println(m[k])
println(m[k])
will be less efficient than
println(m[string(b)])
println(m[string(b)])
because the first version needs to copy and allocate, while the second
one does not.
For some history on this optimization, check out commit
f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, b := range fn.Blocks {
insLoop:
for _, ins := range b.Instrs {
var fromType types.Type
var toType types.Type
// find []byte -> string conversions
switch ins := ins.(type) {
case *ir.Convert:
fromType = ins.X.Type()
toType = ins.Type()
case *ir.MultiConvert:
fromType = ins.X.Type()
toType = ins.Type()
default:
continue
}
if toType != types.Universe.Lookup("string").Type() {
continue
}
tset := typeutil.NewTypeSet(fromType)
// If at least one of the types is []byte, then it's more efficient to inline the conversion
if !tset.Any(func(term *types.Term) bool {
s, ok := term.Type().Underlying().(*types.Slice)
return ok && s.Elem().Underlying() == types.Universe.Lookup("byte").Type()
}) {
continue
}
refs := ins.Referrers()
// need at least two (DebugRef) references: the
// conversion and the *ast.Ident
if refs == nil || len(*refs) < 2 {
continue
}
ident := false
// skip first reference, that's the conversion itself
for _, ref := range (*refs)[1:] {
switch ref := ref.(type) {
case *ir.DebugRef:
if _, ok := ref.Expr.(*ast.Ident); !ok {
// the string seems to be used somewhere
// unexpected; the default branch should
// catch this already, but be safe
continue insLoop
} else {
ident = true
}
case *ir.MapLookup:
default:
// the string is used somewhere else than a
// map lookup
continue insLoop
}
}
// the result of the conversion wasn't assigned to an
// identifier
if !ident {
continue
}
report.Report(pass, ins, "m[string(key)] would be more efficient than k := string(key); m[k]")
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa6001/sa6001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6001/testdata/go1.0/CheckMapBytesKey/key.go
================================================
package pkg
func fn() {
var m map[string]int
var b []byte
_ = m[string(b)]
_ = m[string(b)]
s1 := string(b) //@ diag(`m[string(key)] would be more efficient than k := string(key); m[k]`)
_ = m[s1]
_ = m[s1]
s2 := string(b)
_ = m[s2]
_ = m[s2]
println(s2)
}
================================================
FILE: staticcheck/sa6001/testdata/go1.18/CheckMapBytesKey/key_generics.go
================================================
package pkg
func tpfn[T ~string | []byte | int](b T) {
var m map[string]int
k := string(b) //@ diag(`would be more efficient`)
_ = m[k]
}
================================================
FILE: staticcheck/sa6002/sa6002.go
================================================
package sa6002
import (
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6002",
Requires: []*analysis.Analyzer{buildir.Analyzer},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Storing non-pointer values in \'sync.Pool\' allocates memory`,
Text: `A \'sync.Pool\' is used to avoid unnecessary allocations and reduce the
amount of work the garbage collector has to do.
When passing a value that is not a pointer to a function that accepts
an interface, the value needs to be placed on the heap, which means an
additional allocation. Slices are a common thing to put in sync.Pools,
and they're structs with 3 fields (length, capacity, and a pointer to
an array). In order to avoid the extra allocation, one should store a
pointer to the slice instead.
See the comments on https://go-review.googlesource.com/c/go/+/24371
that discuss this problem.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
"(*sync.Pool).Put": func(call *callcheck.Call) {
arg := call.Args[knowledge.Arg("(*sync.Pool).Put.x")]
typ := arg.Value.Value.Type()
_, isSlice := typ.Underlying().(*types.Slice)
if !typeutil.IsPointerLike(typ) || isSlice {
arg.Invalid("argument should be pointer-like to avoid allocations")
}
},
}
================================================
FILE: staticcheck/sa6002/sa6002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6002/testdata/go1.0/CheckSyncPoolValue/CheckSyncPoolValue.go
================================================
package pkg
import (
"sync"
"unsafe"
)
type T1 struct {
x int
}
type T2 struct {
x int
y int
}
func fn() {
s := []int{}
v := sync.Pool{}
v.Put(s) //@ diag(`argument should be pointer-like`)
v.Put(&s)
v.Put(T1{}) //@ diag(`argument should be pointer-like`)
v.Put(T2{}) //@ diag(`argument should be pointer-like`)
p := &sync.Pool{}
p.Put(s) //@ diag(`argument should be pointer-like`)
p.Put(&s)
var i interface{}
p.Put(i)
var up unsafe.Pointer
p.Put(up)
var basic int
p.Put(basic) //@ diag(`argument should be pointer-like`)
}
func fn2() {
// https://github.com/dominikh/go-tools/issues/873
var pool sync.Pool
func() {
defer pool.Put([]byte{}) //@ diag(`argument should be pointer-like`)
}()
}
func fn3() {
var pool sync.Pool
defer pool.Put([]byte{}) //@ diag(`argument should be pointer-like`)
go pool.Put([]byte{}) //@ diag(`argument should be pointer-like`)
}
================================================
FILE: staticcheck/sa6003/sa6003.go
================================================
package sa6003
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/internal/sharedcheck"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6003",
Run: sharedcheck.CheckRangeStringRunes,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Converting a string to a slice of runes before ranging over it`,
Text: `You may want to loop over the runes in a string. Instead of converting
the string to a slice of runes and looping over that, you can loop
over the string itself. That is,
for _, r := range s {}
and
for _, r := range []rune(s) {}
will yield the same values. The first version, however, will be faster
and avoid unnecessary memory allocations.
Do note that if you are interested in the indices, ranging over a
string and over a slice of runes will yield different indices. The
first one yields byte offsets, while the second one yields indices in
the slice of runes.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
================================================
FILE: staticcheck/sa6003/sa6003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6003/testdata/go1.0/CheckRangeStringRunes/CheckRangeStringRunes.go
================================================
package pkg
type String string
func fn(s string, s2 String) {
for _, r := range s {
println(r)
}
for _, r := range []rune(s) { //@ diag(`should range over string`)
println(r)
}
for i, r := range []rune(s) {
println(i)
println(r)
}
x := []rune(s)
for _, r := range x { //@ diag(`should range over string`)
println(r)
}
y := []rune(s)
for _, r := range y {
println(r)
}
println(y[0])
for _, r := range []rune(s2) { //@ diag(`should range over string`)
println(r)
}
}
================================================
FILE: staticcheck/sa6003/testdata/go1.18/CheckRangeStringRunes/generics.go
================================================
package pkg
func tpfn1[T string](x T) {
for _, c := range []rune(x) { //@ diag(`should range over string`)
println(c)
}
}
func tpfn2[T1 string, T2 []rune](x T1) {
for _, c := range T2(x) { //@ diag(`should range over string`)
println(c)
}
}
================================================
FILE: staticcheck/sa6005/sa6005.go
================================================
package sa6005
import (
"fmt"
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6005",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Inefficient string comparison with \'strings.ToLower\' or \'strings.ToUpper\'`,
Text: `Converting two strings to the same case and comparing them like so
if strings.ToLower(s1) == strings.ToLower(s2) {
...
}
is significantly more expensive than comparing them with
\'strings.EqualFold(s1, s2)\'. This is due to memory usage as well as
computational complexity.
\'strings.ToLower\' will have to allocate memory for the new strings, as
well as convert both strings fully, even if they differ on the very
first byte. strings.EqualFold, on the other hand, compares the strings
one character at a time. It doesn't need to create two intermediate
strings and can return as soon as the first non-matching character has
been found.
For a more in-depth explanation of this issue, see
https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkToLowerToUpperComparisonQ = pattern.MustParse(`
(BinaryExpr
(CallExpr fun@(Symbol (Or "strings.ToLower" "strings.ToUpper")) [a])
tok@(Or "==" "!=")
(CallExpr fun [b]))`)
checkToLowerToUpperComparisonR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b])`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkToLowerToUpperComparisonQ) {
rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr)
method := "strings.EqualFold"
if m.State["tok"].(token.Token) == token.NEQ {
rn = &ast.UnaryExpr{
Op: token.NOT,
X: rn,
}
method = "!" + method
}
report.Report(pass, node,
fmt.Sprintf("should use %s instead", method),
report.Fixes(edit.Fix("replace with "+method, edit.ReplaceWithNode(pass.Fset, node, rn))))
}
return nil, nil
}
================================================
FILE: staticcheck/sa6005/sa6005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6005/testdata/go1.0/CheckToLowerToUpperComparison/CheckToLowerToUpperComparison.go
================================================
package pkg
import "strings"
func fn() {
const (
s1 = "foo"
s2 = "bar"
)
if strings.ToLower(s1) == strings.ToLower(s2) { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if strings.ToUpper(s1) == strings.ToUpper(s2) { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if strings.ToLower(s1) != strings.ToLower(s2) { //@ diag(`should use !strings.EqualFold instead`)
panic("")
}
switch strings.ToLower(s1) == strings.ToLower(s2) { //@ diag(`should use strings.EqualFold instead`)
case true, false:
panic("")
}
if strings.ToLower(s1) == strings.ToLower(s2) || s1+s2 == s2+s1 { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if strings.ToLower(s1) > strings.ToLower(s2) {
panic("")
}
if strings.ToLower(s1) < strings.ToLower(s2) {
panic("")
}
if strings.ToLower(s1) == strings.ToUpper(s2) {
panic("")
}
}
================================================
FILE: staticcheck/sa6005/testdata/go1.0/CheckToLowerToUpperComparison/CheckToLowerToUpperComparison.go.golden
================================================
package pkg
import "strings"
func fn() {
const (
s1 = "foo"
s2 = "bar"
)
if strings.EqualFold(s1, s2) { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if strings.EqualFold(s1, s2) { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if !strings.EqualFold(s1, s2) { //@ diag(`should use !strings.EqualFold instead`)
panic("")
}
switch strings.EqualFold(s1, s2) { //@ diag(`should use strings.EqualFold instead`)
case true, false:
panic("")
}
if strings.EqualFold(s1, s2) || s1+s2 == s2+s1 { //@ diag(`should use strings.EqualFold instead`)
panic("")
}
if strings.ToLower(s1) > strings.ToLower(s2) {
panic("")
}
if strings.ToLower(s1) < strings.ToLower(s2) {
panic("")
}
if strings.ToLower(s1) == strings.ToUpper(s2) {
panic("")
}
}
================================================
FILE: staticcheck/sa6006/sa6006.go
================================================
package sa6006
import (
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA6006",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Using io.WriteString to write \'[]byte\'`,
Text: `Using io.WriteString to write a slice of bytes, as in
io.WriteString(w, string(b))
is both unnecessary and inefficient. Converting from \'[]byte\' to \'string\'
has to allocate and copy the data, and we could simply use \'w.Write(b)\'
instead.`,
Since: "2024.1",
},
})
var Analyzer = SCAnalyzer.Analyzer
var ioWriteStringConversion = pattern.MustParse(`(CallExpr (Symbol "io.WriteString") [_ (CallExpr (Builtin "string") [arg])])`)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, ioWriteStringConversion) {
if !code.IsOfStringConvertibleByteSlice(pass, m.State["arg"].(ast.Expr)) {
continue
}
report.Report(pass, node, "use io.Writer.Write instead of converting from []byte to string to use io.WriteString")
}
return nil, nil
}
================================================
FILE: staticcheck/sa6006/sa6006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa6006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa6006/testdata/go1.0/CheckByteSliceInIOWriteString/CheckByteSliceInIOWriteString.go
================================================
package pkg
import (
"io"
)
func f() {
var b []byte
io.WriteString(nil, string(b)) //@ diag(`use io.Writer.Write`)
type custom []byte
var c custom
io.WriteString(nil, string(c)) //@ diag(`use io.Writer.Write`)
g := func() []byte { return nil }
io.WriteString(nil, string(g())) //@ diag(`use io.Writer.Write`)
var d string
io.WriteString(nil, d)
io.WriteString(nil, string(123))
string := func(x []byte) string { return "" }
io.WriteString(nil, string(b))
}
================================================
FILE: staticcheck/sa9001/sa9001.go
================================================
package sa9001
import (
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9001",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Defers in range loops may not run when you expect them to`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
loop := node.(*ast.RangeStmt)
typ := pass.TypesInfo.TypeOf(loop.X)
_, ok := typeutil.CoreType(typ).(*types.Chan)
if !ok {
return
}
stmts := []*ast.DeferStmt{}
exits := false
fn2 := func(node ast.Node) bool {
switch stmt := node.(type) {
case *ast.DeferStmt:
stmts = append(stmts, stmt)
case *ast.FuncLit:
// Don't look into function bodies
return false
case *ast.ReturnStmt:
exits = true
case *ast.BranchStmt:
exits = node.(*ast.BranchStmt).Tok == token.BREAK
}
return true
}
ast.Inspect(loop.Body, fn2)
if exits {
return
}
for _, stmt := range stmts {
report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed")
}
}
code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa9001/sa9001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9001/testdata/go1.0/CheckDubiousDeferInChannelRangeLoop/CheckDubiousDeferInChannelRangeLoop.go
================================================
package pkg
func fn() {
var ch chan int
for range ch {
defer println() //@ diag(`defers in this range loop`)
}
}
func fn2() {
var ch chan int
for range ch {
defer println()
break
}
for range ch {
defer println()
return
}
}
================================================
FILE: staticcheck/sa9001/testdata/go1.18/CheckDubiousDeferInChannelRangeLoop/generics.go
================================================
package pkg
func tpfn[T chan int]() {
var ch T
for range ch {
defer println() //@ diag(`defers in this range loop`)
}
}
================================================
FILE: staticcheck/sa9002/sa9002.go
================================================
package sa9002
import (
"fmt"
"go/ast"
"strconv"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9002",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Using a non-octal \'os.FileMode\' that looks like it was meant to be in octal.`,
Since: "2017.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
call := node.(*ast.CallExpr)
for _, arg := range call.Args {
lit, ok := arg.(*ast.BasicLit)
if !ok {
continue
}
if !typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "os.FileMode") &&
!typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "io/fs.FileMode") {
continue
}
if len(lit.Value) == 3 &&
lit.Value[0] != '0' &&
lit.Value[0] >= '0' && lit.Value[0] <= '7' &&
lit.Value[1] >= '0' && lit.Value[1] <= '7' &&
lit.Value[2] >= '0' && lit.Value[2] <= '7' {
v, err := strconv.ParseInt(lit.Value, 10, 64)
if err != nil {
continue
}
report.Report(pass, arg, fmt.Sprintf("file mode '%s' evaluates to %#o; did you mean '0%s'?", lit.Value, v, lit.Value),
report.Fixes(edit.Fix("Fix octal literal", edit.ReplaceWithString(arg, "0"+lit.Value))))
}
}
}
code.Preorder(pass, fn, (*ast.CallExpr)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa9002/sa9002_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9002
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9002/testdata/go1.0/CheckNonOctalFileMode/CheckNonOctalFileMode.go
================================================
package pkg
import "os"
func fn() {
os.OpenFile("", 0, 644) //@ diag(`file mode`)
}
func fn2() (string, int, os.FileMode) {
return "", 0, 0
}
func fn3() {
os.OpenFile(fn2())
}
================================================
FILE: staticcheck/sa9002/testdata/go1.0/CheckNonOctalFileMode/CheckNonOctalFileMode.go.golden
================================================
package pkg
import "os"
func fn() {
os.OpenFile("", 0, 0644) //@ diag(`file mode`)
}
func fn2() (string, int, os.FileMode) {
return "", 0, 0
}
func fn3() {
os.OpenFile(fn2())
}
================================================
FILE: staticcheck/sa9003/sa9003.go
================================================
package sa9003
import (
"go/ast"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9003",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Empty body in an if or else branch`,
Since: "2017.1",
NonDefault: true,
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if fn.Source() == nil {
continue
}
if irutil.IsExample(fn) {
continue
}
cb := func(node ast.Node) bool {
ifstmt, ok := node.(*ast.IfStmt)
if !ok {
return true
}
if ifstmt.Else != nil {
b, ok := ifstmt.Else.(*ast.BlockStmt)
if !ok || len(b.List) != 0 {
return true
}
report.Report(pass, ifstmt.Else, "empty branch", report.FilterGenerated(), report.ShortRange())
}
if len(ifstmt.Body.List) != 0 {
return true
}
report.Report(pass, ifstmt, "empty branch", report.FilterGenerated(), report.ShortRange())
return true
}
if source := fn.Source(); source != nil {
ast.Inspect(source, cb)
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa9003/sa9003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9003/testdata/go1.0/CheckEmptyBranch/CheckEmptyBranch.go
================================================
package pkg
func fn1() {
if true { //@ diag(`empty branch`)
}
if true { //@ diag(`empty branch`)
} else { //@ diag(`empty branch`)
}
if true {
println()
}
if true {
println()
} else { //@ diag(`empty branch`)
}
if true { //@ diag(`empty branch`)
// TODO handle error
}
if true {
} else {
println()
}
if true {
} else if false { //@ diag(`empty branch`)
}
}
================================================
FILE: staticcheck/sa9003/testdata/go1.0/CheckEmptyBranch/CheckEmptyBranch_generated.go
================================================
// Code generated by a human. DO NOT EDIT.
package pkg
func fn2() {
if true {
}
}
================================================
FILE: staticcheck/sa9003/testdata/go1.0/CheckEmptyBranch/CheckEmptyBranch_test.go
================================================
package pkg
import "testing"
func TestFoo(t *testing.T) {
if true { //@ diag(`empty branch`)
// TODO
}
}
func ExampleFoo() {
if true {
// TODO
}
}
================================================
FILE: staticcheck/sa9004/sa9004.go
================================================
package sa9004
import (
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9004",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Only the first constant has an explicit type`,
Text: `In a constant declaration such as the following:
const (
First byte = 1
Second = 2
)
the constant Second does not have the same type as the constant First.
This construct shouldn't be confused with
const (
First byte = iota
Second
)
where \'First\' and \'Second\' do indeed have the same type. The type is only
passed on when no explicit value is assigned to the constant.
When declaring enumerations with explicit values it is therefore
important not to write
const (
EnumFirst EnumType = 1
EnumSecond = 2
EnumThird = 3
)
This discrepancy in types can cause various confusing behaviors and
bugs.
Wrong type in variable declarations
The most obvious issue with such incorrect enumerations expresses
itself as a compile error:
package pkg
const (
EnumFirst uint8 = 1
EnumSecond = 2
)
func fn(useFirst bool) {
x := EnumSecond
if useFirst {
x = EnumFirst
}
}
fails to compile with
./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
Losing method sets
A more subtle issue occurs with types that have methods and optional
interfaces. Consider the following:
package main
import "fmt"
type Enum int
func (e Enum) String() string {
return "an enum"
}
const (
EnumFirst Enum = 1
EnumSecond = 2
)
func main() {
fmt.Println(EnumFirst)
fmt.Println(EnumSecond)
}
This code will output
an enum
2
as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`,
Since: "2019.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
decl := node.(*ast.GenDecl)
if !decl.Lparen.IsValid() {
return
}
if decl.Tok != token.CONST {
return
}
groups := astutil.GroupSpecs(pass.Fset, decl.Specs)
groupLoop:
for _, group := range groups {
if len(group) < 2 {
continue
}
if group[0].(*ast.ValueSpec).Type == nil {
// first constant doesn't have a type
continue groupLoop
}
firstType := pass.TypesInfo.TypeOf(group[0].(*ast.ValueSpec).Values[0])
for i, spec := range group {
spec := spec.(*ast.ValueSpec)
if i > 0 && spec.Type != nil {
continue groupLoop
}
if len(spec.Names) != 1 || len(spec.Values) != 1 {
continue groupLoop
}
if !types.ConvertibleTo(pass.TypesInfo.TypeOf(spec.Values[0]), firstType) {
continue groupLoop
}
switch v := spec.Values[0].(type) {
case *ast.BasicLit:
case *ast.UnaryExpr:
if _, ok := v.X.(*ast.BasicLit); !ok {
continue groupLoop
}
default:
// if it's not a literal it might be typed, such as
// time.Microsecond = 1000 * Nanosecond
continue groupLoop
}
}
var edits []analysis.TextEdit
typ := group[0].(*ast.ValueSpec).Type
for _, spec := range group[1:] {
nspec := *spec.(*ast.ValueSpec)
nspec.Type = typ
// The position of `spec` node excludes comments (if any).
// However, on generating the source back from the node, the comments are included. Setting `Comment` to nil ensures deduplication of comments.
nspec.Comment = nil
edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec))
}
report.Report(pass, group[0],
"only the first constant in this group has an explicit type",
report.Fixes(edit.Fix("Add type to all constants in group", edits...)))
}
}
code.Preorder(pass, fn, (*ast.GenDecl)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa9004/sa9004_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9004
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9004/testdata/go1.0/CheckMissingEnumTypesInDeclaration/CheckMissingEnumTypesInDeclaration.go
================================================
package pkg
const c1 int = 0
const c2 = 0
const (
c3 int = iota
c4
c5
)
const (
c6 int = 1 //@ diag(`only the first constant in this group has an explicit type`)
c7 = 2 // comment for testing https://github.com/dominikh/go-tools/issues/866
c8 = 3
)
const (
c9 int = 1
c10 = 2
c11 = 3
c12 int = 4
)
const (
c13 = 1
c14 int = 2
c15 int = 3
c16 int = 4
)
const (
c17 = 1
c18 int = 2
c19 = 3
c20 int = 4
)
const (
c21 int = 1
c22 = 2
)
const (
c23 int = 1
c24 int = 2
c25 string = "" //@ diag(`only the first constant in this group has an explicit type`)
c26 = ""
c27 = 1
c28 int = 2
c29 int = 1
c30 = 2
c31 int = 2
c32 string = "" //@ diag(`only the first constant in this group has an explicit type`)
c33 = ""
)
const (
c34 int = 1 //@ diag(`only the first constant in this group has an explicit type`)
c35 = 2
c36 int = 2
)
const (
c37 int = 1
c38 = "2"
)
const (
c39 int8 = 1.0 //@ diag(`only the first constant in this group has an explicit type`)
c40 = 'a'
c41 = 3
)
type String string
const (
c42 String = "" //@ diag(`only the first constant in this group has an explicit type`)
c43 = ""
)
================================================
FILE: staticcheck/sa9004/testdata/go1.0/CheckMissingEnumTypesInDeclaration/CheckMissingEnumTypesInDeclaration.go.golden
================================================
package pkg
const c1 int = 0
const c2 = 0
const (
c3 int = iota
c4
c5
)
const (
c6 int = 1 //@ diag(`only the first constant in this group has an explicit type`)
c7 int = 2 // comment for testing https://github.com/dominikh/go-tools/issues/866
c8 int = 3
)
const (
c9 int = 1
c10 = 2
c11 = 3
c12 int = 4
)
const (
c13 = 1
c14 int = 2
c15 int = 3
c16 int = 4
)
const (
c17 = 1
c18 int = 2
c19 = 3
c20 int = 4
)
const (
c21 int = 1
c22 = 2
)
const (
c23 int = 1
c24 int = 2
c25 string = "" //@ diag(`only the first constant in this group has an explicit type`)
c26 string = ""
c27 = 1
c28 int = 2
c29 int = 1
c30 = 2
c31 int = 2
c32 string = "" //@ diag(`only the first constant in this group has an explicit type`)
c33 string = ""
)
const (
c34 int = 1 //@ diag(`only the first constant in this group has an explicit type`)
c35 int = 2
c36 int = 2
)
const (
c37 int = 1
c38 = "2"
)
const (
c39 int8 = 1.0 //@ diag(`only the first constant in this group has an explicit type`)
c40 int8 = 'a'
c41 int8 = 3
)
type String string
const (
c42 String = "" //@ diag(`only the first constant in this group has an explicit type`)
c43 String = ""
)
================================================
FILE: staticcheck/sa9005/sa9005.go
================================================
package sa9005
import (
"fmt"
"go/types"
"honnef.co/go/tools/analysis/callcheck"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/knowledge"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9005",
Requires: []*analysis.Analyzer{
buildir.Analyzer,
// Filtering generated code because it may include empty structs generated from data models.
generated.Analyzer,
},
Run: callcheck.Analyzer(rules),
},
Doc: &lint.RawDocumentation{
Title: `Trying to marshal a struct with no public fields nor custom marshaling`,
Text: `
The \'encoding/json\' and \'encoding/xml\' packages only operate on exported
fields in structs, not unexported ones. It is usually an error to try
to (un)marshal structs that only consist of unexported fields.
This check will not flag calls involving types that define custom
marshaling behavior, e.g. via \'MarshalJSON\' methods. It will also not
flag empty structs.`,
Since: "2019.2",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var rules = map[string]callcheck.Check{
// TODO(dh): should we really flag XML? Even an empty struct
// produces a non-zero amount of data, namely its type name.
// Let's see if we encounter any false positives.
//
// Also, should we flag gob?
"encoding/json.Marshal": check(knowledge.Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"),
"encoding/xml.Marshal": check(knowledge.Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"),
"(*encoding/json.Encoder).Encode": check(knowledge.Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"),
"(*encoding/xml.Encoder).Encode": check(knowledge.Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"),
"encoding/json.Unmarshal": check(knowledge.Arg("json.Unmarshal.v"), "UnmarshalJSON", "UnmarshalText"),
"encoding/xml.Unmarshal": check(knowledge.Arg("xml.Unmarshal.v"), "UnmarshalXML", "UnmarshalText"),
"(*encoding/json.Decoder).Decode": check(knowledge.Arg("(*encoding/json.Decoder).Decode.v"), "UnmarshalJSON", "UnmarshalText"),
"(*encoding/xml.Decoder).Decode": check(knowledge.Arg("(*encoding/xml.Decoder).Decode.v"), "UnmarshalXML", "UnmarshalText"),
}
func check(argN int, meths ...string) callcheck.Check {
return func(call *callcheck.Call) {
if code.IsGenerated(call.Pass, call.Instr.Pos()) {
return
}
arg := call.Args[argN]
T := arg.Value.Value.Type()
Ts, ok := typeutil.Dereference(T).Underlying().(*types.Struct)
if !ok {
return
}
if Ts.NumFields() == 0 {
return
}
fields := typeutil.FlattenFields(Ts)
for _, field := range fields {
if field.Var.Exported() {
return
}
}
// OPT(dh): we could use a method set cache here
ms := call.Instr.Parent().Prog.MethodSets.MethodSet(T)
// TODO(dh): we're not checking the signature, which can cause false negatives.
// This isn't a huge problem, however, since vet complains about incorrect signatures.
for _, meth := range meths {
if ms.Lookup(nil, meth) != nil {
return
}
}
arg.Invalid(fmt.Sprintf("struct type '%s' doesn't have any exported fields, nor custom marshaling", typeutil.Dereference(T)))
}
}
================================================
FILE: staticcheck/sa9005/sa9005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9005/testdata/go1.0/CheckNoopMarshal/CheckNoopMarshal.go
================================================
package pkg
import (
"encoding/json"
"encoding/xml"
)
type T1 struct{}
type T2 struct{ x int }
type T3 struct{ X int }
type T4 struct{ T3 }
type t5 struct{ X int }
type T6 struct{ t5 }
type T7 struct{ x int }
func (T7) MarshalJSON() ([]byte, error) { return nil, nil }
func (*T7) UnmarshalJSON([]byte) error { return nil }
type T8 struct{ x int }
func (T8) MarshalXML() ([]byte, error) { return nil, nil }
func (*T8) UnmarshalXML(*xml.Decoder, *xml.StartElement) error { return nil }
type T9 struct{}
func (T9) MarshalText() ([]byte, error) { return nil, nil }
func (*T9) UnmarshalText([]byte) error { return nil }
type T10 struct{}
type T11 struct{ T10 }
type T12 struct{ T7 }
type t13 struct{}
func (t13) MarshalJSON() ([]byte, error) { return nil, nil }
type T14 struct{ t13 }
type T15 struct{ *t13 }
type T16 struct{ *T3 }
type T17 struct{ *T17 }
type T18 struct {
T17
Actual int
}
type t19 string
type T20 string
type T21 struct{ t19 }
type T22 struct{ T20 }
type T23 struct{ *T20 }
func fn() {
// don't flag structs with no fields
json.Marshal(T1{})
// no exported fields
json.Marshal(T2{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
// pointer vs non-pointer makes no difference
json.Marshal(&T2{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
// exported field
json.Marshal(T3{})
// exported field, pointer makes no difference
json.Marshal(&T3{})
// embeds struct with exported fields
json.Marshal(T4{})
// exported field
json.Marshal(t5{})
// embeds unexported type, but said type does have exported fields
json.Marshal(T6{})
// MarshalJSON
json.Marshal(T7{})
// MarshalXML does not apply to JSON
json.Marshal(T8{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T8' doesn't have any exported fields, nor custom marshaling`)
// MarshalText
json.Marshal(T9{})
// embeds exported struct, but it has no fields
json.Marshal(T11{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T11' doesn't have any exported fields, nor custom marshaling`)
// embeds type with MarshalJSON
json.Marshal(T12{})
// embeds type with MarshalJSON and type isn't exported
json.Marshal(T14{})
// embedded pointer with MarshalJSON
json.Marshal(T15{})
// embedded pointer to struct with exported fields
json.Marshal(T16{})
// don't recurse forever on recursive data structure
json.Marshal(T17{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T17' doesn't have any exported fields, nor custom marshaling`)
json.Marshal(T18{})
// embedding an unexported, non-struct type
json.Marshal(T21{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T21' doesn't have any exported fields, nor custom marshaling`)
// embedding an exported, non-struct type
json.Marshal(T22{})
// embedding an exported, non-struct type
json.Marshal(T23{})
// MarshalJSON does not apply to XML
xml.Marshal(T7{}) //@ diag(`struct type 'example.com/CheckNoopMarshal.T7' doesn't have any exported fields, nor custom marshaling`)
// MarshalXML
xml.Marshal(T8{})
var t2 T2
var t3 T3
var t7 T7
var t8 T8
var t9 T9
// check that all other variations of methods also work
json.Unmarshal(nil, &t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
json.Unmarshal(nil, &t3)
json.Unmarshal(nil, &t9)
xml.Unmarshal(nil, &t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
xml.Unmarshal(nil, &t3)
xml.Unmarshal(nil, &t9)
(*json.Decoder)(nil).Decode(&t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
(*json.Decoder)(nil).Decode(&t3)
(*json.Decoder)(nil).Decode(&t9)
(*json.Encoder)(nil).Encode(t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
(*json.Encoder)(nil).Encode(t3)
(*json.Encoder)(nil).Encode(t9)
(*xml.Decoder)(nil).Decode(&t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
(*xml.Decoder)(nil).Decode(&t3)
(*xml.Decoder)(nil).Decode(&t9)
(*xml.Encoder)(nil).Encode(t2) //@ diag(`struct type 'example.com/CheckNoopMarshal.T2' doesn't have any exported fields, nor custom marshaling`)
(*xml.Encoder)(nil).Encode(t3)
(*xml.Encoder)(nil).Encode(t9)
(*json.Decoder)(nil).Decode(&t7)
(*json.Decoder)(nil).Decode(&t8) //@ diag(`struct type 'example.com/CheckNoopMarshal.T8' doesn't have any exported fields, nor custom marshaling`)
(*json.Encoder)(nil).Encode(t7)
(*json.Encoder)(nil).Encode(t8) //@ diag(`struct type 'example.com/CheckNoopMarshal.T8' doesn't have any exported fields, nor custom marshaling`)
(*xml.Decoder)(nil).Decode(&t7) //@ diag(`struct type 'example.com/CheckNoopMarshal.T7' doesn't have any exported fields, nor custom marshaling`)
(*xml.Decoder)(nil).Decode(&t8)
(*xml.Encoder)(nil).Encode(t7) //@ diag(`struct type 'example.com/CheckNoopMarshal.T7' doesn't have any exported fields, nor custom marshaling`)
(*xml.Encoder)(nil).Encode(t8)
}
var _, _ = json.Marshal(T9{})
================================================
FILE: staticcheck/sa9006/sa9006.go
================================================
package sa9006
import (
"fmt"
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9006",
Run: run,
Requires: code.RequiredAnalyzers,
},
Doc: &lint.RawDocumentation{
Title: `Dubious bit shifting of a fixed size integer value`,
Text: `Bit shifting a value past its size will always clear the value.
For instance:
v := int8(42)
v >>= 8
will always result in 0.
This check flags bit shifting operations on fixed size integer values only.
That is, int, uint and uintptr are never flagged to avoid potential false
positives in somewhat exotic but valid bit twiddling tricks:
// Clear any value above 32 bits if integers are more than 32 bits.
func f(i int) int {
v := i >> 32
v = v << 32
return i-v
}`,
Since: "2020.2",
Severity: lint.SeverityWarning,
// Technically this should be MergeIfAll, because the type of
// v might be different for different build tags. Practically,
// don't write code that depends on that.
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkFixedLengthTypeShiftQ = pattern.MustParse(`
(Or
(AssignStmt _ (Or ">>=" "<<=") _)
(BinaryExpr _ (Or ">>" "<<") _))
`)
)
func run(pass *analysis.Pass) (any, error) {
isDubiousShift := func(x, y ast.Expr) (int64, int64, bool) {
typ, ok := pass.TypesInfo.TypeOf(x).Underlying().(*types.Basic)
if !ok {
return 0, 0, false
}
switch typ.Kind() {
case types.Int8, types.Int16, types.Int32, types.Int64,
types.Uint8, types.Uint16, types.Uint32, types.Uint64:
// We're only interested in fixed–size types.
default:
return 0, 0, false
}
const bitsInByte = 8
typeBits := pass.TypesSizes.Sizeof(typ) * bitsInByte
shiftLength, ok := code.ExprToInt(pass, y)
if !ok {
return 0, 0, false
}
return typeBits, shiftLength, shiftLength >= typeBits
}
for node := range code.Matches(pass, checkFixedLengthTypeShiftQ) {
switch e := node.(type) {
case *ast.AssignStmt:
if size, shift, yes := isDubiousShift(e.Lhs[0], e.Rhs[0]); yes {
report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift))
}
case *ast.BinaryExpr:
if size, shift, yes := isDubiousShift(e.X, e.Y); yes {
report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift))
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa9006/sa9006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9006/testdata/go1.0/CheckStaticBitShift/CheckStaticBitShift.go
================================================
package pkg
// Partially copied from go vet's test suite.
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE-THIRD-PARTY file.
type Number int8
func fn() {
var n8 Number
n8 <<= 8 //@ diag(`will always clear it`)
var i8 int8
_ = i8 << 7
_ = (i8 + 1) << 8 //@ diag(`will always clear it`)
_ = i8 << (7 + 1) //@ diag(`will always clear it`)
_ = i8 >> 8 //@ diag(`will always clear it`)
i8 <<= 8 //@ diag(`will always clear it`)
i8 >>= 8 //@ diag(`will always clear it`)
i8 <<= 12 //@ diag(`will always clear it`)
var i16 int16
_ = i16 << 15
_ = i16 << 16 //@ diag(`will always clear it`)
_ = i16 >> 16 //@ diag(`will always clear it`)
i16 <<= 16 //@ diag(`will always clear it`)
i16 >>= 16 //@ diag(`will always clear it`)
i16 <<= 18 //@ diag(`will always clear it`)
var i32 int32
_ = i32 << 31
_ = i32 << 32 //@ diag(`will always clear it`)
_ = i32 >> 32 //@ diag(`will always clear it`)
i32 <<= 32 //@ diag(`will always clear it`)
i32 >>= 32 //@ diag(`will always clear it`)
i32 <<= 40 //@ diag(`will always clear it`)
var i64 int64
_ = i64 << 63
_ = i64 << 64 //@ diag(`will always clear it`)
_ = i64 >> 64 //@ diag(`will always clear it`)
i64 <<= 64 //@ diag(`will always clear it`)
i64 >>= 64 //@ diag(`will always clear it`)
i64 <<= 70 //@ diag(`will always clear it`)
var u8 uint8
_ = u8 << 7
_ = u8 << 8 //@ diag(`will always clear it`)
_ = u8 >> 8 //@ diag(`will always clear it`)
u8 <<= 8 //@ diag(`will always clear it`)
u8 >>= 8 //@ diag(`will always clear it`)
u8 <<= 12 //@ diag(`will always clear it`)
var u16 uint16
_ = u16 << 15
_ = u16 << 16 //@ diag(`will always clear it`)
_ = u16 >> 16 //@ diag(`will always clear it`)
u16 <<= 16 //@ diag(`will always clear it`)
u16 >>= 16 //@ diag(`will always clear it`)
u16 <<= 18 //@ diag(`will always clear it`)
var u32 uint32
_ = u32 << 31
_ = u32 << 32 //@ diag(`will always clear it`)
_ = u32 >> 32 //@ diag(`will always clear it`)
u32 <<= 32 //@ diag(`will always clear it`)
u32 >>= 32 //@ diag(`will always clear it`)
u32 <<= 40 //@ diag(`will always clear it`)
var u64 uint64
_ = u64 << 63
_ = u64 << 64 //@ diag(`will always clear it`)
_ = u64 >> 64 //@ diag(`will always clear it`)
u64 <<= 64 //@ diag(`will always clear it`)
u64 >>= 64 //@ diag(`will always clear it`)
u64 <<= 70 //@ diag(`will always clear it`)
_ = u64 << u64
}
func fn1() {
var ui uint
_ = ui << 64
_ = ui >> 64
ui <<= 64
ui >>= 64
var uptr uintptr
_ = uptr << 64
_ = uptr >> 64
uptr <<= 64
uptr >>= 64
var i int
_ = i << 64
_ = i >> 64
i <<= 64
i >>= 64
}
================================================
FILE: staticcheck/sa9007/sa9007.go
================================================
package sa9007
import (
"fmt"
"os"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9007",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Deleting a directory that shouldn't be deleted",
Text: `
It is virtually never correct to delete system directories such as
/tmp or the user's home directory. However, it can be fairly easy to
do by mistake, for example by mistakenly using \'os.TempDir\' instead
of \'ioutil.TempDir\', or by forgetting to add a suffix to the result
of \'os.UserHomeDir\'.
Writing
d := os.TempDir()
defer os.RemoveAll(d)
in your unit tests will have a devastating effect on the stability of your system.
This check flags attempts at deleting the following directories:
- os.TempDir
- os.UserCacheDir
- os.UserConfigDir
- os.UserHomeDir
`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
call, ok := instr.(ir.CallInstruction)
if !ok {
continue
}
if !irutil.IsCallTo(call.Common(), "os.RemoveAll") {
continue
}
kind := ""
ex := ""
callName := ""
arg := irutil.Flatten(call.Common().Args[0])
switch arg := arg.(type) {
case *ir.Call:
callName = irutil.CallName(&arg.Call)
if callName != "os.TempDir" {
continue
}
kind = "temporary"
ex = os.TempDir()
case *ir.Extract:
if arg.Index != 0 {
continue
}
first, ok := arg.Tuple.(*ir.Call)
if !ok {
continue
}
callName = irutil.CallName(&first.Call)
switch callName {
case "os.UserCacheDir":
kind = "cache"
ex, _ = os.UserCacheDir()
case "os.UserConfigDir":
kind = "config"
ex, _ = os.UserConfigDir()
case "os.UserHomeDir":
kind = "home"
ex, _ = os.UserHomeDir()
default:
continue
}
default:
continue
}
if ex == "" {
report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind),
report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory", callName, kind)))
} else {
report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind),
report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory, for example %s", callName, kind, ex)))
}
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa9007/sa9007_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9007
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9007/testdata/go1.0/CheckBadRemoveAll/CheckBadRemoveAll.go
================================================
package pkg
import (
"os"
"path/filepath"
)
func fn1() {
x := os.TempDir()
defer os.RemoveAll(x) //@ diag(`deletes the user's entire temporary directory`)
x = ""
}
func fn2() {
x := os.TempDir()
if true {
os.RemoveAll(x) //@ diag(`deletes the user's entire temporary directory`)
}
}
func fn3() {
x := os.TempDir()
if true {
x = filepath.Join(x, "foo")
}
// we don't flag this to avoid false positives in infeasible paths
os.RemoveAll(x)
}
func fn4() {
x, _ := os.UserCacheDir()
os.RemoveAll(x) //@ diag(`deletes the user's entire cache directory`)
x, _ = os.UserConfigDir()
os.RemoveAll(x) //@ diag(`deletes the user's entire config directory`)
x, _ = os.UserHomeDir()
os.RemoveAll(x) //@ diag(`deletes the user's entire home directory`)
}
================================================
FILE: staticcheck/sa9008/sa9008.go
================================================
package sa9008
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9008",
Run: run,
Requires: append([]*analysis.Analyzer{buildir.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `\'else\' branch of a type assertion is probably not reading the right value`,
Text: `
When declaring variables as part of an \'if\' statement (like in \"if
foo := ...; foo {\"), the same variables will also be in the scope of
the \'else\' branch. This means that in the following example
if x, ok := x.(int); ok {
// ...
} else {
fmt.Printf("unexpected type %T", x)
}
\'x\' in the \'else\' branch will refer to the \'x\' from \'x, ok
:=\'; it will not refer to the \'x\' that is being type-asserted. The
result of a failed type assertion is the zero value of the type that
is being asserted to, so \'x\' in the else branch will always have the
value \'0\' and the type \'int\'.
`,
Since: "2022.1",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var typeAssertionShadowingElseQ = pattern.MustParse(`(IfStmt (AssignStmt [obj@(Ident _) ok@(Ident _)] ":=" assert@(TypeAssertExpr obj _)) ok _ elseBranch)`)
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): without the IR-based verification, this check is able
// to find more bugs, but also more prone to false positives. It
// would be a good candidate for the 'codereview' category of
// checks.
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
for _, m := range code.Matches(pass, typeAssertionShadowingElseQ) {
shadow := pass.TypesInfo.ObjectOf(m.State["obj"].(*ast.Ident))
shadowed := m.State["assert"].(*ast.TypeAssertExpr).X
path, exact := astutil.PathEnclosingInterval(code.File(pass, shadow), shadow.Pos(), shadow.Pos())
if !exact {
// TODO(dh): when can this happen?
continue
}
irfn := ir.EnclosingFunction(irpkg, path)
if irfn == nil {
// For example for functions named "_", because we don't generate IR for them.
continue
}
shadoweeIR, isAddr := irfn.ValueForExpr(m.State["obj"].(*ast.Ident))
if shadoweeIR == nil || isAddr {
// TODO(dh): is this possible?
continue
}
var branch ast.Node
switch br := m.State["elseBranch"].(type) {
case ast.Node:
branch = br
case []ast.Stmt:
branch = &ast.BlockStmt{List: br}
case nil:
continue
default:
panic(fmt.Sprintf("unexpected type %T", br))
}
ast.Inspect(branch, func(node ast.Node) bool {
ident, ok := node.(*ast.Ident)
if !ok {
return true
}
if pass.TypesInfo.ObjectOf(ident) != shadow {
return true
}
v, isAddr := irfn.ValueForExpr(ident)
if v == nil || isAddr {
return true
}
if irutil.Flatten(v) != shadoweeIR {
// Same types.Object, but different IR value. This
// either means that the variable has been
// assigned to since the type assertion, or that
// the variable has escaped to the heap. Either
// way, we shouldn't flag reads of it.
return true
}
report.Report(pass, ident,
fmt.Sprintf("%s refers to the result of a failed type assertion and is a zero value, not the value that was being type-asserted", report.Render(pass, ident)),
report.Related(shadow, "this is the variable being read"),
report.Related(shadowed, "this is the variable being shadowed"))
return true
})
}
return nil, nil
}
================================================
FILE: staticcheck/sa9008/sa9008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9008/testdata/go1.0/CheckTypeAssertionShadowingElse/CheckTypeAssertionShadowingElse.go
================================================
package pkg
func fn1(x interface{}) {
if x, ok := x.(int); ok {
_ = x
} else {
_ = x //@ diag(`x refers to the result of a failed type assertion`)
x = 1
// No diagnostic, x is no longer the zero value
_ = x
}
if x, ok := x.(int); ok {
} else {
_ = x //@ diag(`x refers to the result of a failed type assertion`)
_ = &x
_ = x
}
if y, ok := x.(int); ok {
_ = y
} else {
// No diagnostic because x isn't shadowed
_ = x
}
if y, ok := x.(int); ok {
_ = y
} else {
// No diagnostic because y isn't shadowing x
_ = y
}
if x, ok := x.(int); ok && true {
_ = x
} else {
// No diagnostic because the condition isn't simply 'ok'
_ = x
}
if x, ok := x.(*int); ok {
_ = x
} else if x != nil { //@ diag(`x refers to`)
} else if x == nil { //@ diag(`x refers to`)
}
}
================================================
FILE: staticcheck/sa9009/sa9009.go
================================================
package sa9009
import (
"fmt"
"strings"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9009",
Run: run,
Requires: []*analysis.Analyzer{},
},
Doc: &lint.RawDocumentation{
Title: "Ineffectual Go compiler directive",
Text: `
A potential Go compiler directive was found, but is ineffectual as it begins
with whitespace.`,
Since: "2024.1",
Severity: lint.SeverityWarning,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
for _, cg := range f.Comments {
for _, c := range cg.List {
// Compiler directives have to be // comments
if !strings.HasPrefix(c.Text, "//") {
continue
}
if pass.Fset.PositionFor(c.Pos(), false).Column != 1 {
// Compiler directives have to be top-level. This also
// avoids a false positive for
// 'import _ "unsafe" // go:linkname'
continue
}
text := strings.TrimLeft(c.Text[2:], " \t")
if len(text) == len(c.Text[2:]) {
// There was no leading whitespace
continue
}
if !strings.HasPrefix(text, "go:") {
// Not an attempted compiler directive
continue
}
text = text[3:]
if len(text) == 0 || text[0] < 'a' || text[0] > 'z' {
// A letter other than a-z after "go:", so unlikely to be an
// attempted compiler directive
continue
}
report.Report(pass, c,
fmt.Sprintf(
"ineffectual compiler directive due to extraneous space: %q",
c.Text))
}
}
}
return nil, nil
}
================================================
FILE: staticcheck/sa9009/sa9009_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9009
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9009/testdata/go1.0/Directives/pkg.go
================================================
package pkg
import _ "unsafe" // go:linkname
// go:linkname bad1 foo.Bad1 //@ diag(`ineffectual compiler directive`)
func bad1() {
}
// go:foobar sdlkfjlaksdj //@ diag(`ineffectual compiler directive`)
func bad2() {
}
/* go:foobar 1234*/ // cannot be a compiler directive
func bad3() {
}
/* go:foobar 4321*/ // cannot be a compiler directive
func bad4() {
}
//go:linkname good1 good.One
func good1() {
}
//go:foobar blahblah
func good2() {
}
/*go:foobar asdf*/ // cannot be a compiler directive
func good3() {
}
/*go:foobar asdf*/ // cannot be a compiler directive
func good4() {
}
// go: probably just talking about Go
func good5() {
}
================================================
FILE: staticcheck/sa9010/sa9010.go
================================================
package sa9010
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "SA9010",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Returned function should be called in defer`,
Text: `
If you have a function such as:
func f() func() {
// Do something.
return func() {
// Do something.
}
}
Then calling that in defer:
defer f()
Is almost always a mistake, since you typically want to call the returned
function:
defer f()()
`,
Since: "Unreleased",
Severity: lint.SeverityWarning,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(n ast.Node) {
def := n.(*ast.DeferStmt)
if _, ok := pass.TypesInfo.TypeOf(def.Call).Underlying().(*types.Signature); ok {
report.Report(pass, def, "deferred return function not called")
}
}
code.Preorder(pass, fn, (*ast.DeferStmt)(nil))
return nil, nil
}
================================================
FILE: staticcheck/sa9010/sa9010_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package sa9010
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: staticcheck/sa9010/testdata/go1.0/example.com/deferr/deferr.go
================================================
package deferr
func x() {
var (
t t1
tt t2
varReturnNothing = func() {}
varReturnInt = func() int { return 0 }
varReturnFunc = func() func() { return func() {} }
varReturnFuncInt = func(int) func(int) int { return func(int) int { return 0 } }
varReturnMulti = func() (int, func()) { return 0, func() {} }
namedReturnNothing = named(func() {})
namedReturnFunc = namedReturn(func() func() { return func() {} })
)
// Correct.
defer returnNothing()
defer varReturnNothing()
defer namedReturnNothing()
defer t.returnNothing()
defer tt.returnNothing()
defer tt.t.returnNothing()
defer func() {}()
defer close(make(chan int))
defer returnInt()
defer varReturnInt()
defer t.returnInt()
defer tt.returnInt()
defer tt.t.returnInt()
defer func() int { return 0 }()
defer returnFunc()()
defer varReturnFunc()()
defer namedReturnFunc()()
defer t.returnFunc()()
defer tt.returnFunc()()
defer tt.t.returnFunc()()
defer func() func() { return func() {} }()()
defer returnFuncInt(0)(0)
defer varReturnFuncInt(0)(0)
defer t.returnFuncInt(0)(0)
defer tt.returnFuncInt(0)(0)
defer tt.t.returnFuncInt(0)(0)
defer func(int) func(int) int { return func(int) int { return 0 } }(0)(0)
defer returnMulti()
defer varReturnMulti()
defer t.returnMulti()
defer tt.returnMulti()
defer tt.t.returnMulti()
defer func() (int, func()) { return 0, func() {} }()
// Wrong.
defer returnFunc() //@ diag(`deferred return function not called`)
defer varReturnFunc() //@ diag(`deferred return function not called`)
defer namedReturnFunc() //@ diag(`deferred return function not called`)
defer t.returnFunc() //@ diag(`deferred return function not called`)
defer tt.returnFunc() //@ diag(`deferred return function not called`)
defer tt.t.returnFunc() //@ diag(`deferred return function not called`)
defer func() func() { return func() {} }() //@ diag(`deferred return function not called`)
defer returnFuncInt(0) //@ diag(`deferred return function not called`)
defer t.returnFuncInt(0) //@ diag(`deferred return function not called`)
defer tt.returnFuncInt(0) //@ diag(`deferred return function not called`)
defer tt.t.returnFuncInt(0) //@ diag(`deferred return function not called`)
defer func(int) func(int) int { return func(int) int { return 0 } }(0) //@ diag(`deferred return function not called`)
// Function returns a function which returns another function. This is
// getting silly, but we do flag it.
defer silly1()() //@ diag(`deferred return function not called`)
defer func() func() func() { //@ diag(`deferred return function not called`)
return func() func() {
return func() {}
}
}()()
}
func returnNothing() {}
func returnInt() int { return 0 }
func returnFunc() func() { return func() {} }
func returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
func returnMulti() (int, func()) { return 0, func() {} }
type (
t1 struct{}
t2 struct{ t t1 }
named func()
namedReturn func() func()
)
func (t1) returnNothing() {}
func (t1) returnInt() int { return 0 }
func (t1) returnFunc() func() { return func() {} }
func (t1) returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
func (t1) returnMulti() (int, func()) { return 0, func() {} }
func (*t2) returnNothing() {}
func (*t2) returnInt() int { return 0 }
func (*t2) returnFunc() func() { return func() {} }
func (*t2) returnFuncInt(int) func(int) int { return func(int) int { return 0 } }
func (*t2) returnMulti() (int, func()) { return 0, func() {} }
func silly1() func() func() {
return func() func() {
return func() {}
}
}
================================================
FILE: staticcheck/sa9010/testdata/go1.18/deferr/deferr.go
================================================
package deferr
func x() {
defer tpReturnFuncInt[int](0) //@ diag(`deferred return function not called`)
defer tpReturnFuncInt(0)(0)
}
func tpReturnFuncInt[T any](T) func(int) int { return func(int) int { return 0 } }
================================================
FILE: staticcheck.conf
================================================
checks = ["inherit", "-SA9003"]
================================================
FILE: structlayout/layout.go
================================================
package structlayout
import "fmt"
type Field struct {
Name string `json:"name"`
Type string `json:"type"`
Start int64 `json:"start"`
End int64 `json:"end"`
Size int64 `json:"size"`
Align int64 `json:"align"`
IsPadding bool `json:"is_padding"`
}
func (f Field) String() string {
if f.IsPadding {
return fmt.Sprintf("%s: %d-%d (size %d, align %d)",
"padding", f.Start, f.End, f.Size, f.Align)
}
return fmt.Sprintf("%s %s: %d-%d (size %d, align %d)",
f.Name, f.Type, f.Start, f.End, f.Size, f.Align)
}
================================================
FILE: stylecheck/analysis.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package stylecheck
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/stylecheck/st1000"
"honnef.co/go/tools/stylecheck/st1001"
"honnef.co/go/tools/stylecheck/st1003"
"honnef.co/go/tools/stylecheck/st1005"
"honnef.co/go/tools/stylecheck/st1006"
"honnef.co/go/tools/stylecheck/st1008"
"honnef.co/go/tools/stylecheck/st1011"
"honnef.co/go/tools/stylecheck/st1012"
"honnef.co/go/tools/stylecheck/st1013"
"honnef.co/go/tools/stylecheck/st1015"
"honnef.co/go/tools/stylecheck/st1016"
"honnef.co/go/tools/stylecheck/st1017"
"honnef.co/go/tools/stylecheck/st1018"
"honnef.co/go/tools/stylecheck/st1019"
"honnef.co/go/tools/stylecheck/st1020"
"honnef.co/go/tools/stylecheck/st1021"
"honnef.co/go/tools/stylecheck/st1022"
"honnef.co/go/tools/stylecheck/st1023"
)
var Analyzers = []*lint.Analyzer{
st1000.SCAnalyzer,
st1001.SCAnalyzer,
st1003.SCAnalyzer,
st1005.SCAnalyzer,
st1006.SCAnalyzer,
st1008.SCAnalyzer,
st1011.SCAnalyzer,
st1012.SCAnalyzer,
st1013.SCAnalyzer,
st1015.SCAnalyzer,
st1016.SCAnalyzer,
st1017.SCAnalyzer,
st1018.SCAnalyzer,
st1019.SCAnalyzer,
st1020.SCAnalyzer,
st1021.SCAnalyzer,
st1022.SCAnalyzer,
st1023.SCAnalyzer,
}
================================================
FILE: stylecheck/doc.go
================================================
//go:generate go run ../generate.go
// Package stylecheck contains analyzes that enforce style rules.
// Most of the recommendations made are universally agreed upon by the wider Go community.
// Some analyzes, however, implement stricter rules that not everyone will agree with.
// In the context of Staticcheck, these analyzes are not enabled by default.
//
// For the most part it is recommended to follow the advice given by the analyzers that are enabled by default,
// but you may want to disable additional analyzes on a case by case basis.
package stylecheck
================================================
FILE: stylecheck/st1000/st1000.go
================================================
package st1000
import (
"fmt"
"go/ast"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1000",
Run: run,
},
Doc: &lint.RawDocumentation{
Title: `Incorrect or missing package comment`,
Text: `Packages must have a package comment that is formatted according to
the guidelines laid out in
https://go.dev/wiki/CodeReviewComments#package-comments.`,
Since: "2019.1",
NonDefault: true,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// - At least one file in a non-main package should have a package comment
//
// - The comment should be of the form
// "Package x ...". This has a slight potential for false
// positives, as multiple files can have package comments, in
// which case they get appended. But that doesn't happen a lot in
// the real world.
if pass.Pkg.Name() == "main" {
return nil, nil
}
hasDocs := false
for _, f := range pass.Files {
if code.IsInTest(pass, f) {
continue
}
text, ok := docText(f.Doc)
if ok {
hasDocs = true
prefix := "Package " + f.Name.Name
isNonAlpha := func(b byte) bool {
// This check only considers ASCII, which can lead to false negatives for some malformed package
// comments.
return !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'))
}
if !strings.HasPrefix(text, prefix) || (len(text) > len(prefix) && !isNonAlpha(text[len(prefix)])) {
report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
}
}
}
if !hasDocs {
for _, f := range pass.Files {
if code.IsInTest(pass, f) {
continue
}
report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
}
}
return nil, nil
}
func docText(doc *ast.CommentGroup) (string, bool) {
if doc == nil {
return "", false
}
// We trim spaces primarily because of /**/ style comments, which often have leading space.
text := strings.TrimSpace(doc.Text())
return text, text != ""
}
================================================
FILE: stylecheck/st1000/st1000_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1000
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-1/CheckPackageComment-1.go
================================================
package pkg //@ diag(`at least one file in a package should have a package comment`)
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-2/CheckPackageComment-2.go
================================================
// This package is great //@ diag(`package comment should be of the form`)
package pkg
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-3/CheckPackageComment-3.go
================================================
/*
Package pkg is useful.
*/
package pkg
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-4/CheckPackageComment-4.go
================================================
// Package pkg, while not being very long, sure likes punctuation.
package pkg
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-5/CheckPackageComment-5.go
================================================
// Package pkg
package pkg
================================================
FILE: stylecheck/st1000/testdata/go1.0/CheckPackageComment-6/CheckPackageComment-6.go
================================================
// Package pkg2 //@ diag(`package comment should be of the form`)
package pkg
================================================
FILE: stylecheck/st1001/st1001.go
================================================
package st1001
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/config"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1001",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer, config.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Dot imports are discouraged`,
Text: `Dot imports that aren't in external test packages are discouraged.
The \'dot_import_whitelist\' option can be used to whitelist certain
imports.
Quoting Go Code Review Comments:
> The \'import .\' form can be useful in tests that, due to circular
> dependencies, cannot be made part of the package being tested:
>
> package foo_test
>
> import (
> "bar/testutil" // also imports "foo"
> . "foo"
> )
>
> In this case, the test file cannot be in package foo because it
> uses \'bar/testutil\', which imports \'foo\'. So we use the \'import .\'
> form to let the file pretend to be part of package foo even though
> it is not. Except for this one case, do not use \'import .\' in your
> programs. It makes the programs much harder to read because it is
> unclear whether a name like \'Quux\' is a top-level identifier in the
> current package or in an imported package.`,
Since: "2019.1",
Options: []string{"dot_import_whitelist"},
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
imports:
for _, imp := range f.Imports {
path := imp.Path.Value
path = path[1 : len(path)-1]
for _, w := range config.For(pass).DotImportWhitelist {
if w == path {
continue imports
}
}
if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1001/st1001_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1001
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1001/testdata/go1.0/CheckDotImports/CheckDotImports.go
================================================
// Package pkg ...
package pkg
import . "fmt" //@ diag(`should not use dot imports`)
var _ = Println
================================================
FILE: stylecheck/st1001/testdata/go1.0/CheckDotImports/CheckDotImports_test.go
================================================
package pkg
import . "fmt"
var _ = Println
================================================
FILE: stylecheck/st1003/st1003.go
================================================
package st1003
import (
"fmt"
"go/ast"
"go/token"
"strings"
"unicode"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/config"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1003",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, config.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Poorly chosen identifier`,
Text: `Identifiers, such as variable and package names, follow certain rules.
See the following links for details:
- https://go.dev/doc/effective_go#package-names
- https://go.dev/doc/effective_go#mixed-caps
- https://go.dev/wiki/CodeReviewComments#initialisms
- https://go.dev/wiki/CodeReviewComments#variable-names`,
Since: "2019.1",
NonDefault: true,
Options: []string{"initialisms"},
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
// knownNameExceptions is a set of names that are known to be exempt from naming checks.
// This is usually because they are constrained by having to match names in the
// standard library.
var knownNameExceptions = map[string]bool{
"LastInsertId": true, // must match database/sql
"kWh": true,
}
func run(pass *analysis.Pass) (any, error) {
// A large part of this function is copied from
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
// licensed under the BSD 3-clause license.
allCaps := func(s string) bool {
hasUppercaseLetters := false
for _, r := range s {
if !hasUppercaseLetters && r >= 'A' && r <= 'Z' {
hasUppercaseLetters = true
}
if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
return false
}
}
return hasUppercaseLetters
}
check := func(id *ast.Ident, thing string, initialisms map[string]bool) {
if id.Name == "_" {
return
}
if knownNameExceptions[id.Name] {
return
}
// Handle two common styles from other languages that don't belong in Go.
if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") {
report.Report(pass, id, "should not use ALL_CAPS in Go names; use CamelCase instead", report.FilterGenerated())
return
}
should := lintName(id.Name, initialisms)
if id.Name == should {
return
}
if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") {
report.Report(pass, id, fmt.Sprintf("should not use underscores in Go names; %s %s should be %s", thing, id.Name, should), report.FilterGenerated())
return
}
report.Report(pass, id, fmt.Sprintf("%s %s should be %s", thing, id.Name, should), report.FilterGenerated())
}
checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) {
if fl == nil {
return
}
for _, f := range fl.List {
for _, id := range f.Names {
check(id, thing, initialisms)
}
}
}
il := config.For(pass).Initialisms
initialisms := make(map[string]bool, len(il))
for _, word := range il {
initialisms[word] = true
}
for _, f := range pass.Files {
// Package names need slightly different handling than other names.
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
report.Report(pass, f.Name, "should not use underscores in package names", report.FilterGenerated())
}
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
report.Report(pass, f.Name, fmt.Sprintf("should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name)), report.FilterGenerated())
}
}
fn := func(node ast.Node) {
switch v := node.(type) {
case *ast.AssignStmt:
if v.Tok != token.DEFINE {
return
}
for _, exp := range v.Lhs {
if id, ok := exp.(*ast.Ident); ok {
check(id, "var", initialisms)
}
}
case *ast.FuncDecl:
// Functions with no body are defined elsewhere (in
// assembly, or via go:linkname). These are likely to
// be something very low level (such as the runtime),
// where our rules don't apply.
if v.Body == nil {
return
}
if code.IsInTest(pass, v) &&
(strings.HasPrefix(v.Name.Name, "Example") ||
strings.HasPrefix(v.Name.Name, "Test") ||
strings.HasPrefix(v.Name.Name, "Benchmark") ||
strings.HasPrefix(v.Name.Name, "Fuzz")) {
return
}
thing := "func"
if v.Recv != nil {
thing = "method"
}
if !isTechnicallyExported(v) {
check(v.Name, thing, initialisms)
}
checkList(v.Type.Params, thing+" parameter", initialisms)
checkList(v.Type.Results, thing+" result", initialisms)
case *ast.GenDecl:
if v.Tok == token.IMPORT {
return
}
var thing string
switch v.Tok {
case token.CONST:
thing = "const"
case token.TYPE:
thing = "type"
case token.VAR:
thing = "var"
}
for _, spec := range v.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
check(s.Name, thing, initialisms)
case *ast.ValueSpec:
for _, id := range s.Names {
check(id, thing, initialisms)
}
}
}
case *ast.InterfaceType:
// Do not check interface method names.
// They are often constrained by the method names of concrete types.
for _, x := range v.Methods.List {
ft, ok := x.Type.(*ast.FuncType)
if !ok { // might be an embedded interface name
continue
}
checkList(ft.Params, "interface method parameter", initialisms)
checkList(ft.Results, "interface method result", initialisms)
}
case *ast.RangeStmt:
if v.Tok == token.ASSIGN {
return
}
if id, ok := v.Key.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
if id, ok := v.Value.(*ast.Ident); ok {
check(id, "range var", initialisms)
}
case *ast.StructType:
for _, f := range v.Fields.List {
for _, id := range f.Names {
check(id, "struct field", initialisms)
}
}
}
}
needle := []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.FuncDecl)(nil),
(*ast.GenDecl)(nil),
(*ast.InterfaceType)(nil),
(*ast.RangeStmt)(nil),
(*ast.StructType)(nil),
}
code.Preorder(pass, fn, needle...)
return nil, nil
}
// lintName returns a different name if it should be different.
func lintName(name string, initialisms map[string]bool) (should string) {
// A large part of this function is copied from
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
// licensed under the BSD 3-clause license.
// Fast path for simple cases: "_" and all lowercase.
if name == "_" {
return name
}
if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 {
return name
}
// Split camelCase at any lower->upper transition, and split on underscores.
// Check each word for common initialisms.
runes := []rune(name)
w, i := 0, 0 // index of start of word, scan
for i+1 <= len(runes) {
eow := false // whether we hit the end of a word
if i+1 == len(runes) {
eow = true
} else if runes[i+1] == '_' && i+1 != len(runes)-1 {
// underscore; shift the remainder forward over any run of underscores
eow = true
n := 1
for i+n+1 < len(runes) && runes[i+n+1] == '_' {
n++
}
// Leave at most one underscore if the underscore is between two digits
if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
n--
}
copy(runes[i+1:], runes[i+n+1:])
runes = runes[:len(runes)-n]
} else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) {
// lower->non-lower
eow = true
}
i++
if !eow {
continue
}
// [w,i) is a word.
word := string(runes[w:i])
if u := strings.ToUpper(word); initialisms[u] {
// Keep consistent case, which is lowercase only at the start.
if w == 0 && unicode.IsLower(runes[w]) {
u = strings.ToLower(u)
}
// All the common initialisms are ASCII,
// so we can replace the bytes exactly.
// TODO(dh): this won't be true once we allow custom initialisms
copy(runes[w:], []rune(u))
} else if w > 0 && strings.ToLower(word) == word {
// already all lowercase, and not the first word, so uppercase the first character.
runes[w] = unicode.ToUpper(runes[w])
}
w = i
}
return string(runes)
}
func isTechnicallyExported(f *ast.FuncDecl) bool {
if f.Recv != nil || f.Doc == nil {
return false
}
const export = "//export "
const linkname = "//go:linkname "
for _, c := range f.Doc.List {
if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name {
return true
}
if strings.HasPrefix(c.Text, linkname) {
return true
}
}
return false
}
================================================
FILE: stylecheck/st1003/st1003_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1003
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1003/testdata/go1.0/CheckNames/CheckNames.go
================================================
// Package pkg_foo ...
package pkg_foo //@ diag(`should not use underscores in package names`)
import _ "unsafe"
var range_ int
var _abcdef int
var abcdef_ int
var abc_def int //@ diag(`should not use underscores in Go names; var abc_def should be abcDef`)
var abc_def_ int //@ diag(`should not use underscores in Go names; var abc_def_ should be abcDef_`)
func fn_1() {} //@ diag(`func fn_1 should be fn1`)
func fn2() {}
func fn_Id() {} //@ diag(`func fn_Id should be fnID`)
func fnId() {} //@ diag(`func fnId should be fnID`)
var FOO_BAR int //@ diag(`should not use ALL_CAPS in Go names; use CamelCase instead`)
var Foo_BAR int //@ diag(`var Foo_BAR should be FooBAR`)
var foo_bar int //@ diag(`foo_bar should be fooBar`)
var kFoobar int // not a check we inherited from golint. more false positives than true ones.
var _1000 int // issue 858
func fn(x []int) {
var (
a_b = 1 //@ diag(`var a_b should be aB`)
c_d int //@ diag(`var c_d should be cD`)
)
a_b += 2
for e_f := range x { //@ diag(`range var e_f should be eF`)
_ = e_f
}
_ = a_b
_ = c_d
}
//export fn_3
func fn_3() {}
//export not actually the export keyword
func fn_4() {} //@ diag(`func fn_4 should be fn4`)
//export
func fn_5() {} //@ diag(`func fn_5 should be fn5`)
// export fn_6
func fn_6() {} //@ diag(`func fn_6 should be fn6`)
//export fn_8
func fn_7() {} //@ diag(`func fn_7 should be fn7`)
//go:linkname fn_8 time.Now
func fn_8() {}
================================================
FILE: stylecheck/st1003/testdata/go1.0/CheckNames_generated/CheckNames_generated.go
================================================
// Code generated by an actual human. DO NOT EDIT.
// Package pkg_foo ...
package pkg_foo
================================================
FILE: stylecheck/st1005/st1005.go
================================================
package st1005
import (
"go/constant"
"strings"
"unicode"
"unicode/utf8"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ir"
"honnef.co/go/tools/go/ir/irutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1005",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Incorrectly formatted error string`,
Text: `Error strings follow a set of guidelines to ensure uniformity and good
composability.
Quoting Go Code Review Comments:
> Error strings should not be capitalized (unless beginning with
> proper nouns or acronyms) or end with punctuation, since they are
> usually printed following other context. That is, use
> \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so
> that \'log.Printf("Reading %s: %v", filename, err)\' formats without a
> spurious capital letter mid-message.`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
objNames := map[*ir.Package]map[string]bool{}
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
objNames[irpkg] = map[string]bool{}
for _, m := range irpkg.Members {
if typ, ok := m.(*ir.Type); ok {
objNames[irpkg][typ.Name()] = true
}
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
objNames[fn.Package()][fn.Name()] = true
}
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
if code.IsInTest(pass, fn) {
// We don't care about malformed error messages in tests;
// they're usually for direct human consumption, not part
// of an API
continue
}
for _, block := range fn.Blocks {
instrLoop:
for _, ins := range block.Instrs {
call, ok := ins.(*ir.Call)
if !ok {
continue
}
if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
continue
}
k, ok := call.Common().Args[0].(*ir.Const)
if !ok {
continue
}
s := constant.StringVal(k.Value)
if len(s) == 0 {
continue
}
switch s[len(s)-1] {
case '.', ':', '!', '\n':
report.Report(pass, call, "error strings should not end with punctuation or newlines")
}
before, _, ok0 := strings.Cut(s, " ")
if !ok0 {
// single word error message, probably not a real
// error but something used in tests or during
// debugging
continue
}
word := before
first, n := utf8.DecodeRuneInString(word)
if !unicode.IsUpper(first) {
continue
}
for _, c := range word[n:] {
if unicode.IsUpper(c) || unicode.IsDigit(c) {
// Word is probably an initialism or multi-word function name. Digits cover elliptic curves like
// P384.
continue instrLoop
}
}
if strings.ContainsRune(word, '(') {
// Might be a function call
continue instrLoop
}
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
if objNames[fn.Package()][word] {
// Word is probably the name of a function or type in this package
continue
}
// First word in error starts with a capital
// letter, and the word doesn't contain any other
// capitals, making it unlikely to be an
// initialism or multi-word function name.
//
// It could still be a proper noun, though.
report.Report(pass, call, "error strings should not be capitalized")
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1005/st1005_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1005
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1005/testdata/go1.0/CheckErrorStrings/CheckErrorStrings.go
================================================
// Package pkg ...
package pkg
import "errors"
func fn() {
errors.New("a perfectly fine error")
errors.New("Not a great error") //@ diag(`error strings should not be capitalized`)
errors.New("also not a great error.") //@ diag(`error strings should not end with punctuation or newlines`)
errors.New("URL is okay")
errors.New("SomeFunc is okay")
errors.New("URL is okay, but the period is not.") //@ diag(`error strings should not end with punctuation or newlines`)
errors.New("T must not be nil")
errors.New("Foo() failed")
errors.New("Foo(bar) failed")
errors.New("Foo(bar, baz) failed")
errors.New("P384 is a nice curve")
}
func Write() {
errors.New("Write: this is broken")
}
type T struct{}
func (T) Read() {
errors.New("Read: this is broken")
errors.New("Read failed")
}
func fn2() {
// The error string hasn't to be in the same function
errors.New("Read failed")
}
================================================
FILE: stylecheck/st1006/st1006.go
================================================
package st1006
import (
"go/types"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1006",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Poorly chosen receiver name`,
Text: `Quoting Go Code Review Comments:
> The name of a method's receiver should be a reflection of its
> identity; often a one or two letter abbreviation of its type
> suffices (such as "c" or "cl" for "Client"). Don't use generic
> names such as "me", "this" or "self", identifiers typical of
> object-oriented languages that place more emphasis on methods as
> opposed to functions. The name need not be as descriptive as that
> of a method argument, as its role is obvious and serves no
> documentary purpose. It can be very short as it will appear on
> almost every line of every method of the type; familiarity admits
> brevity. Be consistent, too: if you call the receiver "c" in one
> method, don't call it "cl" in another.`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
for _, m := range irpkg.Members {
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if typeutil.Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if recv.Name() == "self" || recv.Name() == "this" {
report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated())
}
if recv.Name() == "_" {
report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated())
}
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1006/st1006_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1006
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1006/testdata/go1.0/CheckReceiverNames/CheckReceiverNames.go
================================================
// Package pkg ...
package pkg
type T1 int
func (x T1) Fn1() {}
func (y T1) Fn2() {}
func (x T1) Fn3() {}
func (T1) Fn4() {}
func (_ T1) Fn5() {} //@ diag(`receiver name should not be an underscore, omit the name if it is unused`)
func (self T1) Fn6() {} //@ diag(`receiver name should be a reflection of its identity`)
================================================
FILE: stylecheck/st1008/st1008.go
================================================
package st1008
import (
"go/types"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1008",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `A function's error value should be its last return value`,
Text: `A function's error value should be its last return value.`,
Since: `2019.1`,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fnLoop:
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
sig := fn.Type().(*types.Signature)
rets := sig.Results()
if rets == nil || rets.Len() < 2 {
continue
}
if types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("error").Type() {
// Last return type is error. If the function also returns
// errors in other positions, that's fine.
continue
}
if rets.Len() >= 2 &&
types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("bool").Type() &&
types.Unalias(rets.At(rets.Len()-2).Type()) == types.Universe.Lookup("error").Type() {
// Accept (..., error, bool) and assume it's a comma-ok function. It's not clear whether the bool should come last or not for these kinds of functions.
continue
}
for i := rets.Len() - 2; i >= 0; i-- {
if types.Unalias(rets.At(i).Type()) == types.Universe.Lookup("error").Type() {
report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
continue fnLoop
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1008/st1008_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1008
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1008/testdata/go1.0/CheckErrorReturn/CheckErrorReturn.go
================================================
// Package pkg ...
package pkg
func fn1() (error, int) { return nil, 0 } //@ diag(`error should be returned as the last argument`)
func fn2() (a, b error, c int) { return nil, nil, 0 } //@ diag(`error should be returned as the last argument`)
func fn3() (a int, b, c error) { return 0, nil, nil }
func fn4() (error, error) { return nil, nil }
func fn5() int { return 0 }
func fn6() (int, error) { return 0, nil }
func fn7() (error, int, error) { return nil, 0, nil }
// it's not clear if the error should come first or second in a function that also has a comma-ok return value
func fn8() (error, bool) { return nil, false }
func fn9() (int, error, bool) { return 0, nil, false }
================================================
FILE: stylecheck/st1008/testdata/go1.9/CheckErrorReturn/CheckErrorReturn.go
================================================
// Package pkg ...
package pkg
type Alias = error
func fn10() (Alias, int) { return nil, 0 } //@ diag(`error should be returned as the last argument`)
================================================
FILE: stylecheck/st1011/st1011.go
================================================
package st1011
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1011",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Poorly chosen name for variable of type \'time.Duration\'`,
Text: `\'time.Duration\' values represent an amount of time, which is represented
as a count of nanoseconds. An expression like \'5 * time.Microsecond\'
yields the value \'5000\'. It is therefore not appropriate to suffix a
variable of type \'time.Duration\' with any time unit, such as \'Msec\' or
\'Milli\'.`,
Since: `2019.1`,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
suffixes := []string{
"Sec", "Secs", "Seconds",
"Msec", "Msecs",
"Milli", "Millis", "Milliseconds",
"Usec", "Usecs", "Microseconds",
"MS", "Ms",
}
fn := func(names []*ast.Ident) {
for _, name := range names {
if _, ok := pass.TypesInfo.Defs[name]; !ok {
continue
}
T := pass.TypesInfo.TypeOf(name)
if !typeutil.IsTypeWithName(T, "time.Duration") && !typeutil.IsPointerToTypeWithName(T, "time.Duration") {
continue
}
for _, suffix := range suffixes {
if strings.HasSuffix(name.Name, suffix) {
report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
break
}
}
}
}
fn2 := func(node ast.Node) {
switch node := node.(type) {
case *ast.ValueSpec:
fn(node.Names)
case *ast.FieldList:
for _, field := range node.List {
fn(field.Names)
}
case *ast.AssignStmt:
if node.Tok != token.DEFINE {
break
}
var names []*ast.Ident
for _, lhs := range node.Lhs {
if lhs, ok := lhs.(*ast.Ident); ok {
names = append(names, lhs)
}
}
fn(names)
}
}
code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
return nil, nil
}
================================================
FILE: stylecheck/st1011/st1011_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1011
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1011/testdata/go1.0/CheckTimeNames/CheckTimeNames.go
================================================
// Package pkg ...
package pkg
import "time"
type T1 struct {
aMS int
B time.Duration
BMillis time.Duration //@ diag(`don't use unit-specific suffix`)
}
func fn1(a, b, cMS time.Duration) { //@ diag(`don't use unit-specific suffix`)
var x time.Duration
var xMS time.Duration //@ diag(`don't use unit-specific suffix`)
var y, yMS time.Duration //@ diag(`don't use unit-specific suffix`)
var zMS = time.Second //@ diag(`don't use unit-specific suffix`)
aMS := time.Second //@ diag(`don't use unit-specific suffix`)
unrelated, aMS := 0, 0
aMS, bMS := 0, time.Second //@ diag(re`var bMS .+ don't use unit-specific suffix`)
_, _, _, _, _, _, _, _ = x, xMS, y, yMS, zMS, aMS, unrelated, bMS
}
================================================
FILE: stylecheck/st1012/st1012.go
================================================
package st1012
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1012",
Run: run,
},
Doc: &lint.RawDocumentation{
Title: `Poorly chosen name for error variable`,
Text: `Error variables that are part of an API should be called \'errFoo\' or
\'ErrFoo\'.`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
for _, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.VAR {
continue
}
for _, spec := range gen.Specs {
spec := spec.(*ast.ValueSpec)
if len(spec.Names) != len(spec.Values) {
continue
}
for i, name := range spec.Names {
val := spec.Values[i]
if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
continue
}
if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
// special case for internal variable names of
// bundled HTTP 2 code in net/http
continue
}
prefix := "err"
if name.IsExported() {
prefix = "Err"
}
if !strings.HasPrefix(name.Name, prefix) {
report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
}
}
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1012/st1012_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1012
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1012/testdata/go1.0/CheckErrorVarNames/CheckErrorVarNames.go
================================================
// Package pkg ...
package pkg
import (
"errors"
"fmt"
)
var (
foo = errors.New("") //@ diag(`error var foo should have name of the form errFoo`)
errBar = errors.New("")
qux, fisk, errAnother = errors.New(""), errors.New(""), errors.New("") //@ diag(`error var qux should have name of the form errFoo`), diag(`error var fisk should have name of the form errFoo`)
abc = fmt.Errorf("") //@ diag(`error var abc should have name of the form errFoo`)
errAbc = fmt.Errorf("")
)
var wrong = errors.New("") //@ diag(`error var wrong should have name of the form errFoo`)
var result = fn()
func fn() error { return nil }
================================================
FILE: stylecheck/st1013/st1013.go
================================================
package st1013
import (
"fmt"
"go/ast"
"go/constant"
"strconv"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/config"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1013",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer, config.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Should use constants for HTTP error codes, not magic numbers`,
Text: `HTTP has a tremendous number of status codes. While some of those are
well known (200, 400, 404, 500), most of them are not. The \'net/http\'
package provides constants for all status codes that are part of the
various specifications. It is recommended to use these constants
instead of hard-coding magic numbers, to vastly improve the
readability of your code.`,
Since: "2019.1",
Options: []string{"http_status_code_whitelist"},
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var query = pattern.MustParse(`
(CallExpr
(Symbol
name@(Or
"net/http.Error"
"net/http.Redirect"
"net/http.StatusText"
"net/http.RedirectHandler"))
args)`)
func run(pass *analysis.Pass) (any, error) {
whitelist := map[string]bool{}
for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
whitelist[code] = true
}
for _, m := range code.Matches(pass, query) {
var arg int
switch m.State["name"].(string) {
case "net/http.Error":
arg = 2
case "net/http.Redirect":
arg = 3
case "net/http.StatusText":
arg = 0
case "net/http.RedirectHandler":
arg = 1
default:
continue
}
args := m.State["args"].([]ast.Expr)
if arg >= len(args) {
continue
}
tv, ok := code.IntegerLiteral(pass, args[arg])
if !ok {
continue
}
n, ok := constant.Int64Val(tv.Value)
if !ok {
continue
}
if whitelist[strconv.FormatInt(n, 10)] {
continue
}
s, ok := httpStatusCodes[n]
if !ok {
continue
}
lit := args[arg]
report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
report.FilterGenerated(),
report.Fixes(edit.Fix(fmt.Sprintf("Use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s))))
}
return nil, nil
}
var httpStatusCodes = map[int64]string{
100: "StatusContinue",
101: "StatusSwitchingProtocols",
102: "StatusProcessing",
200: "StatusOK",
201: "StatusCreated",
202: "StatusAccepted",
203: "StatusNonAuthoritativeInfo",
204: "StatusNoContent",
205: "StatusResetContent",
206: "StatusPartialContent",
207: "StatusMultiStatus",
208: "StatusAlreadyReported",
226: "StatusIMUsed",
300: "StatusMultipleChoices",
301: "StatusMovedPermanently",
302: "StatusFound",
303: "StatusSeeOther",
304: "StatusNotModified",
305: "StatusUseProxy",
307: "StatusTemporaryRedirect",
308: "StatusPermanentRedirect",
400: "StatusBadRequest",
401: "StatusUnauthorized",
402: "StatusPaymentRequired",
403: "StatusForbidden",
404: "StatusNotFound",
405: "StatusMethodNotAllowed",
406: "StatusNotAcceptable",
407: "StatusProxyAuthRequired",
408: "StatusRequestTimeout",
409: "StatusConflict",
410: "StatusGone",
411: "StatusLengthRequired",
412: "StatusPreconditionFailed",
413: "StatusRequestEntityTooLarge",
414: "StatusRequestURITooLong",
415: "StatusUnsupportedMediaType",
416: "StatusRequestedRangeNotSatisfiable",
417: "StatusExpectationFailed",
418: "StatusTeapot",
422: "StatusUnprocessableEntity",
423: "StatusLocked",
424: "StatusFailedDependency",
426: "StatusUpgradeRequired",
428: "StatusPreconditionRequired",
429: "StatusTooManyRequests",
431: "StatusRequestHeaderFieldsTooLarge",
451: "StatusUnavailableForLegalReasons",
500: "StatusInternalServerError",
501: "StatusNotImplemented",
502: "StatusBadGateway",
503: "StatusServiceUnavailable",
504: "StatusGatewayTimeout",
505: "StatusHTTPVersionNotSupported",
506: "StatusVariantAlsoNegotiates",
507: "StatusInsufficientStorage",
508: "StatusLoopDetected",
510: "StatusNotExtended",
511: "StatusNetworkAuthenticationRequired",
}
================================================
FILE: stylecheck/st1013/st1013_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1013
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1013/testdata/go1.0/CheckHTTPStatusCodes/CheckHTTPStatusCodes.go
================================================
// Package pkg ...
package pkg
import "net/http"
func fn() {
// Check all the supported functions
http.Error(nil, "", 506) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.Redirect(nil, nil, "", 506) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.StatusText(506) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.RedirectHandler("", 506) //@ diag(`http.StatusVariantAlsoNegotiates`)
// Don't flag literals with no known constant
http.StatusText(600)
// Don't flag constants
http.StatusText(http.StatusAccepted)
// Don't flag items on the whitelist (well known codes)
http.StatusText(404)
http.Error(fn2())
}
func fn2() (http.ResponseWriter, string, int) { return nil, "", 0 }
================================================
FILE: stylecheck/st1013/testdata/go1.0/CheckHTTPStatusCodes/CheckHTTPStatusCodes.go.golden
================================================
// Package pkg ...
package pkg
import "net/http"
func fn() {
// Check all the supported functions
http.Error(nil, "", http.StatusVariantAlsoNegotiates) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.Redirect(nil, nil, "", http.StatusVariantAlsoNegotiates) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.StatusText(http.StatusVariantAlsoNegotiates) //@ diag(`http.StatusVariantAlsoNegotiates`)
http.RedirectHandler("", http.StatusVariantAlsoNegotiates) //@ diag(`http.StatusVariantAlsoNegotiates`)
// Don't flag literals with no known constant
http.StatusText(600)
// Don't flag constants
http.StatusText(http.StatusAccepted)
// Don't flag items on the whitelist (well known codes)
http.StatusText(404)
http.Error(fn2())
}
func fn2() (http.ResponseWriter, string, int) { return nil, "", 0 }
================================================
FILE: stylecheck/st1015/st1015.go
================================================
package st1015
import (
"go/ast"
"go/token"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1015",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `A switch's default case should be the first or last case`,
Since: "2019.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
hasFallthrough := func(clause ast.Stmt) bool {
// A valid fallthrough statement may be used only as the final non-empty statement in a case clause. Thus we can
// easily avoid falsely matching fallthroughs in nested switches by not descending into blocks.
body := clause.(*ast.CaseClause).Body
for i := len(body) - 1; i >= 0; i-- {
last := body[i]
switch stmt := last.(type) {
case *ast.EmptyStmt:
// Fallthrough may be followed by empty statements
case *ast.BranchStmt:
return stmt.Tok == token.FALLTHROUGH
default:
return false
}
}
return false
}
fn := func(node ast.Node) {
stmt := node.(*ast.SwitchStmt)
list := stmt.Body.List
defaultIdx := -1
for i, c := range list {
if c.(*ast.CaseClause).List == nil {
defaultIdx = i
break
}
}
if defaultIdx == -1 || defaultIdx == 0 || defaultIdx == len(list)-1 {
// No default case, or it's the first or last case
return
}
if hasFallthrough(list[defaultIdx-1]) || hasFallthrough(list[defaultIdx]) {
// We either fall into or out of this case; don't mess with the order
return
}
report.Report(pass, list[defaultIdx], "default case should be first or last in switch statement", report.FilterGenerated())
}
code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
return nil, nil
}
================================================
FILE: stylecheck/st1015/st1015_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1015
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1015/testdata/go1.0/CheckDefaultCaseOrder/CheckDefaultCaseOrder.go
================================================
// Package pkg ...
package pkg
func fn(x int) {
switch x {
}
switch x {
case 1:
}
switch x {
case 1:
case 2:
case 3:
}
switch x {
default:
}
switch x {
default:
case 1:
}
switch x {
case 1:
default:
}
switch x {
case 1:
default: //@ diag(`default case should be first or last in switch statement`)
case 2:
}
// Don't flag either of these two; fallthrough is sensitive to the order of cases
switch x {
case 1:
fallthrough
default:
case 2:
}
switch x {
case 1:
default:
fallthrough
case 2:
}
// Do flag these branches; don't get confused by the nested switch statements
switch x {
case 1:
func() {
switch x {
case 1:
fallthrough
case 2:
}
}()
default: //@ diag(`default case should be first or last in switch statement`)
case 3:
}
switch x {
case 1:
switch x {
case 1:
fallthrough
case 2:
}
default: //@ diag(`default case should be first or last in switch statement`)
case 3:
}
// Fallthrough may be followed by empty statements
switch x {
case 1:
fallthrough
;
default:
case 3:
}
}
================================================
FILE: stylecheck/st1016/st1016.go
================================================
package st1016
import (
"fmt"
"go/types"
"sort"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/passes/buildir"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1016",
Run: run,
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use consistent method receiver names`,
Since: "2019.1",
NonDefault: true,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
for _, m := range irpkg.Members {
names := map[string]int{}
var firstFn *types.Func
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
for _, sel := range ms {
fn := sel.Obj().(*types.Func)
recv := fn.Type().(*types.Signature).Recv()
if code.IsGenerated(pass, recv.Pos()) {
// Don't concern ourselves with methods in generated code
continue
}
if typeutil.Dereference(recv.Type()) != T.Type() {
// skip embedded methods
continue
}
if firstFn == nil {
firstFn = fn
}
if recv.Name() != "" && recv.Name() != "_" {
names[recv.Name()]++
}
}
}
if len(names) > 1 {
var seen []string
for name, count := range names {
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
}
sort.Strings(seen)
report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1016/st1016_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1016
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1016/testdata/go1.0/CheckReceiverNamesIdentical/CheckReceiverNames.go
================================================
// Package pkg ...
package pkg
type T1 int
func (x T1) Fn1() {} //@ diag(`methods on the same type should have the same receiver name`)
func (y T1) Fn2() {}
func (x T1) Fn3() {}
func (T1) Fn4() {}
func (_ T1) Fn5() {}
func (self T1) Fn6() {}
func (bar T3) Fn2() {} //@ diag(`1x "bar", 1x "meow"`)
func (meow T3) Fn3() {}
func (bar T4) Fn2() {}
================================================
FILE: stylecheck/st1016/testdata/go1.0/CheckReceiverNamesIdentical/generated.go
================================================
// Code generated by hand. DO NOT EDIT.
package pkg
// Methods on T2 are only defined in this generated file
// Methods on T3 and T4 are defined in this file and a non-generated file
type T2 struct{}
func (foo T2) Fn1() {}
func (bar T2) Fn2() {}
type T3 struct{}
func (foo T3) Fn1() {}
type T4 struct{}
func (foo T4) Fn1() {}
================================================
FILE: stylecheck/st1017/st1017.go
================================================
package st1017
import (
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1017",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Don't use Yoda conditions`,
Text: `Yoda conditions are conditions of the kind \"if 42 == x\", where the
literal is on the left side of the comparison. These are a common
idiom in languages in which assignment is an expression, to avoid bugs
of the kind \"if (x = 42)\". In Go, which doesn't allow for this kind of
bug, we prefer the more idiomatic \"if x == 42\".`,
Since: "2019.2",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(TrulyConstantExpression _) tok@(Or "==" "!=") right@(Not (TrulyConstantExpression _)))`)
checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
)
func run(pass *analysis.Pass) (any, error) {
for node, m := range code.Matches(pass, checkYodaConditionsQ) {
edits := code.EditMatch(pass, node, m, checkYodaConditionsR)
report.Report(pass, node, "don't use Yoda conditions",
report.FilterGenerated(),
report.Fixes(edit.Fix("Un-Yoda-fy", edits...)))
}
return nil, nil
}
================================================
FILE: stylecheck/st1017/st1017_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1017
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1017/testdata/go1.0/CheckYodaConditions/CheckYodaConditions.go
================================================
// Package pkg ...
package pkg
func fn(x string, y int) {
if "" == x { //@ diag(`Yoda`)
}
if 0 == y { //@ diag(`Yoda`)
}
if 0 > y {
}
if "" == "" {
}
if "" == "" || 0 == y { //@ diag(`Yoda`)
}
}
================================================
FILE: stylecheck/st1017/testdata/go1.0/CheckYodaConditions/CheckYodaConditions.go.golden
================================================
// Package pkg ...
package pkg
func fn(x string, y int) {
if x == "" { //@ diag(`Yoda`)
}
if y == 0 { //@ diag(`Yoda`)
}
if 0 > y {
}
if "" == "" {
}
if "" == "" || y == 0 { //@ diag(`Yoda`)
}
}
================================================
FILE: stylecheck/st1018/st1018.go
================================================
package st1018
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"unicode"
"unicode/utf8"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1018",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Avoid zero-width and control characters in string literals`,
Since: "2019.2",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
lit := node.(*ast.BasicLit)
if lit.Kind != token.STRING {
return
}
type invalid struct {
r rune
off int
}
var invalids []invalid
hasFormat := false
hasControl := false
prev := rune(-1)
const zwj = '\u200d'
for off, r := range lit.Value {
if unicode.Is(unicode.Cf, r) {
if r >= '\U000e0020' && r <= '\U000e007f' {
// These are used for spelling out country codes for flag emoji
} else if unicode.Is(unicode.Variation_Selector, r) {
// Always allow variation selectors
} else if r == zwj && (unicode.Is(unicode.S, prev) || unicode.Is(unicode.Variation_Selector, prev)) {
// Allow zero-width joiner in emoji, including those that use variation selectors.
// Technically some foreign scripts make valid use of zero-width joiners, too, but for now we'll err
// on the side of flagging all non-emoji uses of ZWJ.
} else {
switch r {
case '\u0600', '\u0601', '\u0602', '\u0603', '\u0604', '\u0605', '\u0890', '\u0891', '\u08e2':
// Arabic characters that are not actually invisible. If anyone knows why these are in the
// Other, Format category please let me know.
case '\u061c', '\u202A', '\u202B', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u202C', '\u2069':
// Bidirectional formatting characters. At best they will render confusingly, at worst they're used
// to cause confusion.
fallthrough
default:
invalids = append(invalids, invalid{r, off})
hasFormat = true
}
}
} else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
invalids = append(invalids, invalid{r, off})
hasControl = true
}
prev = r
}
switch len(invalids) {
case 0:
return
case 1:
var kind string
if hasFormat {
kind = "format"
} else if hasControl {
kind = "control"
} else {
panic("unreachable")
}
r := invalids[0]
msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
replacement := strconv.QuoteRune(r.r)
replacement = replacement[1 : len(replacement)-1]
edit := analysis.SuggestedFix{
Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r),
TextEdits: []analysis.TextEdit{{
Pos: lit.Pos() + token.Pos(r.off),
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
NewText: []byte(replacement),
}},
}
delete := analysis.SuggestedFix{
Message: fmt.Sprintf("delete %s character %U", kind, r.r),
TextEdits: []analysis.TextEdit{{
Pos: lit.Pos() + token.Pos(r.off),
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
}},
}
report.Report(pass, lit, msg, report.Fixes(edit, delete))
default:
var kind string
if hasFormat && hasControl {
kind = "format and control"
} else if hasFormat {
kind = "format"
} else if hasControl {
kind = "control"
} else {
panic("unreachable")
}
msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind)
var edits []analysis.TextEdit
var deletions []analysis.TextEdit
for _, r := range invalids {
replacement := strconv.QuoteRune(r.r)
replacement = replacement[1 : len(replacement)-1]
edits = append(edits, analysis.TextEdit{
Pos: lit.Pos() + token.Pos(r.off),
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
NewText: []byte(replacement),
})
deletions = append(deletions, analysis.TextEdit{
Pos: lit.Pos() + token.Pos(r.off),
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
})
}
edit := analysis.SuggestedFix{
Message: fmt.Sprintf("replace all %s characters with escape sequences", kind),
TextEdits: edits,
}
delete := analysis.SuggestedFix{
Message: fmt.Sprintf("delete all %s characters", kind),
TextEdits: deletions,
}
report.Report(pass, lit, msg, report.Fixes(edit, delete))
}
}
code.Preorder(pass, fn, (*ast.BasicLit)(nil))
return nil, nil
}
================================================
FILE: stylecheck/st1018/st1018_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1018
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1018/testdata/go1.0/CheckInvisibleCharacters/CheckInvisibleCharacters.go
================================================
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
================================================
FILE: stylecheck/st1018/testdata/go1.0/CheckInvisibleCharacters/CheckInvisibleCharacters.go.golden
================================================
-- replace control character U+0007 with '\a' --
// Package pkg ...
package pkg
var (
a = "\a" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬\a" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- delete control character U+0007 --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- delete format character U+200B --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- replace format character U+200B with '\u200b' --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `Zero\u200bWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- delete all control characters --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- replace all control characters with escape sequences --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "\a\x1a" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- delete all format and control characters --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
-- replace all format and control characters with escape sequences --
// Package pkg ...
package pkg
var (
a = "" //@ diag(`Unicode control character U+0007`)
b = "" //@ diag(`Unicode control characters`)
c = "Test test"
d = `T
est`
e = `ZeroWidth` //@ diag(`Unicode format character U+200B`)
f = "\u200b"
g = "👩🏽🔬" //@ diag(`Unicode control character U+0007`)
h = "👩🏽🔬\a\u200b" //@ diag(`Unicode format and control characters`)
i = "👨👩👦"
j = "🏳️🌈"
k = "🏴"
)
================================================
FILE: stylecheck/st1019/st1019.go
================================================
package st1019
import (
"fmt"
"go/ast"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1019",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Importing the same package multiple times`,
Text: `Go allows importing the same package multiple times, as long as
different import aliases are being used. That is, the following
bit of code is valid:
import (
"fmt"
fumpt "fmt"
format "fmt"
)
However, this is very rarely done on purpose. Usually, it is a
sign of code that got refactored, accidentally adding duplicate
import statements. It is also a rarely known feature, which may
contribute to confusion.
Do note that sometimes, this feature may be used
intentionally (see for example
https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)
– if you want to allow this pattern in your code base, you're
advised to disable this check.
It is acceptable to import the same package twice if one of the imports
uses the blank identifier. This is allowed in order to increase
resilience against erroneous changes when using the same package for its
side effects as well as its exported API.`,
Since: "2020.1",
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
// Collect all imports by their import path
imports := make(map[string][]*ast.ImportSpec, len(f.Imports))
for _, imp := range f.Imports {
if imp.Name != nil && imp.Name.Name == "_" {
// Allow blank imports to coexist with one normal import.
//
// We don't have to count the number of blank imports,
// goimports removes duplicates.
continue
}
imports[imp.Path.Value] = append(imports[imp.Path.Value], imp)
}
for path, value := range imports {
if path[1:len(path)-1] == "unsafe" {
// Don't flag unsafe. Cgo generated code imports
// unsafe as _cgo_unsafe, in addition to the user's import.
continue
}
// If there's more than one import per path, we flag that
if len(value) > 1 {
s := fmt.Sprintf("package %s is being imported more than once", path)
opts := []report.Option{report.FilterGenerated()}
for _, imp := range value[1:] {
opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path)))
}
report.Report(pass, value[0], s, opts...)
}
}
}
return nil, nil
}
================================================
FILE: stylecheck/st1019/st1019_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1019
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1019/testdata/go1.0/CheckDuplicatedImports/CheckDuplicatedImports.go
================================================
// Package pkg ...
package pkg
import (
"fmt" //@ diag(`package "fmt" is being imported more than once`)
fmt1 "fmt"
fmt2 "fmt"
fine "net/http"
"os" //@ diag(`package "os" is being imported more than once`)
os1 "os"
"C"
_ "unsafe"
// This imports the package for its side effects, and then again to use it.
// If we flagged this, the user would have to remove the _ import. But if
// later the user stopped using the package directly, they'd be prone to
// removing the import, losing its side effects.
"net/http/pprof"
_ "net/http/pprof"
_ "strconv"
strconv1 "strconv" //@ diag(`package "strconv" is being imported more than once`)
strconv2 "strconv"
)
var _ = fmt.Println
var _ = fmt1.Println
var _ = fmt2.Println
var _ = fine.ListenAndServe
var _ = os.Getenv
var _ = os1.Getenv
var _ = pprof.Cmdline
var _ = strconv1.AppendBool
var _ = strconv2.AppendBool
================================================
FILE: stylecheck/st1020/st1020.go
================================================
package st1020
import (
"fmt"
"go/ast"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1020",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "The documentation of an exported function should start with the function's name",
Text: `Doc comments work best as complete sentences, which
allow a wide variety of automated presentations. The first sentence
should be a one-sentence summary that starts with the name being
declared.
If every doc comment begins with the name of the item it describes,
you can use the \'doc\' subcommand of the \'go\' tool and run the output
through grep.
See https://go.dev/doc/effective_go#commentary for more
information on how to write good documentation.`,
Since: "2020.1",
NonDefault: true,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
fn := func(node ast.Node) {
if code.IsInTest(pass, node) {
return
}
decl := node.(*ast.FuncDecl)
text, ok := docText(decl.Doc)
if !ok {
return
}
if !ast.IsExported(decl.Name.Name) {
return
}
if strings.HasPrefix(text, "Deprecated: ") {
return
}
kind := "function"
if decl.Recv != nil {
kind = "method"
var ident *ast.Ident
T := decl.Recv.List[0].Type
if T_, ok := T.(*ast.StarExpr); ok {
T = T_.X
}
switch T := T.(type) {
case *ast.IndexExpr:
ident = T.X.(*ast.Ident)
case *ast.IndexListExpr:
ident = T.X.(*ast.Ident)
case *ast.Ident:
ident = T
default:
lint.ExhaustiveTypeSwitch(T)
}
if !ast.IsExported(ident.Name) {
return
}
}
prefix := decl.Name.Name + " "
if !strings.HasPrefix(text, prefix) {
report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated())
}
}
code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
return nil, nil
}
func docText(doc *ast.CommentGroup) (string, bool) {
if doc == nil {
return "", false
}
// We trim spaces primarily because of /**/ style comments, which often have leading space.
text := strings.TrimSpace(doc.Text())
return text, text != ""
}
================================================
FILE: stylecheck/st1020/st1020_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1020
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1020/testdata/go1.0/CheckExportedFunctionDocs/CheckExportedFunctionDocs.go
================================================
package pkg
// whatever
func foo() {}
// Foo is amazing
func Foo() {}
// Whatever //@ diag(`comment on exported function`)
func Bar() {}
type T struct{}
// Whatever
func (T) foo() {}
// Foo is amazing
func (T) Foo() {}
// Whatever //@ diag(`comment on exported method`)
func (T) Bar() {}
// Deprecated: don't use.
func (T) Dep() {}
//
func Qux() {} // this is fine, because "no comment" and "empty comment" are treated the same
//
// Meow is amazing.
//
// godoc allows this style, because ast.CommentGroup.Text strips whitespace.
// We currently make no effort to flag it.
//
func Meow() {}
//some:directive
func F1() {} // we pretend that directives aren't part of the doc string, just like godoc in Go 1.15+ does
//some:directive
// F2 is amazing
func F2() {}
//some:directive //@ diag(`comment on exported function`)
// Whatever
func F3() {}
// Deprecated: don't use.
func F4() {}
//some:directive
// Deprecated: don't use.
func F5() {}
// wrong comment yo. //@diag (`comment on exported function`)
//
// Deprecated: don't use.
func F6() {}
================================================
FILE: stylecheck/st1020/testdata/go1.0/CheckExportedFunctionDocs/foo_test.go
================================================
package pkg
import "testing"
// This is a test
func TestFoo(t *testing.T) {}
================================================
FILE: stylecheck/st1020/testdata/go1.18/CheckExportedFunctionDocs/generics.go
================================================
package pkg
// Whatever //@ diag(`comment on exported function`)
func TPFoo[T any]() {}
// Whatever //@ diag(`comment on exported function`)
func TPBar[T1, T2 any]() {}
// TPBaz is amazing
func TPBaz[T any]() {}
type TPT[T any] struct{}
// Foo is amazing
func (TPT[T]) Foo() {}
// Whatever //@ diag(`comment on exported method`)
func (TPT[T]) Bar() {}
type TPT2[T1, T2 any] struct{}
// Foo is amazing
func (TPT2[T1, T2]) Foo() {}
// Whatever //@ diag(`comment on exported method`)
func (*TPT2[T1, T2]) Bar() {}
// Deprecated: don't use.
func TPFooDepr[T any]() {}
// Deprecated: don't use.
func TPBarDepr[T1, T2 any]() {}
// Deprecated: don't use.
func (TPT[T]) BarDepr() {}
// Deprecated: don't use.
func (*TPT2[T1, T2]) BarDepr() {}
================================================
FILE: stylecheck/st1021/st1021.go
================================================
package st1021
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1021",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "The documentation of an exported type should start with type's name",
Text: `Doc comments work best as complete sentences, which
allow a wide variety of automated presentations. The first sentence
should be a one-sentence summary that starts with the name being
declared.
If every doc comment begins with the name of the item it describes,
you can use the \'doc\' subcommand of the \'go\' tool and run the output
through grep.
See https://go.dev/doc/effective_go#commentary for more
information on how to write good documentation.`,
Since: "2020.1",
NonDefault: true,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
var genDecl *ast.GenDecl
fn := func(node ast.Node, push bool) bool {
if !push {
genDecl = nil
return false
}
if code.IsInTest(pass, node) {
return false
}
switch node := node.(type) {
case *ast.GenDecl:
if node.Tok == token.IMPORT {
return false
}
genDecl = node
return true
case *ast.TypeSpec:
if !ast.IsExported(node.Name.Name) {
return false
}
doc := node.Doc
text, ok := docText(doc)
if !ok {
if len(genDecl.Specs) != 1 {
// more than one spec in the GenDecl, don't validate the
// docstring
return false
}
if genDecl.Lparen.IsValid() {
// 'type ( T )' is weird, don't guess the user's intention
return false
}
doc = genDecl.Doc
text, ok = docText(doc)
if !ok {
return false
}
}
// Check comment before we strip articles in case the type's name is an article.
if strings.HasPrefix(text, node.Name.Name+" ") {
return false
}
s := text
articles := [...]string{"A", "An", "The"}
for _, a := range articles {
if strings.HasPrefix(s, a+" ") {
s = s[len(a)+1:]
break
}
}
if !strings.HasPrefix(s, node.Name.Name+" ") {
report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
}
return false
case *ast.FuncLit, *ast.FuncDecl:
return false
default:
lint.ExhaustiveTypeSwitch(node)
return false
}
}
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
return nil, nil
}
func docText(doc *ast.CommentGroup) (string, bool) {
if doc == nil {
return "", false
}
// We trim spaces primarily because of /**/ style comments, which often have leading space.
text := strings.TrimSpace(doc.Text())
return text, text != ""
}
================================================
FILE: stylecheck/st1021/st1021_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1021
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1021/testdata/go1.0/CheckExportedTypeDocs/CheckExportedTypeDocs.go
================================================
package pkg
// Some type
type t1 struct{}
// Some type //@ diag(`comment on exported type`)
type T2 struct{}
// T3 is amazing
type T3 struct{}
type (
// Some type //@ diag(`comment on exported type`)
T4 struct{}
// The T5 type is amazing
T5 struct{}
// Some type
t6 struct{}
)
// Some types
type (
T7 struct{}
T8 struct{}
)
// Some types
type (
T9 struct{}
)
func fn() {
// Some type
type T1 struct{}
}
//
type T10 struct{} // this is fine, because "no comment" and "empty comment" are treated the same
//
// T11 is amazing.
//
// godoc allows this style, because ast.CommentGroup.Text strips whitespace.
// We currently make no effort to flag it.
//
type T11 struct{}
//some:directive
type T12 struct{} // we pretend that directives aren't part of the doc string, just like godoc in Go 1.15+ does
//some:directive
// T13 is amazing
type T13 struct{}
//some:directive //@ diag(`comment on exported type`)
// Whatever
type T14 struct{}
// A does stuff.
type A struct{}
================================================
FILE: stylecheck/st1022/st1022.go
================================================
package st1022
import (
"fmt"
"go/ast"
"go/token"
"strings"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "ST1022",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "The documentation of an exported variable or constant should start with variable's name",
Text: `Doc comments work best as complete sentences, which
allow a wide variety of automated presentations. The first sentence
should be a one-sentence summary that starts with the name being
declared.
If every doc comment begins with the name of the item it describes,
you can use the \'doc\' subcommand of the \'go\' tool and run the output
through grep.
See https://go.dev/doc/effective_go#commentary for more
information on how to write good documentation.`,
Since: "2020.1",
NonDefault: true,
MergeIf: lint.MergeIfAny,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
var genDecl *ast.GenDecl
fn := func(node ast.Node, push bool) bool {
if !push {
genDecl = nil
return false
}
if code.IsInTest(pass, node) {
return false
}
switch node := node.(type) {
case *ast.GenDecl:
if node.Tok == token.IMPORT {
return false
}
genDecl = node
return true
case *ast.ValueSpec:
if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
// Don't try to guess the user's intention
return false
}
name := node.Names[0].Name
if !ast.IsExported(name) {
return false
}
text, ok := docText(genDecl.Doc)
if !ok {
return false
}
prefix := name + " "
if !strings.HasPrefix(text, prefix) {
kind := "var"
if genDecl.Tok == token.CONST {
kind = "const"
}
report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
}
return false
case *ast.FuncLit, *ast.FuncDecl:
return false
default:
lint.ExhaustiveTypeSwitch(node)
return false
}
}
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
return nil, nil
}
func docText(doc *ast.CommentGroup) (string, bool) {
if doc == nil {
return "", false
}
// We trim spaces primarily because of /**/ style comments, which often have leading space.
text := strings.TrimSpace(doc.Text())
return text, text != ""
}
================================================
FILE: stylecheck/st1022/st1022_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1022
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1022/testdata/go1.0/CheckExportedVarDocs/CheckExportedVarDocs.go
================================================
package pkg
// Whatever
var a int
// Whatever //@ diag(`should be of the form`)
var B int
// Whatever
var (
// Whatever
C int
)
func fn() {
// Whatever
var D int
_ = D
}
//
var E int // this is fine, because "no comment" and "empty comment" are treated the same
//
// F is amazing.
//
// godoc allows this style, because ast.CommentGroup.Text strips whitespace.
// We currently make no effort to flag it.
//
var F int
//some:directive
var G int // we pretend that directives aren't part of the doc string, just like godoc in Go 1.15+ does
//some:directive
// H is amazing
var H int
//some:directive //@ diag(`should be of the form`)
// Whatever
var I int
================================================
FILE: stylecheck/st1023/st1023.go
================================================
package st1023
import (
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/internal/sharedcheck"
)
func init() {
SCAnalyzer.Analyzer.Name = "ST1023"
}
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: sharedcheck.RedundantTypeInDeclarationChecker("should", false),
Doc: &lint.RawDocumentation{
Title: "Redundant type in variable declaration",
Since: "2021.1",
NonDefault: true,
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
================================================
FILE: stylecheck/st1023/st1023_test.go
================================================
// Code generated by generate.go. DO NOT EDIT.
package st1023
import (
"testing"
"honnef.co/go/tools/analysis/lint/testutil"
)
func TestTestdata(t *testing.T) {
testutil.Run(t, SCAnalyzer)
}
================================================
FILE: stylecheck/st1023/testdata/go1.0/CheckRedundantTypeInDeclaration/CheckRedundantTypeInDeclaration.go
================================================
package pkg
import (
"io"
"math"
)
type MyInt int
const X int = 1
const Y = 1
func gen1() int { return 0 }
func gen2() io.ReadCloser { return nil }
func gen3() MyInt { return 0 }
// don't flag global variables
var a int = gen1()
func fn() {
var _ int = gen1() // don't flag blank identifier
var a int = Y // don't flag named untyped constants
var b int = 1 //@ diag(`should omit type int`)
var c int = 1.0 // different default type
var d MyInt = 1 // different default type
var e io.ReadCloser = gen2() //@ diag(`should omit type io.ReadCloser`)
var f io.Reader = gen2() // different interface type
var g float64 = math.Pi // don't flag named untyped constants
var h bool = true //@ diag(`should omit type bool`)
var i string = "" //@ diag(`should omit type string`)
var j MyInt = gen3() //@ diag(`should omit type MyInt`)
var k uint8 = Y // different default type on constant
var l uint8 = (Y + Y) / 2 // different default type on rhs
var m int = (Y + Y) / 2 // complex expression
_, _, _, _, _, _, _, _, _, _, _, _, _ = a, b, c, d, e, f, g, h, i, j, k, l, m
}
================================================
FILE: stylecheck/st1023/testdata/go1.0/CheckRedundantTypeInDeclaration/CheckRedundantTypeInDeclaration.go.golden
================================================
package pkg
import (
"io"
"math"
)
type MyInt int
const X int = 1
const Y = 1
func gen1() int { return 0 }
func gen2() io.ReadCloser { return nil }
func gen3() MyInt { return 0 }
// don't flag global variables
var a int = gen1()
func fn() {
var _ int = gen1() // don't flag blank identifier
var a int = Y // don't flag named untyped constants
var b = 1 //@ diag(`should omit type int`)
var c int = 1.0 // different default type
var d MyInt = 1 // different default type
var e = gen2() //@ diag(`should omit type io.ReadCloser`)
var f io.Reader = gen2() // different interface type
var g float64 = math.Pi // don't flag named untyped constants
var h = true //@ diag(`should omit type bool`)
var i = "" //@ diag(`should omit type string`)
var j = gen3() //@ diag(`should omit type MyInt`)
var k uint8 = Y // different default type on constant
var l uint8 = (Y + Y) / 2 // different default type on rhs
var m int = (Y + Y) / 2 // complex expression
_, _, _, _, _, _, _, _, _, _, _, _, _ = a, b, c, d, e, f, g, h, i, j, k, l, m
}
================================================
FILE: stylecheck/st1023/testdata/go1.0/CheckRedundantTypeInDeclaration_syscall/CheckRedundantTypeInDeclaration_syscall.go
================================================
package pkg
import _ "syscall"
func fn() {
// not flagged because we're importing syscall
var x int = 1
_ = x
}
================================================
FILE: unused/implements.go
================================================
package unused
import (
"go/types"
)
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
func lookupMethod(T *types.Interface, pkg *types.Package, name string) (int, *types.Func) {
if name != "_" {
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if sameId(m, pkg, name) {
return i, m
}
}
}
return -1, nil
}
func sameId(obj types.Object, pkg *types.Package, name string) bool {
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
if name != obj.Name() {
return false
}
// obj.Name == name
if obj.Exported() {
return true
}
// not exported, so packages must be the same (pkg == nil for
// fields in Universe scope; this can only happen for types
// introduced via Eval)
if pkg == nil || obj.Pkg() == nil {
return pkg == obj.Pkg()
}
// pkg != nil && obj.pkg != nil
return pkg.Path() == obj.Pkg().Path()
}
func implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*types.Selection, bool) {
// fast path for common case
if T.Empty() {
return nil, true
}
if ityp, _ := V.Underlying().(*types.Interface); ityp != nil {
// TODO(dh): is this code reachable?
for m := range T.Methods() {
_, obj := lookupMethod(ityp, m.Pkg(), m.Name())
switch {
case obj == nil:
return nil, false
case !types.Identical(obj.Type(), m.Type()):
return nil, false
}
}
return nil, true
}
// A concrete type implements T if it implements all methods of T.
var sels []*types.Selection
var c methodsChecker
for m := range T.Methods() {
sel := msV.Lookup(m.Pkg(), m.Name())
if sel == nil {
return nil, false
}
f, _ := sel.Obj().(*types.Func)
if f == nil {
return nil, false
}
if !c.methodIsCompatible(f, m) {
return nil, false
}
sels = append(sels, sel)
}
return sels, true
}
type methodsChecker struct {
typeParams map[*types.TypeParam]types.Type
}
// Currently, this doesn't support methods like `foo(x []T)`.
func (c *methodsChecker) methodIsCompatible(implFunc *types.Func, interfaceFunc *types.Func) bool {
if types.Identical(implFunc.Type(), interfaceFunc.Type()) {
return true
}
implSig, implOk := implFunc.Type().(*types.Signature)
interfaceSig, interfaceOk := interfaceFunc.Type().(*types.Signature)
if !implOk || !interfaceOk {
// probably not reachable. handle conservatively.
return false
}
if !c.typesAreCompatible(implSig.Params(), interfaceSig.Params()) {
return false
}
if !c.typesAreCompatible(implSig.Results(), interfaceSig.Results()) {
return false
}
return true
}
func (c *methodsChecker) typesAreCompatible(implTypes, interfaceTypes *types.Tuple) bool {
if implTypes.Len() != interfaceTypes.Len() {
return false
}
for i := 0; i < implTypes.Len(); i++ {
if !c.typeIsCompatible(implTypes.At(i).Type(), interfaceTypes.At(i).Type()) {
return false
}
}
return true
}
func (c *methodsChecker) typeIsCompatible(implType, interfaceType types.Type) bool {
if types.Identical(implType, interfaceType) {
return true
}
// We only support trivial use of type parameters. This isn't fully compatible with compiler type checking yet.
tp, ok := interfaceType.(*types.TypeParam)
if !ok {
return false
}
if c.typeParams == nil {
c.typeParams = make(map[*types.TypeParam]types.Type)
}
if c.typeParams[tp] == nil {
if !satisfiesConstraint(implType, tp) {
return false
}
c.typeParams[tp] = implType
return true
}
return types.Identical(c.typeParams[tp], implType)
}
func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool {
bound := tp.Constraint().Underlying().(*types.Interface)
return types.Satisfies(t, bound)
}
================================================
FILE: unused/runtime.go
================================================
package unused
// Functions defined in the Go runtime that may be called through
// compiler magic or via assembly.
var runtimeFuncs = map[string]bool{
// Copied from cmd/compile/internal/typecheck/builtin.go, var runtimeDecls
"newobject": true,
"panicindex": true,
"panicslice": true,
"panicdivide": true,
"panicmakeslicelen": true,
"throwinit": true,
"panicwrap": true,
"gopanic": true,
"gorecover": true,
"goschedguarded": true,
"printbool": true,
"printfloat": true,
"printint": true,
"printhex": true,
"printuint": true,
"printcomplex": true,
"printstring": true,
"printpointer": true,
"printiface": true,
"printeface": true,
"printslice": true,
"printnl": true,
"printsp": true,
"printlock": true,
"printunlock": true,
"concatstring2": true,
"concatstring3": true,
"concatstring4": true,
"concatstring5": true,
"concatstrings": true,
"cmpstring": true,
"intstring": true,
"slicebytetostring": true,
"slicebytetostringtmp": true,
"slicerunetostring": true,
"stringtoslicebyte": true,
"stringtoslicerune": true,
"slicecopy": true,
"slicestringcopy": true,
"decoderune": true,
"countrunes": true,
"convI2I": true,
"convT16": true,
"convT32": true,
"convT64": true,
"convTstring": true,
"convTslice": true,
"convT2E": true,
"convT2Enoptr": true,
"convT2I": true,
"convT2Inoptr": true,
"assertE2I": true,
"assertE2I2": true,
"assertI2I": true,
"assertI2I2": true,
"panicdottypeE": true,
"panicdottypeI": true,
"panicnildottype": true,
"ifaceeq": true,
"efaceeq": true,
"fastrand": true,
"makemap64": true,
"makemap": true,
"makemap_small": true,
"mapaccess1": true,
"mapaccess1_fast32": true,
"mapaccess1_fast64": true,
"mapaccess1_faststr": true,
"mapaccess1_fat": true,
"mapaccess2": true,
"mapaccess2_fast32": true,
"mapaccess2_fast64": true,
"mapaccess2_faststr": true,
"mapaccess2_fat": true,
"mapassign": true,
"mapassign_fast32": true,
"mapassign_fast32ptr": true,
"mapassign_fast64": true,
"mapassign_fast64ptr": true,
"mapassign_faststr": true,
"mapiterinit": true,
"mapdelete": true,
"mapdelete_fast32": true,
"mapdelete_fast64": true,
"mapdelete_faststr": true,
"mapiternext": true,
"mapclear": true,
"makechan64": true,
"makechan": true,
"chanrecv1": true,
"chanrecv2": true,
"chansend1": true,
"closechan": true,
"writeBarrier": true,
"typedmemmove": true,
"typedmemclr": true,
"typedslicecopy": true,
"selectnbsend": true,
"selectnbrecv": true,
"selectnbrecv2": true,
"selectsetpc": true,
"selectgo": true,
"block": true,
"makeslice": true,
"makeslice64": true,
"growslice": true,
"memmove": true,
"memclrNoHeapPointers": true,
"memclrHasPointers": true,
"memequal": true,
"memequal8": true,
"memequal16": true,
"memequal32": true,
"memequal64": true,
"memequal128": true,
"int64div": true,
"uint64div": true,
"int64mod": true,
"uint64mod": true,
"float64toint64": true,
"float64touint64": true,
"float64touint32": true,
"int64tofloat64": true,
"uint64tofloat64": true,
"uint32tofloat64": true,
"complex128div": true,
"racefuncenter": true,
"racefuncenterfp": true,
"racefuncexit": true,
"raceread": true,
"racewrite": true,
"racereadrange": true,
"racewriterange": true,
"msanread": true,
"msanwrite": true,
"x86HasPOPCNT": true,
"x86HasSSE41": true,
"arm64HasATOMICS": true,
"mallocgc": true,
"panicshift": true,
"panicmakeslicecap": true,
"goPanicIndex": true,
"goPanicIndexU": true,
"goPanicSliceAlen": true,
"goPanicSliceAlenU": true,
"goPanicSliceAcap": true,
"goPanicSliceAcapU": true,
"goPanicSliceB": true,
"goPanicSliceBU": true,
"goPanicSlice3Alen": true,
"goPanicSlice3AlenU": true,
"goPanicSlice3Acap": true,
"goPanicSlice3AcapU": true,
"goPanicSlice3B": true,
"goPanicSlice3BU": true,
"goPanicSlice3C": true,
"goPanicSlice3CU": true,
"goPanicSliceConvert": true,
"printuintptr": true,
"convT": true,
"convTnoptr": true,
"makeslicecopy": true,
"unsafeslicecheckptr": true,
"panicunsafeslicelen": true,
"panicunsafeslicenilptr": true,
"unsafestringcheckptr": true,
"panicunsafestringlen": true,
"panicunsafestringnilptr": true,
"mulUintptr": true,
"memequal0": true,
"f32equal": true,
"f64equal": true,
"c64equal": true,
"c128equal": true,
"strequal": true,
"interequal": true,
"nilinterequal": true,
"memhash": true,
"memhash0": true,
"memhash8": true,
"memhash16": true,
"memhash32": true,
"memhash64": true,
"memhash128": true,
"f32hash": true,
"f64hash": true,
"c64hash": true,
"c128hash": true,
"strhash": true,
"interhash": true,
"nilinterhash": true,
"int64tofloat32": true,
"uint64tofloat32": true,
"getcallerpc": true,
"getcallersp": true,
"msanmove": true,
"asanread": true,
"asanwrite": true,
"checkptrAlignment": true,
"checkptrArithmetic": true,
"libfuzzerTraceCmp1": true,
"libfuzzerTraceCmp2": true,
"libfuzzerTraceCmp4": true,
"libfuzzerTraceCmp8": true,
"libfuzzerTraceConstCmp1": true,
"libfuzzerTraceConstCmp2": true,
"libfuzzerTraceConstCmp4": true,
"libfuzzerTraceConstCmp8": true,
"libfuzzerHookStrCmp": true,
"libfuzzerHookEqualFold": true,
"addCovMeta": true,
"x86HasFMA": true,
"armHasVFPv4": true,
// Extracted from assembly code in the standard library, with the exception of the runtime package itself
"abort": true,
"aeshashbody": true,
"args": true,
"asminit": true,
"badctxt": true,
"badmcall2": true,
"badmcall": true,
"badmorestackg0": true,
"badmorestackgsignal": true,
"badsignal2": true,
"callbackasm1": true,
"callCfunction": true,
"cgocallback_gofunc": true,
"cgocallbackg": true,
"checkgoarm": true,
"check": true,
"debugCallCheck": true,
"debugCallWrap": true,
"emptyfunc": true,
"entersyscall": true,
"exit": true,
"exits": true,
"exitsyscall": true,
"externalthreadhandler": true,
"findnull": true,
"goexit1": true,
"gostring": true,
"i386_set_ldt": true,
"_initcgo": true,
"init_thread_tls": true,
"ldt0setup": true,
"libpreinit": true,
"load_g": true,
"morestack": true,
"mstart": true,
"nacl_sysinfo": true,
"nanotimeQPC": true,
"nanotime": true,
"newosproc0": true,
"newproc": true,
"newstack": true,
"noted": true,
"nowQPC": true,
"osinit": true,
"printf": true,
"racecallback": true,
"reflectcallmove": true,
"reginit": true,
"rt0_go": true,
"save_g": true,
"schedinit": true,
"setldt": true,
"settls": true,
"sighandler": true,
"sigprofNonGo": true,
"sigtrampgo": true,
"_sigtramp": true,
"sigtramp": true,
"stackcheck": true,
"syscall_chdir": true,
"syscall_chroot": true,
"syscall_close": true,
"syscall_dup2": true,
"syscall_execve": true,
"syscall_exit": true,
"syscall_fcntl": true,
"syscall_forkx": true,
"syscall_gethostname": true,
"syscall_getpid": true,
"syscall_ioctl": true,
"syscall_pipe": true,
"syscall_rawsyscall6": true,
"syscall_rawSyscall6": true,
"syscall_rawsyscall": true,
"syscall_RawSyscall": true,
"syscall_rawsysvicall6": true,
"syscall_setgid": true,
"syscall_setgroups": true,
"syscall_setpgid": true,
"syscall_setsid": true,
"syscall_setuid": true,
"syscall_syscall6": true,
"syscall_syscall": true,
"syscall_Syscall": true,
"syscall_sysvicall6": true,
"syscall_wait4": true,
"syscall_write": true,
"traceback": true,
"tstart": true,
"usplitR0": true,
"wbBufFlush": true,
"write": true,
// Other runtime functions that can get called in non-standard ways
"bgsweep": true,
"memhash_varlen": true,
"strhashFallback": true,
"asanregisterglobals": true,
"cgoUse": true,
"cgoCheckPointer": true,
"cgoCheckResult": true,
"_cgo_panic_internal": true,
"addExitHook": true,
}
var runtimeCoverageFuncs = map[string]bool{
"initHook": true,
"markProfileEmitted": true,
"processCoverTestDir": true,
}
================================================
FILE: unused/serialize.go
================================================
package unused
import (
"fmt"
"go/token"
"os"
"golang.org/x/tools/go/types/objectpath"
)
type ObjectPath struct {
PkgPath string
ObjPath objectpath.Path
}
// XXX make sure that node 0 always exists and is always the root
type SerializedGraph struct {
nodes []Node
nodesByPath map[ObjectPath]NodeID
// XXX deduplicating on position is dubious for `switch x := foo.(type)`, where x will be declared many times for
// the different types, but all at the same position. On the other hand, merging these nodes is probably fine.
nodesByPosition map[token.Position]NodeID
}
func trace(f string, args ...any) {
fmt.Fprintf(os.Stderr, f, args...)
fmt.Fprintln(os.Stderr)
}
func (g *SerializedGraph) Merge(nodes []Node) {
if g.nodesByPath == nil {
g.nodesByPath = map[ObjectPath]NodeID{}
}
if g.nodesByPosition == nil {
g.nodesByPosition = map[token.Position]NodeID{}
}
if len(g.nodes) == 0 {
// Seed nodes with a root node
g.nodes = append(g.nodes, Node{})
}
// OPT(dh): reuse storage between calls to Merge
remapping := make([]NodeID, len(nodes))
// First pass: compute remapping of IDs of to-be-merged nodes
for _, n := range nodes {
// XXX Column is never 0. it's 1 if there is no column information in the export data. which sucks, because
// objects can also genuinely be in column 1.
if n.id != 0 && n.obj.Path == (ObjectPath{}) && n.obj.Position.Column == 0 {
// If the object has no path, then it couldn't have come from export data, which means it needs to have full
// position information including a column.
panic(fmt.Sprintf("object %q has no path but also no column information", n.obj.Name))
}
if orig, ok := g.nodesByPath[n.obj.Path]; ok {
// We already have a node for this object
trace("deduplicating %d -> %d based on path %s", n.id, orig, n.obj.Path)
remapping[n.id] = orig
} else if orig, ok := g.nodesByPosition[n.obj.Position]; ok && n.obj.Position.Column != 0 {
// We already have a node for this object
trace("deduplicating %d -> %d based on position %s", n.id, orig, n.obj.Position)
remapping[n.id] = orig
} else {
// This object is new to us; change ID to avoid collision
newID := NodeID(len(g.nodes))
trace("new node, remapping %d -> %d", n.id, newID)
remapping[n.id] = newID
g.nodes = append(g.nodes, Node{
id: newID,
obj: n.obj,
uses: make([]NodeID, 0, len(n.uses)),
owns: make([]NodeID, 0, len(n.owns)),
})
if n.id == 0 {
// Our root uses all the roots of the subgraphs
g.nodes[0].uses = append(g.nodes[0].uses, newID)
}
if n.obj.Path != (ObjectPath{}) {
g.nodesByPath[n.obj.Path] = newID
}
if n.obj.Position.Column != 0 {
g.nodesByPosition[n.obj.Position] = newID
}
}
}
// Second step: apply remapping
for _, n := range nodes {
n.id = remapping[n.id]
for i := range n.uses {
n.uses[i] = remapping[n.uses[i]]
}
for i := range n.owns {
n.owns[i] = remapping[n.owns[i]]
}
g.nodes[n.id].uses = append(g.nodes[n.id].uses, n.uses...)
g.nodes[n.id].owns = append(g.nodes[n.id].owns, n.owns...)
}
}
================================================
FILE: unused/testdata/src/example.com/alias/alias.go
================================================
package main
import "net/http"
type t1 struct{} //@ used("t1", true)
type t2 struct{} //@ used("t2", false)
type t3 struct{} //@ used("t3", true)
type alias1 = t1 //@ used("alias1", true)
type alias2 = t2 //@ used("alias2", false)
type alias3 = t3 //@ used("alias3", false)
type alias4 = int //@ used("alias4", false)
func main() { //@ used("main", true)
var _ alias1 //@ used("_", true)
var _ t3 //@ used("_", true)
}
type t4 struct { //@ used("t4", true)
x int //@ used("x", true)
}
func (t4) foo() {} //@ used("foo", true)
//lint:ignore U1000 alias5 is ignored, which also ignores t4
type alias5 = t4 //@ used("alias5", true)
//lint:ignore U1000 alias6 is ignored, and we don't incorrectly try to include http.Server's fields and methods in the graph
type alias6 = http.Server //@ used("alias6", true)
//lint:ignore U1000 aliases don't have to be to named types
type alias7 = struct { //@ used("alias7", true)
x int //@ used("x", true)
}
================================================
FILE: unused/testdata/src/example.com/anonymous/anonymous.go
================================================
package pkg
import "fmt"
type Node interface { //@ used("Node", true)
position() int //@ used("position", true)
}
type noder struct{} //@ used("noder", true)
func (noder) position() int { panic("unreachable") } //@ used("position", true)
func Fn() { //@ used("Fn", true)
nodes := []Node{struct { //@ used("nodes", true)
noder //@ used("noder", true)
}{}}
fmt.Println(nodes)
}
================================================
FILE: unused/testdata/src/example.com/blank/blank.go
================================================
package pkg
import _ "fmt"
type t1 struct{} //@ used("t1", false)
type t2 struct { //@ used("t2", true)
_ int //@ used("_", true)
}
type t3 struct{} //@ used("t3", true)
type t4 struct{} //@ used("t4", true)
type t5 struct{} //@ used("t5", true)
var _ = t2{} //@ used("_", true)
func fn1() { //@ used("fn1", false)
_ = t1{}
var _ = t1{} //@ quiet("_")
}
func fn2() { //@ used("fn2", true)
_ = t3{}
var _ t4 //@ used("_", true)
var _ *t5 = nil //@ used("_", true)
}
func init() { //@ used("init", true)
fn2()
}
func _() {} //@ used("_", true)
type _ struct{} //@ used("_", true)
================================================
FILE: unused/testdata/src/example.com/blank-function-parameter/blank.go
================================================
package pkg
type customType int //@ used("customType", true)
func Foo(customType) {} //@ used("Foo", true), used("", true)
func bar(customType) {} //@ used("bar", false), quiet("")
================================================
FILE: unused/testdata/src/example.com/cgo/cgo.go
================================================
package pkg
//go:cgo_export_dynamic
func foo() {} //@ used("foo", true)
func bar() {} //@ used("bar", false)
================================================
FILE: unused/testdata/src/example.com/constexpr/constexpr.go
================================================
package pkg
import (
"io"
"unsafe"
)
// https://staticcheck.dev/issues/812
var (
w io.Writer //@ used("w", true)
sz = unsafe.Sizeof(w) //@ used("sz", true)
)
var _ = sz //@ used("_", true)
type t struct { //@ used("t", true)
F int //@ used("F", true)
}
const S = unsafe.Sizeof(t{}) //@ used("S", true)
================================================
FILE: unused/testdata/src/example.com/consts/consts.go
================================================
package pkg
const c1 = 1 //@ used("c1", true)
const c2 = 1 //@ used("c2", true)
const c3 = 1 //@ used("c3", true)
const c4 = 1 //@ used("c4", true)
const C5 = 1 //@ used("C5", true)
const (
c6 = 0 //@ used("c6", true)
c7 //@ used("c7", true)
c8 //@ used("c8", true)
c9 //@ used("c9", false)
c10 //@ used("c10", false)
c11 //@ used("c11", false)
)
// constants named _ are used, but are not part of constant groups
const (
c12 = 0 //@ used("c12", false)
_ //@ used("_", true)
c13 //@ used("c13", false)
)
var _ = []int{c3: 1} //@ used("_", true)
type T1 struct { //@ used("T1", true)
F1 [c1]int //@ used("F1", true)
}
func init() { //@ used("init", true)
_ = []int{c2: 1}
var _ [c4]int //@ used("_", true)
_ = c7
}
func Fn() { //@ used("Fn", true)
const X = 1 //@ used("X", false)
}
================================================
FILE: unused/testdata/src/example.com/conversion/conversion.go
================================================
package pkg
import (
"compress/flate"
"unsafe"
)
type t1 struct { //@ used("t1", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t2 struct { //@ used("t2", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t3 struct { //@ used("t3", true)
a int //@ used("a", true)
b int //@ used("b", false)
}
type t4 struct { //@ used("t4", true)
a int //@ used("a", true)
b int //@ used("b", false)
}
type t5 struct { //@ used("t5", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t6 struct { //@ used("t6", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t7 struct { //@ used("t7", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t8 struct { //@ used("t8", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
type t9 struct { //@ used("t9", true)
Offset int64 //@ used("Offset", true)
Err error //@ used("Err", true)
}
type t10 struct { //@ used("t10", true)
a int //@ used("a", true)
b int //@ used("b", true)
}
func fn() { //@ used("fn", true)
// All fields in t2 used because they're initialised in t1
v1 := t1{0, 1} //@ used("v1", true)
v2 := t2(v1) //@ used("v2", true)
_ = v2
// Field b isn't used by anyone
v3 := t3{} //@ used("v3", true)
v4 := t4(v3) //@ used("v4", true)
println(v3.a)
_ = v4
// Both fields are used
v5 := t5{} //@ used("v5", true)
v6 := t6(v5) //@ used("v6", true)
println(v5.a)
println(v6.b)
v7 := &t7{} //@ used("v7", true)
println(v7.a)
println(v7.b)
v8 := (*t8)(v7) //@ used("v8", true)
_ = v8
vb := flate.ReadError{} //@ used("vb", true)
v9 := t9(vb) //@ used("v9", true)
_ = v9
// All fields are used because this is an unsafe conversion
var b []byte //@ used("b", true)
v10 := (*t10)(unsafe.Pointer(&b[0])) //@ used("v10", true)
_ = v10
}
func init() { fn() } //@ used("init", true)
================================================
FILE: unused/testdata/src/example.com/cyclic/cyclic.go
================================================
package pkg
func a() { //@ used("a", false)
b()
}
func b() { //@ used("b", false)
a()
}
================================================
FILE: unused/testdata/src/example.com/defer/defer.go
================================================
package pkg
type t struct{} //@ used("t", true)
func (t) fn1() {} //@ used("fn1", true)
func (t) fn2() {} //@ used("fn2", true)
func fn1() {} //@ used("fn1", true)
func fn2() {} //@ used("fn2", true)
func Fn() { //@ used("Fn", true)
var v t //@ used("v", true)
defer fn1()
defer v.fn1()
go fn2()
go v.fn2()
}
================================================
FILE: unused/testdata/src/example.com/elem/elem.go
================================================
// Test of field usage detection
package pkg
type t15 struct { //@ used("t15", true)
f151 int //@ used("f151", true)
}
type a2 [1]t15 //@ used("a2", true)
type t16 struct{} //@ used("t16", true)
type a3 [1][1]t16 //@ used("a3", true)
func foo() { //@ used("foo", true)
_ = a2{0: {1}}
_ = a3{{{}}}
}
func init() { foo() } //@ used("init", true)
================================================
FILE: unused/testdata/src/example.com/embedded_call/embedded_call.go
================================================
package pkg
var t1 struct { //@ used("t1", true)
t2 //@ used("t2", true)
t3 //@ used("t3", true)
t4 //@ used("t4", true)
}
type t2 struct{} //@ used("t2", true)
type t3 struct{} //@ used("t3", true)
type t4 struct { //@ used("t4", true)
t5 //@ used("t5", true)
}
type t5 struct{} //@ used("t5", true)
func (t2) foo() {} //@ used("foo", true)
func (t3) bar() {} //@ used("bar", true)
func (t5) baz() {} //@ used("baz", true)
func init() { //@ used("init", true)
t1.foo()
_ = t1.bar
t1.baz()
}
================================================
FILE: unused/testdata/src/example.com/embedding/embedding.go
================================================
package pkg
type I interface { //@ used("I", true)
f1() //@ used("f1", true)
f2() //@ used("f2", true)
}
func init() { //@ used("init", true)
var _ I //@ used("_", true)
}
type t1 struct{} //@ used("t1", true)
type T2 struct { //@ used("T2", true)
t1 //@ used("t1", true)
}
func (t1) f1() {} //@ used("f1", true)
func (T2) f2() {} //@ used("f2", true)
func Fn() { //@ used("Fn", true)
var v T2 //@ used("v", true)
_ = v.t1
}
type I2 interface { //@ used("I2", true)
f3() //@ used("f3", true)
f4() //@ used("f4", true)
}
type t3 struct{} //@ used("t3", true)
type t4 struct { //@ used("t4", true)
x int //@ used("x", false)
y int //@ used("y", false)
t3 //@ used("t3", true)
}
func (*t3) f3() {} //@ used("f3", true)
func (*t4) f4() {} //@ used("f4", true)
func init() { //@ used("init", true)
var i I2 = &t4{} //@ used("i", true)
i.f3()
i.f4()
}
type i3 interface { //@ used("i3", true)
F() //@ used("F", true)
}
type I4 interface { //@ used("I4", true)
i3
}
type T5 struct { //@ used("T5", true)
t6 //@ used("t6", true)
}
type t6 struct { //@ used("t6", true)
F int //@ used("F", true)
}
type t7 struct { //@ used("t7", true)
X int //@ used("X", true)
}
type t8 struct { //@ used("t8", true)
t7 //@ used("t7", true)
}
type t9 struct { //@ used("t9", true)
t8 //@ used("t8", true)
}
var _ = t9{} //@ used("_", true)
type t10 struct{} //@ used("t10", true)
func (*t10) Foo() {} //@ used("Foo", true)
type t11 struct { //@ used("t11", true)
t10 //@ used("t10", true)
}
var _ = t11{} //@ used("_", true)
type i5 interface{} //@ used("i5", true)
type I6 interface { //@ used("I6", true)
i5
}
// When recursively looking for embedded exported fields, don't visit top-level type again
type t12 struct { //@ used("t12", true)
*t12 //@ used("t12", false)
F int //@ used("F", true)
}
var _ = t12{} //@ used("_", true)
// embedded fields whose names are exported are used, same as normal exported fields.
type T13 struct { //@ used("T13", true)
T14 //@ used("T14", true)
*T15 //@ used("T15", true)
}
type T14 struct{} //@ used("T14", true)
type T15 struct{} //@ used("T15", true)
================================================
FILE: unused/testdata/src/example.com/embedding-alias/embedding-alias.go
================================================
package pkg
type s1 struct{} //@ used("s1", true)
// Make sure the alias is used, and not just the type it points to.
type a1 = s1 //@ used("a1", true)
type E1 struct { //@ used("E1", true)
a1 //@ used("a1", true)
}
func F1(e E1) { //@ used("F1", true), used("e", true)
_ = e.a1
}
// Make sure fields get used correctly when embedded multiple times
type s2 struct { //@ used("s2", true)
a int //@ used("a", true)
b int //@ used("b", true)
c int //@ used("c", false)
}
type a2 = s2 //@ used("a2", true)
type E2 struct { //@ used("E2", true)
a2 //@ used("a2", true)
}
type E3 struct { //@ used("E3", true)
a2 //@ used("a2", true)
}
func F2(e E2) { //@ used("F2", true), used("e", true)
_ = e.a
}
func F3(e E3) { //@ used("F3", true), used("e", true)
_ = e.b
}
// Make sure embedding aliases to unnamed types works
type a4 = struct { //@ used("a4", true)
a int //@ used("a", true)
b int //@ used("b", true)
c int //@ used("c", false)
}
type E4 struct { //@ used("E4", true)
a4 //@ used("a4", true)
}
type E5 struct { //@ used("E5", true)
a4 //@ used("a4", true)
}
func F4(e E4) { //@ used("F4", true), used("e", true)
_ = e.a
}
func F5(e E5) { //@ used("F5", true), used("e", true)
_ = e.b
}
================================================
FILE: unused/testdata/src/example.com/embedding2/embedding2.go
================================================
package main
type AA interface { //@ used("AA", true)
A() //@ used("A", true)
}
type BB interface { //@ used("BB", true)
AA
}
type CC interface { //@ used("CC", true)
BB
C() //@ used("C", true)
}
func c(cc CC) { //@ used("c", true), used("cc", true)
cc.A()
}
type z struct{} //@ used("z", true)
func (z) A() {} //@ used("A", true)
func (z) B() {} //@ used("B", true)
func (z) C() {} //@ used("C", true)
func main() { //@ used("main", true)
c(z{})
}
================================================
FILE: unused/testdata/src/example.com/exported_fields/exported_fields.go
================================================
package pkg
type t1 struct { //@ used("t1", true)
F1 int //@ used("F1", true)
}
type T2 struct { //@ used("T2", true)
F2 int //@ used("F2", true)
}
var v struct { //@ used("v", true)
T3 //@ used("T3", true)
}
type T3 struct{} //@ used("T3", true)
func (T3) Foo() {} //@ used("Foo", true)
func init() { //@ used("init", true)
v.Foo()
}
func init() { //@ used("init", true)
_ = t1{}
}
type codeResponse struct { //@ used("codeResponse", true)
Tree *codeNode `json:"tree"` //@ used("Tree", true)
}
type codeNode struct { //@ used("codeNode", true)
}
func init() { //@ used("init", true)
_ = codeResponse{}
}
================================================
FILE: unused/testdata/src/example.com/exported_fields_main/exported_fields_main.go
================================================
package main
type t1 struct { //@ used("t1", true)
F1 int //@ used("F1", true)
}
type T2 struct { //@ used("T2", true)
F2 int //@ used("F2", true)
}
func init() { //@ used("init", true)
_ = t1{}
_ = T2{}
}
================================================
FILE: unused/testdata/src/example.com/exported_method_test/exported_method.go
================================================
package pkg
================================================
FILE: unused/testdata/src/example.com/exported_method_test/exported_method_test.go
================================================
package pkg
import (
"bytes"
"io"
"io/ioutil"
"testing"
)
type countReadSeeker struct { //@ used_test("countReadSeeker", true)
io.ReadSeeker //@ used_test("ReadSeeker", true)
N int64 //@ used_test("N", true)
}
func (rs *countReadSeeker) Read(buf []byte) (int, error) { //@ used_test("Read", true), used_test("rs", true), used_test("buf", true)
n, err := rs.ReadSeeker.Read(buf) //@ used_test("n", true), used_test("err", true)
rs.N += int64(n)
return n, err
}
func TestFoo(t *testing.T) { //@ used_test("TestFoo", true), used_test("t", true)
r := bytes.NewReader([]byte("Hello, world!")) //@ used_test("r", true)
cr := &countReadSeeker{ReadSeeker: r} //@ used_test("cr", true)
ioutil.ReadAll(cr)
if cr.N != 13 {
t.Errorf("got %d, want 13", cr.N)
}
}
var sink int //@ used_test("sink", true)
func BenchmarkFoo(b *testing.B) { //@ used_test("BenchmarkFoo", true), used_test("b", true)
for i := 0; i < b.N; i++ { //@ used_test("i", true)
sink = fn()
}
}
func fn() int { return 0 } //@ used_test("fn", true)
================================================
FILE: unused/testdata/src/example.com/fields/fields.go
================================================
// Test of field usage detection
package pkg
type t1 struct { //@ used("t1", true)
f11 int //@ used("f11", true)
f12 int //@ used("f12", true)
}
type t2 struct { //@ used("t2", true)
f21 int //@ used("f21", true)
f22 int //@ used("f22", true)
}
type t3 struct { //@ used("t3", true)
f31 t4 //@ used("f31", true)
}
type t4 struct { //@ used("t4", true)
f41 int //@ used("f41", true)
}
type t5 struct { //@ used("t5", true)
f51 int //@ used("f51", true)
}
type t6 struct { //@ used("t6", true)
f61 int //@ used("f61", true)
}
type t7 struct { //@ used("t7", true)
f71 int //@ used("f71", true)
}
type m1 map[string]t7 //@ used("m1", true)
type t8 struct { //@ used("t8", true)
f81 int //@ used("f81", true)
}
type t9 struct { //@ used("t9", true)
f91 int //@ used("f91", true)
}
type t10 struct { //@ used("t10", true)
f101 int //@ used("f101", true)
}
type t11 struct { //@ used("t11", true)
f111 int //@ used("f111", true)
}
type s1 []t11 //@ used("s1", true)
type t12 struct { //@ used("t12", true)
f121 int //@ used("f121", true)
}
type s2 []t12 //@ used("s2", true)
type t13 struct { //@ used("t13", true)
f131 int //@ used("f131", true)
}
type t14 struct { //@ used("t14", true)
f141 int //@ used("f141", true)
}
type a1 [1]t14 //@ used("a1", true)
type t15 struct { //@ used("t15", true)
f151 int //@ used("f151", true)
}
type a2 [1]t15 //@ used("a2", true)
type t16 struct { //@ used("t16", true)
f161 int //@ used("f161", true)
}
type t17 struct { //@ used("t17", false)
f171 int //@ quiet("f171")
f172 int //@ quiet("f172")
}
type t18 struct { //@ used("t18", true)
f181 int //@ used("f181", true)
f182 int //@ used("f182", false)
f183 int //@ used("f183", false)
}
type t19 struct { //@ used("t19", true)
f191 int //@ used("f191", true)
}
type m2 map[string]t19 //@ used("m2", true)
type t20 struct { //@ used("t20", true)
f201 int //@ used("f201", true)
}
type m3 map[string]t20 //@ used("m3", true)
type t21 struct { //@ used("t21", true)
f211 int //@ used("f211", false)
f212 int //@ used("f212", true)
}
type t22 struct { //@ used("t22", false)
f221 int //@ quiet("f221")
f222 int //@ quiet("f222")
}
func foo() { //@ used("foo", true)
_ = t10{1}
_ = t21{f212: 1}
_ = []t1{{1, 2}}
_ = t2{1, 2}
_ = []struct {
a int //@ used("a", true)
}{{1}}
// XXX
// _ = []struct{ foo struct{ bar int } }{{struct{ bar int }{1}}}
_ = []t1{t1{1, 2}}
_ = []t3{{t4{1}}}
_ = map[string]t5{"a": {1}}
_ = map[t6]string{{1}: "a"}
_ = m1{"a": {1}}
_ = map[t8]t8{{}: {1}}
_ = map[t9]t9{{1}: {}}
_ = s1{{1}}
_ = s2{2: {1}}
_ = [...]t13{{1}}
_ = a1{{1}}
_ = a2{0: {1}}
_ = map[[1]t16]int{{{1}}: 1}
y := struct { //@ used("y", true)
x int //@ used("x", true)
}{}
_ = y
_ = t18{f181: 1}
_ = []m2{{"a": {1}}}
_ = [][]m3{{{"a": {1}}}}
}
func init() { foo() } //@ used("init", true)
func superUnused() { //@ used("superUnused", false)
var _ struct { //@ quiet("_")
x int //@ quiet("x")
}
}
================================================
FILE: unused/testdata/src/example.com/fields/fields_go123.go
================================================
//go:build go1.23
package pkg
import "structs"
// hostLayout isn't used because the fields of hlt5 aren't marked used because
// the hostLayout named type doesn't trigger the structs.HostLayout logic.
type hostLayout structs.HostLayout //@ used("hostLayout", false)
type hostLayoutAlias = structs.HostLayout //@ used("hostLayoutAlias", true)
type hlt1 struct { //@ used("hlt1", true)
_ structs.HostLayout //@ used("_", true)
hlf11 int //@ used("hlf11", true)
}
type hlt2 struct { //@ used("hlt2", true)
structs.HostLayout //@ used("HostLayout", true)
hlf21 int //@ used("hlf21", true)
}
type hlt3 struct { //@ used("hlt3", true)
// Aliases of structs.HostLayout do mark fields used.
hostLayoutAlias //@ used("hostLayoutAlias", true)
hlf31 int //@ used("hlf31", true)
}
type hlt4 struct { //@ used("hlt4", true)
// Embedding a struct that itself has a structs.HostLayout field doesn't
// mark this struct's fields used.
hlt3 //@ used("hlt3", false)
hlf41 int //@ used("hlf41", false)
}
type hlt5 struct { //@ used("hlt5", true)
// Named types with underlying type structs.HostLayout don't mark fields
// used.
hostLayout //@ used("hostLayout", false)
hlf51 int //@ used("hlf51", false)
}
type hlt6 struct { //@ used("hlt6", false)
// The fields may be used, but the overall struct isn't.
_ structs.HostLayout //@ quiet("_")
hlf61 int //@ quiet("hlf61")
}
type hlt7 struct { //@ used("hlt7", true)
// Fields are used recursively, as they affect the layout
_ structs.HostLayout //@ used("_", true)
hlf71 [2]hlt7sub //@ used("hlf71", true)
}
type hlt7sub struct { //@ used("hlt7sub", true)
hlt7sub1 int //@ used("hlt7sub1", true)
}
var _ hlt1 //@ used("_", true)
var _ hlt2 //@ used("_", true)
var _ hlt3 //@ used("_", true)
var _ hlt4 //@ used("_", true)
var _ hlt5 //@ used("_", true)
var _ hlt7 //@ used("_", true)
================================================
FILE: unused/testdata/src/example.com/functions/functions.go
================================================
package main
type state func() state //@ used("state", true)
func a() state { //@ used("a", true)
return a
}
func main() { //@ used("main", true)
st := a //@ used("st", true)
_ = st()
}
type t1 struct{} //@ used("t1", false)
type t2 struct{} //@ used("t2", true)
type t3 struct{} //@ used("t3", true)
func fn1() t1 { return t1{} } //@ used("fn1", false)
func fn2() (x t2) { return } //@ used("fn2", true), used("x", true)
func fn3() *t3 { return nil } //@ used("fn3", true)
func fn4() { //@ used("fn4", true)
const x = 1 //@ used("x", true)
const y = 2 //@ used("y", false)
type foo int //@ used("foo", false)
type bar int //@ used("bar", true)
_ = x
_ = bar(0)
}
func init() { //@ used("init", true)
fn2()
fn3()
fn4()
}
================================================
FILE: unused/testdata/src/example.com/generated/generated.go
================================================
// Code generated by a monkey. DO NOT EDIT.
// https://staticcheck.dev/issues/1333
package pkg
func generated1() {} //@ used("generated1", true)
func generated2() { normal2() } //@ used("generated2", true)
var x = normal4() //@ used("x", true)
================================================
FILE: unused/testdata/src/example.com/generated/normal.go
================================================
package pkg
// https://staticcheck.dev/issues/1333
func normal1() {} //@ used("normal1", false)
func normal2() {} //@ used("normal2", true)
func normal3() int { return 0 } //@ used("normal3", false)
func normal4() int { return 0 } //@ used("normal4", true)
================================================
FILE: unused/testdata/src/example.com/generic-interfaces/generic-interfaces.go
================================================
package pkg
import (
"io"
"os"
)
type I1[T any] interface { //@ used("I1", true), used("T", true)
m1() T //@ used("m1", true)
}
type S1 struct{} //@ used("S1", true)
func (s *S1) m1() string { //@ used("s", true), used("m1", true)
return ""
}
type I2[T any] interface { //@ used("I2", true), used("T", true)
m2(T) //@ used("m2", true)
}
type S2 struct{} //@ used("S2", true)
func (s *S2) m2(p string) { //@ used("s", true), used("p", true), used("m2", true)
return
}
type I3[T any] interface { //@ used("I3", true), used("T", true)
m3(T) T //@ used("m3", true)
}
type S3_1 struct{} //@ used("S3_1", true)
func (s *S3_1) m3(p string) string { //@ used("s", true), used("p", true), used("m3", true)
return ""
}
type S3_2 struct{} //@ used("S3_2", true)
func (s *S3_2) m3(p int) string { //@ quiet("s"), quiet("p"), used("m3", false)
return ""
}
type I4[T, U any] interface { //@ used("I4", true), used("T", true), used("U", true)
m4_1(T) U //@ used("m4_1", true)
m4_2() T //@ used("m4_2", true)
m4_3() U //@ used("m4_3", true)
}
type S4_1 struct{} //@ used("S4_1", true)
func (s *S4_1) m4_1(p string) int { //@ used("s", true), used("p", true), used("m4_1", true)
return 42
}
func (s *S4_1) m4_2() string { //@ used("s", true), used("m4_2", true)
return ""
}
func (s *S4_1) m4_3() int { //@ used("s", true), used("m4_3", true)
return 0
}
type S4_2 struct{} //@ used("S4_2", true)
func (s *S4_2) m4_1(p bool) int { //@ quiet("s"), quiet("p"), used("m4_1", false)
return 0
}
func (s *S4_2) m4_2() string { //@ quiet("s"), used("m4_2", false)
return ""
}
func (s *S4_2) m4_3() int { //@ quiet("s"), used("m4_3", false)
return 0
}
type S4_3 struct{} //@ used("S4_3", true)
func (s *S4_3) m4_1(p string) int { //@ quiet("s"), quiet("p"), used("m4_1", false)
return 42
}
func (s *S4_3) m4_2() int { //@ quiet("s"), used("m4_2", false)
return 0
}
type I5[T comparable, U comparable] interface { //@ used("I5", true), used("T", true), used("U", true)
m5(T) U //@ used("m5", true)
}
type S5_1 struct{} //@ used("S5_1", true)
func (s *S5_1) m5(p string) int { //@ used("s", true), used("p", true), used("m5", true)
return 0
}
type S5_2 struct{} //@ used("S5_2", true)
func (s *S5_2) m5(p any) int { //@ used("s", true), used("p", true), used("m5", true)
return 0
}
type S5_3 struct{} //@ used("S5_3", true)
func (s *S5_3) m5(p string) any { //@ used("s", true), used("p", true), used("m5", true)
return 0
}
type S5_4 struct{} //@ used("S5_4", true)
func (s *S5_4) m5(p string) io.Reader { //@ used("s", true), used("p", true), used("m5", true)
return nil
}
type I6[R io.Reader, W io.Writer] interface { //@ used("I6", true), used("R", true), used("W", true)
m6_1(R) R //@ used("m6_1", true)
m6_2(W) W //@ used("m6_2", true)
}
type S6_1 struct{} //@ used("S6_1", true)
func (s *S6_1) m6_1(p io.Reader) io.Reader { //@ used("s", true), used("p", true), used("m6_1", true)
return p
}
func (s *S6_1) m6_2(p io.Writer) io.Writer { //@ used("s", true), used("p", true), used("m6_2", true)
return p
}
type S6_2 struct{} //@ used("S6_2", true)
func (s *S6_2) m6_1(p io.ReadCloser) io.ReadCloser { //@ used("s", true), used("p", true), used("m6_1", true)
return p
}
func (s *S6_2) m6_2(p io.WriteCloser) io.WriteCloser { //@ used("s", true), used("p", true), used("m6_2", true)
return p
}
type S6_3 struct{} //@ used("S6_3", true)
func (s *S6_3) m6_1(p int) int { //@ quiet("s"), quiet("p"), used("m6_1", false)
return p
}
func (s *S6_3) m6_2(p io.Writer) io.Writer { //@ quiet("s"), quiet("p"), used("m6_2", false)
return p
}
type S6_4 struct{} //@ used("S6_4", true)
func (s *S6_4) m6_1(p *os.File) *os.File { //@ used("s", true), used("p", true), used("m6_1", true)
return p
}
func (s *S6_4) m6_2(p *os.File) *os.File { //@ used("s", true), used("p", true), used("m6_2", true)
return p
}
type S6_5 struct{} //@ used("S6_5", true)
func (s *S6_5) m6_1(p os.File) os.File { //@ quiet("s"), quiet("p"), used("m6_1", false)
return p
}
func (s *S6_5) m6_2(p os.File) os.File { //@ quiet("s"), quiet("p"), used("m6_2", false)
return p
}
type I7[T ~int | ~string] interface { //@ used("I7", true), used("T", true)
m7() T //@ used("m7", true)
}
type S7_1 struct{} //@ used("S7_1", true)
func (s *S7_1) m7() int { //@ used("s", true), used("m7", true)
return 0
}
type S7_2 struct{} //@ used("S7_2", true)
func (s *S7_2) m7() string { //@ used("s", true), used("m7", true)
return ""
}
type S7_3 struct{} //@ used("S7_3", true)
func (s *S7_3) m7() float32 { //@ quiet("s"), used("m7", false)
return 0
}
type S7_4 struct{} //@ used("S7_4", true)
func (s *S7_4) m7() any { //@ quiet("s"), used("m7", false)
return nil
}
type I8[T io.Reader] interface { //@ used("I8", true), used("T", true)
m8() []T //@ used("m8", true)
}
type S8_1 struct{} //@ used("S8_1", true)
// This should be considered as used obviously. It's known incompleteness that we want to improve.
// This test case just verifies that it doesn't crash.
func (s *S8_1) m8() []io.Reader { //@ quiet("s"), used("m8", false)
return nil
}
type S8 struct{} //@ used("S8", true)
type I9[T any] interface { //@ used("I9", true), used("T", true)
make() *T //@ used("make", true)
}
type S9 struct{} //@ used("S9", true)
func (S9) make() *S8 { return nil } //@ used("make", true)
func i9use(i I9[S8]) { i.make() } //@ used("i9use", true), used("i", true)
func init() { //@ used("init", true)
i9use(S9{})
}
================================================
FILE: unused/testdata/src/example.com/ignored/ignored.go
================================================
package pkg
//lint:ignore U1000 consider yourself used
type t1 struct{} //@ used("t1", true)
type t2 struct{} //@ used("t2", true)
type t3 struct{} //@ used("t3", true)
func (t1) fn1() {} //@ used("fn1", true)
func (t1) fn2() {} //@ used("fn2", true)
func (t1) fn3() {} //@ used("fn3", true)
//lint:ignore U1000 be gone
func (t2) fn1() {} //@ used("fn1", true)
func (t2) fn2() {} //@ used("fn2", false)
func (t2) fn3() {} //@ used("fn3", false)
func (t3) fn1() {} //@ used("fn1", false)
func (t3) fn2() {} //@ used("fn2", false)
func (t3) fn3() {} //@ used("fn3", false)
//lint:ignore U1000 consider yourself used
func fn() { //@ used("fn", true)
var _ t2 //@ used("_", true)
var _ t3 //@ used("_", true)
}
//lint:ignore U1000 bye
type t4 struct { //@ used("t4", true)
x int //@ used("x", true)
}
func (t4) bar() {} //@ used("bar", true)
//lint:ignore U1000 consider yourself used
type t5 map[int]struct { //@ used("t5", true)
y int //@ used("y", true)
}
//lint:ignore U1000 consider yourself used
type t6 interface { //@ used("t6", true)
foo() //@ used("foo", true)
}
//lint:ignore U1000 consider yourself used
type t7 = struct { //@ used("t7", true)
z int //@ used("z", true)
}
//lint:ignore U1000 consider yourself used
type t8 struct{} //@ used("t8", true)
func (t8) fn() { //@ used("fn", true)
otherFn()
}
func otherFn() {} //@ used("otherFn", true)
================================================
FILE: unused/testdata/src/example.com/ignored/ignored2.go
================================================
package pkg
func (t1) fn4() {} //@ used("fn4", true)
================================================
FILE: unused/testdata/src/example.com/ignored/ignored3.go
================================================
//lint:file-ignore U1000 consider everything in here used
package pkg
type t9 struct{} //@ used("t9", true)
func (t9) fn1() {} //@ used("fn1", true)
================================================
FILE: unused/testdata/src/example.com/ignored/ignored4.go
================================================
package pkg
func (t9) fn2() {} //@ used("fn2", true)
================================================
FILE: unused/testdata/src/example.com/implicit-conversion/implicit-conversion.go
================================================
package pkg
// https://staticcheck.dev/issues/810
type Thing struct { //@ used("Thing", true)
has struct { //@ used("has", true)
a bool //@ used("a", true)
}
}
func Fn() { //@ used("Fn", true)
type temp struct { //@ used("temp", true)
a bool //@ used("a", true)
}
x := Thing{ //@ used("x", true)
has: temp{true},
}
_ = x
}
================================================
FILE: unused/testdata/src/example.com/increment/increment.go
================================================
package pkg
type T struct { //@ used("T", true), used_test("T", true)
// Writing to fields uses them
f int //@ used("f", true), used_test("f", true)
}
// Not used, v is only written to
var v int //@ used("v", false), used_test("v", false)
func Foo() { //@ used("Foo", true), used_test("Foo", true)
var x T //@ used("x", true), used_test("x", true)
x.f++
v++
}
================================================
FILE: unused/testdata/src/example.com/increment/increment_test.go
================================================
package pkg
// Used because v2 is a sink
var v2 int //@ used_test("v2", true)
func Bar() { //@ used_test("Bar", true)
v2++
}
================================================
FILE: unused/testdata/src/example.com/index-write/write.go
================================================
package pkg
var x int //@ used("x", true)
func Foo() { //@ used("Foo", true)
var s []int //@ used("s", true)
s[x] = 0
}
================================================
FILE: unused/testdata/src/example.com/initializers/initializers.go
================================================
package pkg
// https://staticcheck.dev/issues/507
var x = [3]int{1, 2, 3} //@ used("x", false)
================================================
FILE: unused/testdata/src/example.com/instantiated-functions/instantiated-functions.go
================================================
package pkg
// https://staticcheck.dev/issues/1199
type c1 struct{} //@ used("c1", false)
func Fn[T any]() {} //@ used("Fn", true), used("T", true)
func uncalled() { //@ used("uncalled", false)
Fn[c1]()
}
type c2 struct{} //@ used("c2", true)
func Called() { //@ used("Called", true)
Fn[c2]()
}
================================================
FILE: unused/testdata/src/example.com/interfaces/interfaces.go
================================================
package pkg
type I interface { //@ used("I", true)
fn1() //@ used("fn1", true)
}
type t struct{} //@ used("t", true)
func (t) fn1() {} //@ used("fn1", true)
func (t) fn2() {} //@ used("fn2", false)
func init() { //@ used("init", true)
_ = t{}
}
type I1 interface { //@ used("I1", true)
Foo() //@ used("Foo", true)
}
type I2 interface { //@ used("I2", true)
Foo() //@ used("Foo", true)
bar() //@ used("bar", true)
}
type i3 interface { //@ used("i3", false)
foo() //@ quiet("foo")
bar() //@ quiet("bar")
}
type t1 struct{} //@ used("t1", true)
type t2 struct{} //@ used("t2", true)
type t3 struct{} //@ used("t3", true)
type t4 struct { //@ used("t4", true)
t3 //@ used("t3", true)
}
func (t1) Foo() {} //@ used("Foo", true)
func (t2) Foo() {} //@ used("Foo", true)
func (t2) bar() {} //@ used("bar", true)
func (t3) Foo() {} //@ used("Foo", true)
func (t3) bar() {} //@ used("bar", true)
func Fn() { //@ used("Fn", true)
var v1 t1 //@ used("v1", true)
var v2 t2 //@ used("v2", true)
var v3 t3 //@ used("v3", true)
var v4 t4 //@ used("v4", true)
_ = v1
_ = v2
_ = v3
var x interface{} = v4 //@ used("x", true)
_ = x.(I2)
}
// Text pointer receivers
type T2 struct{} //@ used("T2", true)
func (*T2) fn1() {} //@ used("fn1", true)
func (*T2) fn2() {} //@ used("fn2", false)
================================================
FILE: unused/testdata/src/example.com/interfaces2/interfaces.go
================================================
package pkg
type I interface { //@ used("I", true)
foo() //@ used("foo", true)
}
type T struct{} //@ used("T", true)
func (T) foo() {} //@ used("foo", true)
func (T) bar() {} //@ used("bar", false)
var _ struct { //@ used("_", true)
T //@ used("T", true)
}
================================================
FILE: unused/testdata/src/example.com/issue1289/issue1289.go
================================================
package pkg
func Fn1() { //@ used("Fn1", true)
type Foo[T any] struct { //@ used("Foo", true), used("T", true)
Id int `json:"id"` //@ used("Id", true)
Data T `json:"data"` //@ used("Data", true)
}
type Bar struct { //@ used("Bar", true)
X int `json:"x"` //@ used("X", true)
Y int `json:"y"` //@ used("Y", true)
}
v := Foo[[]Bar]{} //@ used("v", true)
_ = v
}
func Fn2() { //@ used("Fn2", true)
type Foo[T any] struct{} //@ used("Foo", true), used("T", true)
type Bar struct{} //@ used("Bar", true)
v := Foo[[]Bar]{} //@ used("v", true)
_ = v // just use it, but could be some json.Unmarshal, for instance
}
================================================
FILE: unused/testdata/src/example.com/linkname/linkname.go
================================================
package pkg
import _ "unsafe"
//other:directive
//go:linkname ol other4
//go:linkname foo other1
func foo() {} //@ used("foo", true)
//go:linkname bar other2
var bar int //@ used("bar", true)
var (
baz int //@ used("baz", false)
//go:linkname qux other3
qux int //@ used("qux", true)
)
//go:linkname fisk other3
var (
fisk int //@ used("fisk", true)
)
var ol int //@ used("ol", true)
//go:linkname doesnotexist other5
================================================
FILE: unused/testdata/src/example.com/local-type-param-sink/typeparam.go
================================================
package tparamsource
// https://staticcheck.dev/issues/1282
import "reflect"
func TypeOfType[T any]() reflect.Type { //@ used("TypeOfType", true), used("T", true)
var t *T //@ used("t", true)
return reflect.TypeOf(t).Elem()
}
================================================
FILE: unused/testdata/src/example.com/local-type-param-source/typeparam.go
================================================
package tparamsource
// https://staticcheck.dev/issues/1282
import (
"testing"
tparamsink "example.com/local-type-param-sink"
)
func TestFoo(t *testing.T) { //@ used("TestFoo", true), used("t", true)
type EmptyStruct struct{} //@ used("EmptyStruct", true)
_ = tparamsink.TypeOfType[EmptyStruct]()
}
================================================
FILE: unused/testdata/src/example.com/main/main.go
================================================
package main
func Fn1() {} //@ used("Fn1", true)
func Fn2() {} //@ used("Fn2", true)
func fn3() {} //@ used("fn3", false)
const X = 1 //@ used("X", true)
var Y = 2 //@ used("Y", true)
type Z struct{} //@ used("Z", true)
func main() { //@ used("main", true)
Fn1()
}
================================================
FILE: unused/testdata/src/example.com/mapslice/mapslice.go
================================================
package pkg
type M map[int]int //@ used("M", true)
func Fn() { //@ used("Fn", true)
var n M //@ used("n", true)
_ = []M{n}
}
================================================
FILE: unused/testdata/src/example.com/methods/methods.go
================================================
package pkg
type t1 struct{} //@ used("t1", true)
type t2 struct { //@ used("t2", true)
t3 //@ used("t3", true)
}
type t3 struct{} //@ used("t3", true)
func (t1) Foo() {} //@ used("Foo", true)
func (t3) Foo() {} //@ used("Foo", true)
func (t3) foo() {} //@ used("foo", false)
func init() { //@ used("init", true)
_ = t1{}
_ = t2{}
}
================================================
FILE: unused/testdata/src/example.com/named/named.go
================================================
package pkg
type t1 struct{} //@ used("t1", true)
type T2 t1 //@ used("T2", true)
================================================
FILE: unused/testdata/src/example.com/nested/nested.go
================================================
package pkg
type t1 struct{} //@ used("t1", false)
func (t1) fragment() {} //@ used("fragment", false)
func fn1() bool { //@ used("fn1", false)
var v interface{} = t1{} //@ quiet("v")
switch obj := v.(type) { //@ quiet("obj")
case interface {
fragment() //@ quiet("fragment")
}:
obj.fragment()
}
return false
}
type t2 struct{} //@ used("t2", true)
func (t2) fragment() {} //@ used("fragment", true)
func Fn() bool { //@ used("Fn", true)
var v interface{} = t2{} //@ used("v", true)
switch obj := v.(type) { //@ used("obj", true)
case interface {
fragment() //@ used("fragment", true)
}:
obj.fragment()
}
return false
}
func Fn2() bool { //@ used("Fn2", true)
var v interface{} = t2{} //@ used("v", true)
switch obj := v.(type) { //@ used("obj", true)
case interface {
fragment() //@ used("fragment", true)
}:
_ = obj
}
return false
}
================================================
FILE: unused/testdata/src/example.com/nocopy/nocopy.go
================================================
package bar
type myNoCopy1 struct{} //@ used("myNoCopy1", true)
type myNoCopy2 struct{} //@ used("myNoCopy2", true)
type stdlibNoCopy struct{} //@ used("stdlibNoCopy", true)
type locker struct{} //@ used("locker", false)
type someStruct struct { //@ used("someStruct", false)
x int //@ quiet("x")
}
func (myNoCopy1) Lock() {} //@ used("Lock", true)
func (recv myNoCopy2) Lock() {} //@ used("Lock", true), used("recv", true)
func (locker) Lock() {} //@ used("Lock", false)
func (locker) Foobar() {} //@ used("Foobar", false)
func (someStruct) Lock() {} //@ used("Lock", false)
func (stdlibNoCopy) Lock() {} //@ used("Lock", true)
func (stdlibNoCopy) Unlock() {} //@ used("Unlock", true)
type T struct { //@ used("T", true)
noCopy1 myNoCopy1 //@ used("noCopy1", true)
noCopy2 myNoCopy2 //@ used("noCopy2", true)
noCopy3 stdlibNoCopy //@ used("noCopy3", true)
field1 someStruct //@ used("field1", false)
field2 locker //@ used("field2", false)
field3 int //@ used("field3", false)
}
================================================
FILE: unused/testdata/src/example.com/nocopy-main/nocopy-main.go
================================================
package main
type myNoCopy1 struct{} //@ used("myNoCopy1", true)
type myNoCopy2 struct{} //@ used("myNoCopy2", true)
type wrongLocker struct{} //@ used("wrongLocker", false)
type someStruct struct { //@ used("someStruct", false)
x int //@ quiet("x")
}
func (myNoCopy1) Lock() {} //@ used("Lock", true)
func (recv myNoCopy2) Lock() {} //@ used("Lock", true), used("recv", true)
func (wrongLocker) lock() {} //@ used("lock", false)
func (wrongLocker) unlock() {} //@ used("unlock", false)
func (someStruct) Lock() {} //@ used("Lock", false)
type T struct { //@ used("T", true)
noCopy1 myNoCopy1 //@ used("noCopy1", true)
noCopy2 myNoCopy2 //@ used("noCopy2", true)
field1 someStruct //@ used("field1", false)
field2 wrongLocker //@ used("field2", false)
field3 int //@ used("field3", false)
}
func main() { //@ used("main", true)
_ = T{}
}
================================================
FILE: unused/testdata/src/example.com/nocopy-main/stub.go
================================================
package main
================================================
FILE: unused/testdata/src/example.com/parens/parens.go
================================================
package p
func F(c chan bool) { //@ used("F", true), used("c", true)
select {
case (<-c):
case _ = (<-c):
}
}
================================================
FILE: unused/testdata/src/example.com/pointer-type-embedding/pointer-type-embedding.go
================================================
package pkg
func init() { //@ used("init", true)
var p P //@ used("p", true)
_ = p.n
}
type T0 struct { //@ used("T0", true)
m int //@ used("m", false)
n int //@ used("n", true)
}
type T1 struct { //@ used("T1", true)
T0 //@ used("T0", true)
}
type P *T1 //@ used("P", true)
================================================
FILE: unused/testdata/src/example.com/pointers/pointers.go
================================================
package baz
import "fmt"
type Foo interface { //@ used("Foo", true)
bar() //@ used("bar", true)
}
func Bar(f Foo) { //@ used("Bar", true), used("f", true)
f.bar()
}
type Buzz struct{} //@ used("Buzz", true)
func (b *Buzz) bar() { //@ used("bar", true), used("b", true)
fmt.Println("foo bar buzz")
}
================================================
FILE: unused/testdata/src/example.com/quiet/quiet.go
================================================
package pkg
type iface interface { //@ used("iface", false)
foo() //@ quiet("foo")
}
type t1 struct{} //@ used("t1", false)
func (t1) foo() {} //@ used("foo", false)
type t2 struct{} //@ used("t2", true)
func (t t2) bar(arg int) (ret int) { //@ quiet("t"), used("bar", false), quiet("arg"), quiet("ret")
return 0
}
func init() { //@ used("init", true)
_ = t2{}
}
type t3 struct { //@ used("t3", false)
a int //@ quiet("a")
b int //@ quiet("b")
}
type T struct{} //@ used("T", true)
func fn1() { //@ used("fn1", false)
meh := func(arg T) { //@ quiet("meh"), quiet("arg")
}
meh(T{})
}
type localityList []int //@ used("localityList", false)
func (l *localityList) Fn1() {} //@ quiet("l"), used("Fn1", false)
func (l *localityList) Fn2() {} //@ quiet("l"), used("Fn2", false)
================================================
FILE: unused/testdata/src/example.com/selectors/selectors.go
================================================
package pkg
type t struct { //@ used("t", true)
f int //@ used("f", true)
}
func fn(v *t) { //@ used("fn", true), used("v", true)
println(v.f)
}
func init() { //@ used("init", true)
var v t //@ used("v", true)
fn(&v)
}
================================================
FILE: unused/testdata/src/example.com/skipped-test/skipped_test.go
================================================
package pkg
// https://staticcheck.dev/issues/633
import "testing"
func test() { //@ used_test("test", true)
}
func TestSum(t *testing.T) { //@ used_test("TestSum", true), used_test("t", true)
t.Skip("skipping for test")
test()
}
================================================
FILE: unused/testdata/src/example.com/switch_interface/switch_interface.go
================================================
package pkg
type t struct{} //@ used("t", true)
func (t) fragment() {} //@ used("fragment", true)
func fn() bool { //@ used("fn", true)
var v interface{} = t{} //@ used("v", true)
switch obj := v.(type) { //@ used("obj", true)
case interface {
fragment() //@ used("fragment", true)
}:
obj.fragment()
}
return false
}
var x = fn() //@ used("x", true)
var _ = x //@ used("_", true)
================================================
FILE: unused/testdata/src/example.com/tests/tests.go
================================================
package pkg
func fn() {} //@ used("fn", false), used_test("fn", true)
================================================
FILE: unused/testdata/src/example.com/tests/tests_test.go
================================================
package pkg
import "testing"
func TestFn(t *testing.T) { //@ used_test("TestFn", true), used_test("t", true)
fn()
}
================================================
FILE: unused/testdata/src/example.com/tests-main/main.go
================================================
package main
================================================
FILE: unused/testdata/src/example.com/tests-main/main_test.go
================================================
package main
import (
"testing"
)
type t1 struct{} //@ used_test("t1", true)
func TestFoo(t *testing.T) { //@ used_test("TestFoo", true), used_test("t", true)
_ = t1{}
}
================================================
FILE: unused/testdata/src/example.com/type-dedup/dedup.go
================================================
package pkg
type t1 struct { //@ used("t1", true)
a int //@ used("a", true)
b int //@ used("b", false)
}
type t2 struct { //@ used("t2", true)
a int //@ used("a", false)
b int //@ used("b", true)
}
func Fn() { //@ used("Fn", true)
x := t1{} //@ used("x", true)
y := t2{} //@ used("y", true)
println(x.a)
println(y.b)
}
================================================
FILE: unused/testdata/src/example.com/type-dedup2/dedup.go
================================================
package pkg
func fn1(t struct { //@ used("fn1", true), used("t", true)
a int //@ used("a", true)
b int //@ used("b", true)
}) {
println(t.a)
fn2(t)
}
func fn2(t struct { //@ used("fn2", true), used("t", true)
a int //@ used("a", true)
b int //@ used("b", true)
}) {
println(t.b)
}
func Fn() { //@ used("Fn", true)
fn1(struct {
a int //@ used("a", true)
b int //@ used("b", true)
}{})
}
================================================
FILE: unused/testdata/src/example.com/type-dedup3/dedup.go
================================================
package pkg
func fn1(t struct { //@ used("fn1", true), used("t", true)
a int //@ used("a", true)
b int //@ used("b", true)
}) {
fn2(t)
}
func fn2(t struct { //@ used("fn2", true), used("t", true)
a int //@ used("a", true)
b int //@ used("b", true)
}) {
println(t.a)
println(t.b)
}
func Fn() { //@ used("Fn", true)
fn1(struct {
a int //@ used("a", true)
b int //@ used("b", true)
}{1, 2})
}
================================================
FILE: unused/testdata/src/example.com/typeparams/typeparams.go
================================================
//go:build go1.18
package pkg
type c1 struct{} //@ used("c1", true)
type c2 struct{} //@ used("c2", true)
type c3 struct{} //@ used("c3", true)
type c4 struct{} //@ used("c4", true)
type c5 struct{} //@ used("c5", true)
type c6 struct{} //@ used("c6", true)
type c7 struct{} //@ used("c7", true)
type c8 struct{} //@ used("c8", false)
type c9 struct{} //@ used("c9", true)
type S1[T c1] struct{} //@ used("S1", true), used("T", true)
type S2[T any] struct{} //@ used("S2", true), used("T", true)
type S3 S2[c2] //@ used("S3", true)
type I interface { //@ used("I", true)
c3 | c9
}
func Fn1[T c4]() {} //@ used("Fn1", true), used("T", true)
func fn2[T any]() {} //@ used("fn2", true), used("T", true)
func Fn5[T any]() {} //@ used("Fn5", true), used("T", true)
func Fn6[T any]() {} //@ used("Fn6", true), used("T", true)
var _ = fn2[c5] //@ used("_", true)
func Fn3() { //@ used("Fn3", true)
Fn5[c6]()
_ = S2[c7]{}
}
func uncalled() { //@ used("uncalled", false)
_ = Fn6[c8]
}
type S4[T any] struct{} //@ used("S4", true), used("T", true)
func (S4[T]) usedGenerically() {} //@ used("usedGenerically", true), used("T", true)
func (S4[T]) usedInstantiated() {} //@ used("usedInstantiated", true), used("T", true)
func (recv S4[T]) Exported() { //@ used("Exported", true), used("recv", true), used("T", true)
recv.usedGenerically()
}
func (S4[T]) unused() {} //@ used("unused", false), quiet("T")
func Fn4() { //@ used("Fn4", true)
var x S4[int] //@ used("x", true)
x.usedInstantiated()
}
type s1[T any] struct{} //@ used("s1", false), quiet("T")
func (recv s1[a]) foo() { recv.foo(); recv.bar(); recv.baz() } //@ used("foo", false), quiet("recv"), quiet("a")
func (recv s1[b]) bar() { recv.foo(); recv.bar(); recv.baz() } //@ used("bar", false), quiet("recv"), quiet("b")
func (recv s1[c]) baz() { recv.foo(); recv.bar(); recv.baz() } //@ used("baz", false), quiet("recv"), quiet("c")
func fn7[T interface { //@ used("fn7", false), quiet("T")
foo() //@ quiet("foo")
}]() {
}
func fn8[T struct { //@ used("fn8", false), quiet("T")
x int //@ quiet("x")
}]() {
}
func Fn9[T struct { //@ used("Fn9", true), used("T", true)
X *s2 //@ used("X", true)
}]() {
}
type s2 struct{} //@ used("s2", true)
func fn10[E any](x []E) {} //@ used("fn10", false), quiet("E"), quiet("x")
type Tree[T any] struct { //@ used("Tree", true), used("T", true)
Root *Node[T] //@ used("Root", true)
}
type Node[T any] struct { //@ used("Node", true), used("T", true)
Tree *Tree[T] //@ used("Tree", true)
}
type foo struct{} //@ used("foo", true)
type Bar *Node[foo] //@ used("Bar", true)
func (n Node[T]) anyMethod() {} //@ used("anyMethod", false), quiet("n"), quiet("T")
func fn11[T ~struct { //@ used("fn11", false), quiet("T")
Field int //@ quiet("Field")
}]() {
// don't crash because of the composite literal
_ = T{Field: 42}
}
type convertGeneric1 struct { //@ used("convertGeneric1", true)
field int //@ used("field", true)
}
type convertGeneric2 struct { //@ used("convertGeneric2", true)
field int //@ used("field", true)
}
// mark field as used
var _ = convertGeneric1{}.field //@ used("_", true)
func Fn12[T1 convertGeneric1, T2 convertGeneric2](a T1) { //@ used("Fn12", true), used("T1", true), used("T2", true), used("a", true)
_ = T2(a) // conversion marks T2.field as used
}
type S5[A, B any] struct{} //@ used("S5", true), used("A", true), used("B", true)
type S6 S5[int, string] //@ used("S6", true)
================================================
FILE: unused/testdata/src/example.com/typeparams/typeparams_17.go
================================================
//go:build !go1.18
package pkg
================================================
FILE: unused/testdata/src/example.com/types/types.go
================================================
package pkg
import "reflect"
type wkt interface { //@ used("wkt", true)
XXX_WellKnownType() string //@ used("XXX_WellKnownType", true)
}
var typeOfWkt = reflect.TypeOf((*wkt)(nil)).Elem() //@ used("typeOfWkt", true)
func Fn() { //@ used("Fn", true)
_ = typeOfWkt
}
type t *int //@ used("t", true)
var _ t //@ used("_", true)
================================================
FILE: unused/testdata/src/example.com/unsafe-recursive/conversion.go
================================================
package pkg
// https://staticcheck.dev/issues/1249
import "unsafe"
type t1 struct { //@ used("t1", true)
f1 int //@ used("f1", true)
f2 t2 //@ used("f2", true)
f3 *t3 //@ used("f3", true)
t4 //@ used("t4", true)
*t5 //@ used("t5", true)
f6 [5]t6 //@ used("f6", true)
f7 [5][5]t7 //@ used("f7", true)
f8 nt1 //@ used("f8", true)
f9 nt2 //@ used("f9", true)
}
type nt1 *t8 //@ used("nt1", true)
type nt2 [4]t9 //@ used("nt2", true)
type t2 struct{ f int } //@ used("t2", true), used("f", true)
type t3 struct{ f int } //@ used("t3", true), used("f", false)
type t4 struct{ f int } //@ used("t4", true), used("f", true)
type t5 struct{ f int } //@ used("t5", true), used("f", false)
type t6 struct{ f int } //@ used("t6", true), used("f", true)
type t7 struct{ f int } //@ used("t7", true), used("f", true)
type t8 struct{ f int } //@ used("t8", true), used("f", false)
type t9 struct{ f int } //@ used("t9", true), used("f", true)
func Foo(x t1) { //@ used("Foo", true), used("x", true)
_ = unsafe.Pointer(&x)
}
================================================
FILE: unused/testdata/src/example.com/unused-argument/unused-argument.go
================================================
package main
type t1 struct{} //@ used("t1", true)
type t2 struct{} //@ used("t2", true)
func (t1) foo(arg *t2) {} //@ used("foo", true), used("arg", true)
func init() { //@ used("init", true)
t1{}.foo(nil)
}
================================================
FILE: unused/testdata/src/example.com/unused_type/unused_type.go
================================================
package pkg
type t1 struct{} //@ used("t1", false)
func (t1) Fn() {} //@ used("Fn", false)
type t2 struct{} //@ used("t2", true)
func (*t2) Fn() {} //@ used("Fn", true)
func init() { //@ used("init", true)
(*t2).Fn(nil)
}
type t3 struct{} //@ used("t3", false)
func (t3) fn() //@ used("fn", false)
================================================
FILE: unused/testdata/src/example.com/variables/variables.go
================================================
package pkg
var a byte //@ used("a", true)
var b [16]byte //@ used("b", true)
type t1 struct{} //@ used("t1", true)
type t2 struct{} //@ used("t2", true)
type t3 struct{} //@ used("t3", true)
type t4 struct{} //@ used("t4", true)
type t5 struct{} //@ used("t5", true)
type iface interface{} //@ used("iface", true)
var x t1 //@ used("x", true)
var y = t2{} //@ used("y", true)
var j = t3{} //@ used("j", true)
var k = t4{} //@ used("k", true)
var l iface = t5{} //@ used("l", true)
func Fn() { //@ used("Fn", true)
println(a)
_ = b[:]
_ = x
_ = y
_ = j
_ = k
_ = l
}
================================================
FILE: unused/testdata/src/example.com/variables/vartype.go
================================================
package pkg
type t181025 struct{} //@ used("t181025", true)
func (t181025) F() {} //@ used("F", true)
// package-level variable after function declaration used to trigger a
// bug in unused.
var V181025 t181025 //@ used("V181025", true)
================================================
FILE: unused/unused.go
================================================
// Package unused contains code for finding unused code.
package unused
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"io"
"reflect"
"slices"
"strings"
"honnef.co/go/tools/analysis/facts/directives"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/go/types/typeutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/types/objectpath"
)
// OPT(dh): don't track local variables that can't have any interesting outgoing edges. For example, using a local
// variable of type int is meaningless; we don't care if `int` is used or not.
//
// Note that we do have to track variables with for example array types, because the array type could have involved a
// named constant.
//
// We probably have different culling needs depending on the mode of operation, too. If we analyze multiple packages in
// one graph (unused's "whole program" mode), we could remove further useless edges (e.g. into nodes that themselves
// have no outgoing edges and aren't meaningful objects on their own) after having analyzed a package, to keep the
// in-memory representation small on average. If we only analyze a single package, that step would just waste cycles, as
// we're about to throw the entire graph away, anyway.
// TODO(dh): currently, types use methods that implement interfaces. However, this makes a method used even if the
// relevant interface is never used. What if instead interfaces used those methods? Right now we cannot do that, because
// methods use their receivers, so using a method uses the type. But do we need that edge? Is there a way to refer to a
// method without explicitly mentioning the type somewhere? If not, the edge from method to receiver is superfluous.
// XXX vet all code for proper use of core types
// TODO(dh): we cannot observe function calls in assembly files.
/*
This overview is true when using the default options. Different options may change individual behaviors.
- packages use:
- (1.1) exported named types
- (1.2) exported functions (but not methods!)
- (1.3) exported variables
- (1.4) exported constants
- (1.5) init functions
- (1.6) functions exported to cgo
- (1.7) the main function iff in the main package
- (1.8) symbols linked via go:linkname
- (1.9) objects in generated files
- named types use:
- (2.1) exported methods
- (2.2) the type they're based on
- (2.5) all their type parameters. Unused type parameters are probably useless, but they're a brand new feature and we
don't want to introduce false positives because we couldn't anticipate some novel use-case.
- (2.6) all their type arguments
- functions use:
- (4.1) all their arguments, return parameters and receivers
- (4.2) anonymous functions defined beneath them
- (4.3) closures and bound methods.
this implements a simplified model where a function is used merely by being referenced, even if it is never called.
that way we don't have to keep track of closures escaping functions.
- (4.4) functions they return. we assume that someone else will call the returned function
- (4.5) functions/interface methods they call
- (4.6) types they instantiate or convert to
- (4.7) fields they access
- (4.9) package-level variables they assign to iff in tests (sinks for benchmarks)
- (4.10) all their type parameters. See 2.5 for reasoning.
- (4.11) local variables
- Note that the majority of this is handled implicitly by seeing idents be used. In particular, unlike the old
IR-based implementation, the AST-based one doesn't care about closures, bound methods or anonymous functions.
They're all just additional nodes in the AST.
- conversions use:
- (5.1) when converting between two equivalent structs, the fields in
either struct use each other. the fields are relevant for the
conversion, but only if the fields are also accessed outside the
conversion.
- (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
- structs use:
- (6.1) fields of type NoCopy sentinel
- (6.2) exported fields
- (6.3) embedded fields that help implement interfaces (either fully implements it, or contributes required methods) (recursively)
- (6.4) embedded fields that have exported methods (recursively)
- (6.5) embedded structs that have exported fields (recursively)
- (6.6) all fields if they have a structs.HostLayout field
- (7.1) field accesses use fields
- (7.2) fields use their types
- (8.0) How we handle interfaces:
- (8.1) We do not technically care about interfaces that only consist of
exported methods. Exported methods on concrete types are always
marked as used.
- (8.2) Any concrete type implements all known interfaces. Even if it isn't
assigned to any interfaces in our code, the user may receive a value
of the type and expect to pass it back to us through an interface.
Concrete types use their methods that implement interfaces. If the
type is used, it uses those methods. Otherwise, it doesn't. This
way, types aren't incorrectly marked reachable through the edge
from method to type.
- (8.3) All interface methods are marked as used, even if they never get
called. This is to accommodate sum types (unexported interface
method that must exist but never gets called.)
- (8.4) All embedded interfaces are marked as used. This is an
extension of 8.3, but we have to explicitly track embedded
interfaces because in a chain C->B->A, B wouldn't be marked as
used by 8.3 just because it contributes A's methods to C.
- Inherent uses:
- (9.2) variables use their types
- (9.3) types use their underlying and element types
- (9.4) conversions use the type they convert to
- (9.7) variable _reads_ use variables, writes do not, except in tests
- (9.8) runtime functions that may be called from user code via the compiler
- (9.9) objects named the blank identifier are used. They cannot be referred to and are usually used explicitly to
use something that would otherwise be unused.
- The majority of idents get marked as read by virtue of being in the AST.
- const groups:
- (10.1) if one constant out of a block of constants is used, mark all
of them used. a lot of the time, unused constants exist for the sake
of completeness. See also
https://github.com/dominikh/go-tools/issues/365
Do not, however, include constants named _ in constant groups.
- (11.1) anonymous struct types use all their fields. we cannot
deduplicate struct types, as that leads to order-dependent
reports. we can't not deduplicate struct types while still
tracking fields, because then each instance of the unnamed type in
the data flow chain will get its own fields, causing false
positives. Thus, we only accurately track fields of named struct
types, and assume that unnamed struct types use all their fields.
- type parameters use:
- (12.1) their constraint type
*/
var Debug io.Writer
func assert(b bool) {
if !b {
panic("failed assertion")
}
}
// TODO(dh): should we return a map instead of two slices?
type Result struct {
Used []Object
Unused []Object
Quiet []Object
}
var Analyzer = &lint.Analyzer{
Doc: &lint.RawDocumentation{
Title: "Unused code",
},
Analyzer: &analysis.Analyzer{
Name: "U1000",
Doc: "Unused code",
Run: run,
Requires: []*analysis.Analyzer{generated.Analyzer, directives.Analyzer},
ResultType: reflect.TypeFor[Result](),
},
}
func newGraph(
fset *token.FileSet,
files []*ast.File,
pkg *types.Package,
info *types.Info,
directives []lint.Directive,
generated map[string]generated.Generator,
opts Options,
) *graph {
g := graph{
pkg: pkg,
info: info,
files: files,
directives: directives,
generated: generated,
fset: fset,
nodes: []Node{{}},
edges: map[edge]struct{}{},
objects: map[types.Object]NodeID{},
opts: opts,
}
return &g
}
func run(pass *analysis.Pass) (any, error) {
g := newGraph(
pass.Fset,
pass.Files,
pass.Pkg,
pass.TypesInfo,
pass.ResultOf[directives.Analyzer].([]lint.Directive),
pass.ResultOf[generated.Analyzer].(map[string]generated.Generator),
DefaultOptions,
)
g.entry()
sg := &SerializedGraph{
nodes: g.nodes,
}
if Debug != nil {
Debug.Write([]byte(sg.Dot()))
}
return sg.Results(), nil
}
type Options struct {
FieldWritesAreUses bool
PostStatementsAreReads bool
ExportedIsUsed bool
ExportedFieldsAreUsed bool
ParametersAreUsed bool
LocalVariablesAreUsed bool
GeneratedIsUsed bool
}
var DefaultOptions = Options{
FieldWritesAreUses: true,
PostStatementsAreReads: false,
ExportedIsUsed: true,
ExportedFieldsAreUsed: true,
ParametersAreUsed: true,
LocalVariablesAreUsed: true,
GeneratedIsUsed: true,
}
type edgeKind uint8
const (
edgeKindUse = iota + 1
edgeKindOwn
)
type edge struct {
from, to NodeID
kind edgeKind
}
type graph struct {
pkg *types.Package
info *types.Info
files []*ast.File
fset *token.FileSet
directives []lint.Directive
generated map[string]generated.Generator
opts Options
// edges tracks all edges between nodes (uses and owns relationships). This data is also present in the Node struct,
// but there it can't be accessed in O(1) time. edges is used to deduplicate edges.
edges map[edge]struct{}
nodes []Node
objects map[types.Object]NodeID
// package-level named types
namedTypes []*types.TypeName
interfaceTypes []*types.Interface
}
type nodeState uint8
//gcassert:inline
func (ns nodeState) seen() bool { return ns&nodeStateSeen != 0 }
//gcassert:inline
func (ns nodeState) quiet() bool { return ns&nodeStateQuiet != 0 }
const (
nodeStateSeen nodeState = 1 << iota
nodeStateQuiet
)
// OPT(dh): 32 bits would be plenty, but the Node struct would end up with padding, anyway.
type NodeID uint64
type Node struct {
id NodeID
obj Object
// using slices instead of maps here helps make merging of graphs simpler and more efficient, because we can rewrite
// IDs in place instead of having to build new maps.
uses []NodeID
owns []NodeID
}
func (g *graph) objectToObject(obj types.Object) Object {
// OPT(dh): I think we only need object paths in whole-program mode. In other cases, position-based node merging
// should suffice.
// objectpath.For is an expensive function and we'd like to avoid calling it when we know that there cannot be a
// path, or when the path doesn't matter.
//
// Unexported global objects don't have paths. Local variables may have paths when they're parameters or return
// parameters, but we do not care about those, because they're not API that other packages can refer to directly. We
// do have to track fields, because they may be part of an anonymous type declared in a parameter or return
// parameter. We cannot categorically ignore unexported identifiers, because an exported field might have been
// embedded via an unexported field, which will be referred to.
var relevant bool
switch obj := obj.(type) {
case *types.Var:
// If it's a field or it's an exported top-level variable, we care about it. Otherwise, we don't.
// OPT(dh): same question as posed in the default branch
relevant = obj.IsField() || token.IsExported(obj.Name())
default:
// OPT(dh): See if it's worth checking that the object is actually in package scope, and doesn't just have a
// capitalized name.
relevant = token.IsExported(obj.Name())
}
var path ObjectPath
if relevant {
objPath, _ := objectpath.For(obj)
if objPath != "" {
path = ObjectPath{
PkgPath: obj.Pkg().Path(),
ObjPath: objPath,
}
}
}
name := obj.Name()
if sig, ok := obj.Type().(*types.Signature); ok && sig.Recv() != nil {
switch types.Unalias(sig.Recv().Type()).(type) {
case *types.Named, *types.Pointer:
typ := types.TypeString(sig.Recv().Type(), func(*types.Package) string { return "" })
if len(typ) > 0 && typ[0] == '*' {
name = fmt.Sprintf("(%s).%s", typ, obj.Name())
} else if len(typ) > 0 {
name = fmt.Sprintf("%s.%s", typ, obj.Name())
}
}
}
return Object{
Name: name,
ShortName: obj.Name(),
Kind: typString(obj),
Path: path,
Position: g.fset.PositionFor(obj.Pos(), false),
DisplayPosition: report.DisplayPosition(g.fset, obj.Pos()),
}
}
func typString(obj types.Object) string {
switch obj := obj.(type) {
case *types.Func:
return "func"
case *types.Var:
if obj.IsField() {
return "field"
}
return "var"
case *types.Const:
return "const"
case *types.TypeName:
if _, ok := obj.Type().(*types.TypeParam); ok {
return "type param"
} else {
return "type"
}
default:
return "identifier"
}
}
func (g *graph) newNode(obj types.Object) NodeID {
id := NodeID(len(g.nodes))
n := Node{
id: id,
obj: g.objectToObject(obj),
}
g.nodes = append(g.nodes, n)
if _, ok := g.objects[obj]; ok {
panic(fmt.Sprintf("already had a node for %s", obj))
}
g.objects[obj] = id
return id
}
func (g *graph) node(obj types.Object) NodeID {
if obj == nil {
return 0
}
obj = origin(obj)
if n, ok := g.objects[obj]; ok {
return n
}
n := g.newNode(obj)
return n
}
func origin(obj types.Object) types.Object {
switch obj := obj.(type) {
case *types.Var:
return obj.Origin()
case *types.Func:
return obj.Origin()
default:
return obj
}
}
func (g *graph) addEdge(e edge) bool {
if _, ok := g.edges[e]; ok {
return false
}
g.edges[e] = struct{}{}
return true
}
func (g *graph) addOwned(owner, owned NodeID) {
e := edge{owner, owned, edgeKindOwn}
if !g.addEdge(e) {
return
}
n := &g.nodes[owner]
n.owns = append(n.owns, owned)
}
func (g *graph) addUse(by, used NodeID) {
e := edge{by, used, edgeKindUse}
if !g.addEdge(e) {
return
}
nBy := &g.nodes[by]
nBy.uses = append(nBy.uses, used)
}
func (g *graph) see(obj, owner types.Object) {
if obj == nil {
panic("saw nil object")
}
if g.opts.ExportedIsUsed && obj.Pkg() != g.pkg || obj.Pkg() == nil {
return
}
nObj := g.node(obj)
if owner != nil {
nOwner := g.node(owner)
g.addOwned(nOwner, nObj)
}
}
func isIrrelevant(obj types.Object) bool {
switch obj.(type) {
case *types.PkgName:
return true
default:
return false
}
}
func (g *graph) use(used, by types.Object) {
if g.opts.ExportedIsUsed {
if used.Pkg() != g.pkg || used.Pkg() == nil {
return
}
if by != nil && by.Pkg() != g.pkg {
return
}
}
if isIrrelevant(used) {
return
}
nUsed := g.node(used)
nBy := g.node(by)
g.addUse(nBy, nUsed)
}
func (g *graph) entry() {
for _, f := range g.files {
for _, cg := range f.Comments {
for _, c := range cg.List {
if strings.HasPrefix(c.Text, "//go:linkname ") {
// FIXME(dh): we're looking at all comments. The
// compiler only looks at comments in the
// left-most column. The intention probably is to
// only look at top-level comments.
// (1.8) packages use symbols linked via go:linkname
fields := strings.Fields(c.Text)
if len(fields) == 3 {
obj := g.pkg.Scope().Lookup(fields[1])
if obj == nil {
continue
}
g.use(obj, nil)
}
}
}
}
}
for _, f := range g.files {
for _, decl := range f.Decls {
g.decl(decl, nil)
}
}
if g.opts.GeneratedIsUsed {
// OPT(dh): depending on the options used, we do not need to track all objects. For example, if local variables
// are always used, then it is enough to use their surrounding function.
for obj := range g.objects {
path := g.fset.PositionFor(obj.Pos(), false).Filename
if _, ok := g.generated[path]; ok {
g.use(obj, nil)
}
}
}
// We use a normal map instead of a typeutil.Map because we deduplicate
// these on a best effort basis, as an optimization.
allInterfaces := make(map[*types.Interface]struct{})
for _, typ := range g.interfaceTypes {
allInterfaces[typ] = struct{}{}
}
for _, ins := range g.info.Instances {
if typ, ok := ins.Type.(*types.Named); ok && typ.Obj().Pkg() == g.pkg {
if iface, ok := typ.Underlying().(*types.Interface); ok {
allInterfaces[iface] = struct{}{}
}
}
}
processMethodSet := func(named *types.TypeName, ms *types.MethodSet) {
if g.opts.ExportedIsUsed {
for m := range ms.Methods() {
if token.IsExported(m.Obj().Name()) {
// (2.1) named types use exported methods
// (6.4) structs use embedded fields that have exported methods
//
// By reading the selection, we read all embedded fields that are part of the path
g.readSelection(m, named)
}
}
}
if _, ok := named.Type().Underlying().(*types.Interface); !ok {
// (8.0) handle interfaces
//
// We don't care about interfaces implementing interfaces; all their methods are already used, anyway
for iface := range allInterfaces {
if sels, ok := implements(named.Type(), iface, ms); ok {
for _, sel := range sels {
// (8.2) any concrete type implements all known interfaces
// (6.3) structs use embedded fields that help implement interfaces
g.readSelection(sel, named)
}
}
}
}
}
for _, named := range g.namedTypes {
// OPT(dh): do we already have the method set available?
processMethodSet(named, types.NewMethodSet(named.Type()))
processMethodSet(named, types.NewMethodSet(types.NewPointer(named.Type())))
}
type ignoredKey struct {
file string
line int
}
ignores := map[ignoredKey]struct{}{}
for _, dir := range g.directives {
if dir.Command != "ignore" && dir.Command != "file-ignore" {
continue
}
if len(dir.Arguments) == 0 {
continue
}
if slices.Contains(strings.Split(dir.Arguments[0], ","), "U1000") {
pos := g.fset.PositionFor(dir.Node.Pos(), false)
var key ignoredKey
switch dir.Command {
case "ignore":
key = ignoredKey{
pos.Filename,
pos.Line,
}
case "file-ignore":
key = ignoredKey{
pos.Filename,
-1,
}
}
ignores[key] = struct{}{}
}
}
if len(ignores) > 0 {
// all objects annotated with a //lint:ignore U1000 are considered used
for obj := range g.objects {
pos := g.fset.PositionFor(obj.Pos(), false)
key1 := ignoredKey{
pos.Filename,
pos.Line,
}
key2 := ignoredKey{
pos.Filename,
-1,
}
_, ok := ignores[key1]
if !ok {
_, ok = ignores[key2]
}
if ok {
g.use(obj, nil)
// use methods and fields of ignored types
if obj, ok := obj.(*types.TypeName); ok {
if obj.IsAlias() {
if typ, ok := types.Unalias(obj.Type()).(*types.Named); ok && (g.opts.ExportedIsUsed && typ.Obj().Pkg() != obj.Pkg() || typ.Obj().Pkg() == nil) {
// This is an alias of a named type in another package.
// Don't walk its fields or methods; we don't have to.
//
// For aliases to types in the same package, we do want to ignore the fields and methods,
// because ignoring the alias should ignore the aliased type.
continue
}
}
if typ, ok := types.Unalias(obj.Type()).(*types.Named); ok {
for method := range typ.Methods() {
g.use(method, nil)
}
}
if typ, ok := obj.Type().Underlying().(*types.Struct); ok {
for field := range typ.Fields() {
g.use(field, nil)
}
}
}
}
}
}
}
func isOfType[T any](x any) bool {
_, ok := x.(T)
return ok
}
func (g *graph) read(node ast.Node, by types.Object) {
if node == nil {
return
}
switch node := node.(type) {
case *ast.Ident:
// Among many other things, this handles
// (7.1) field accesses use fields
obj := g.info.ObjectOf(node)
g.use(obj, by)
case *ast.BasicLit:
// Nothing to do
case *ast.SliceExpr:
g.read(node.X, by)
g.read(node.Low, by)
g.read(node.High, by)
g.read(node.Max, by)
case *ast.UnaryExpr:
g.read(node.X, by)
case *ast.ParenExpr:
g.read(node.X, by)
case *ast.ArrayType:
g.read(node.Len, by)
g.read(node.Elt, by)
case *ast.SelectorExpr:
g.readSelectorExpr(node, by)
case *ast.IndexExpr:
// Among many other things, this handles
// (2.6) named types use all their type arguments
g.read(node.X, by)
g.read(node.Index, by)
case *ast.IndexListExpr:
// Among many other things, this handles
// (2.6) named types use all their type arguments
g.read(node.X, by)
for _, index := range node.Indices {
g.read(index, by)
}
case *ast.BinaryExpr:
g.read(node.X, by)
g.read(node.Y, by)
case *ast.CompositeLit:
g.read(node.Type, by)
// We get the type of the node itself, not of node.Type, to handle nested composite literals of the kind
// T{{...}}
typ, isStruct := typeutil.CoreType(g.info.TypeOf(node)).(*types.Struct)
if isStruct {
unkeyed := len(node.Elts) != 0 && !isOfType[*ast.KeyValueExpr](node.Elts[0])
if g.opts.FieldWritesAreUses && unkeyed {
// Untagged struct literal that specifies all fields. We have to manually use the fields in the type,
// because the unkeyd literal doesn't contain any nodes referring to the fields.
for field := range typ.Fields() {
g.use(field, by)
}
}
if g.opts.FieldWritesAreUses || unkeyed {
for _, elt := range node.Elts {
g.read(elt, by)
}
} else {
for _, elt := range node.Elts {
kv := elt.(*ast.KeyValueExpr)
g.write(kv.Key, by)
g.read(kv.Value, by)
}
}
} else {
for _, elt := range node.Elts {
g.read(elt, by)
}
}
case *ast.KeyValueExpr:
g.read(node.Key, by)
g.read(node.Value, by)
case *ast.StarExpr:
g.read(node.X, by)
case *ast.MapType:
g.read(node.Key, by)
g.read(node.Value, by)
case *ast.FuncLit:
g.read(node.Type, by)
// See graph.decl's handling of ast.FuncDecl for why this bit of code is necessary.
fn := g.info.TypeOf(node).(*types.Signature)
for params, i := fn.Params(), 0; i < params.Len(); i++ {
g.see(params.At(i), by)
if params.At(i).Name() == "" {
g.use(params.At(i), by)
}
}
g.block(node.Body, by)
case *ast.FuncType:
m := map[*types.Var]struct{}{}
if !g.opts.ParametersAreUsed {
m = map[*types.Var]struct{}{}
// seeScope marks all local variables in the scope as used, but we don't want to unconditionally use
// parameters, as this is controlled by Options.ParametersAreUsed. Pass seeScope a list of variables it
// should skip.
for _, f := range node.Params.List {
for _, name := range f.Names {
m[g.info.ObjectOf(name).(*types.Var)] = struct{}{}
}
}
}
g.seeScope(node, by, m)
// (4.1) functions use all their arguments, return parameters and receivers
// (12.1) type parameters use their constraint type
g.read(node.TypeParams, by)
if g.opts.ParametersAreUsed {
g.read(node.Params, by)
}
g.read(node.Results, by)
case *ast.FieldList:
if node == nil {
return
}
// This branch is only hit for field lists enclosed by parentheses or square brackets, i.e. parameters. Fields
// (for structs) and method lists (for interfaces) are handled elsewhere.
for _, field := range node.List {
if len(field.Names) == 0 {
g.read(field.Type, by)
} else {
for _, name := range field.Names {
// OPT(dh): instead of by -> name -> type, we could just emit by -> type. We don't care about the
// (un)usedness of parameters of any kind.
obj := g.info.ObjectOf(name)
g.use(obj, by)
g.read(field.Type, obj)
}
}
}
case *ast.ChanType:
g.read(node.Value, by)
case *ast.StructType:
// This is only used for anonymous struct types, not named ones.
for _, field := range node.Fields.List {
if len(field.Names) == 0 {
// embedded field
f := g.embeddedField(field.Type, by)
g.use(f, by)
} else {
for _, name := range field.Names {
// (11.1) anonymous struct types use all their fields
// OPT(dh): instead of by -> name -> type, we could just emit by -> type. If the type is used, then the fields are used.
obj := g.info.ObjectOf(name)
g.see(obj, by)
g.use(obj, by)
g.read(field.Type, g.info.ObjectOf(name))
}
}
}
case *ast.TypeAssertExpr:
g.read(node.X, by)
g.read(node.Type, by)
case *ast.InterfaceType:
if len(node.Methods.List) != 0 {
g.interfaceTypes = append(g.interfaceTypes, g.info.TypeOf(node).(*types.Interface))
}
for _, meth := range node.Methods.List {
switch len(meth.Names) {
case 0:
// Embedded type or type union
// (8.4) all embedded interfaces are marked as used
// (this also covers type sets)
g.read(meth.Type, by)
case 1:
// Method
// (8.3) all interface methods are marked as used
obj := g.info.ObjectOf(meth.Names[0])
g.see(obj, by)
g.use(obj, by)
g.read(meth.Type, obj)
default:
panic(fmt.Sprintf("unexpected number of names: %d", len(meth.Names)))
}
}
case *ast.Ellipsis:
g.read(node.Elt, by)
case *ast.CallExpr:
g.read(node.Fun, by)
for _, arg := range node.Args {
g.read(arg, by)
}
// Handle conversions
conv := node
if len(conv.Args) != 1 || conv.Ellipsis.IsValid() {
return
}
dst := g.info.TypeOf(conv.Fun)
src := g.info.TypeOf(conv.Args[0])
// XXX use DereferenceR instead
// XXX guard against infinite recursion in DereferenceR
tSrc := typeutil.CoreType(typeutil.Dereference(src))
tDst := typeutil.CoreType(typeutil.Dereference(dst))
stSrc, okSrc := tSrc.(*types.Struct)
stDst, okDst := tDst.(*types.Struct)
if okDst && okSrc {
// Converting between two structs. The fields are
// relevant for the conversion, but only if the
// fields are also used outside of the conversion.
// Mark fields as used by each other.
assert(stDst.NumFields() == stSrc.NumFields())
for i := 0; i < stDst.NumFields(); i++ {
// (5.1) when converting between two equivalent structs, the fields in
// either struct use each other. the fields are relevant for the
// conversion, but only if the fields are also accessed outside the
// conversion.
g.use(stDst.Field(i), stSrc.Field(i))
g.use(stSrc.Field(i), stDst.Field(i))
}
} else if okSrc && tDst == types.Typ[types.UnsafePointer] {
// (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
g.useAllFieldsRecursively(stSrc, by)
} else if okDst && tSrc == types.Typ[types.UnsafePointer] {
// (5.2) when converting to or from unsafe.Pointer, mark all fields as used.
g.useAllFieldsRecursively(stDst, by)
}
default:
lint.ExhaustiveTypeSwitch(node)
}
}
func (g *graph) useAllFieldsRecursively(typ types.Type, by types.Object) {
switch typ := typ.Underlying().(type) {
case *types.Struct:
for field := range typ.Fields() {
g.use(field, by)
g.useAllFieldsRecursively(field.Type(), by)
}
case *types.Array:
g.useAllFieldsRecursively(typ.Elem(), by)
default:
return
}
}
func (g *graph) write(node ast.Node, by types.Object) {
if node == nil {
return
}
switch node := node.(type) {
case *ast.Ident:
obj := g.info.ObjectOf(node)
if obj == nil {
// This can happen for `switch x := v.(type)`, where that x doesn't have an object
return
}
// (4.9) functions use package-level variables they assign to iff in tests (sinks for benchmarks)
// (9.7) variable _reads_ use variables, writes do not, except in tests
path := g.fset.File(obj.Pos()).Name()
if strings.HasSuffix(path, "_test.go") {
if isGlobal(obj) {
g.use(obj, by)
}
}
case *ast.IndexExpr:
g.read(node.X, by)
g.read(node.Index, by)
case *ast.SelectorExpr:
if g.opts.FieldWritesAreUses {
// Writing to a field constitutes a use. See https://staticcheck.dev/issues/288 for some discussion on that.
//
// This code can also get triggered by qualified package variables, in which case it doesn't matter what we do,
// because the object is in another package.
//
// FIXME(dh): ^ isn't true if we track usedness of exported identifiers
g.readSelectorExpr(node, by)
} else {
g.read(node.X, by)
g.write(node.Sel, by)
}
case *ast.StarExpr:
g.read(node.X, by)
case *ast.ParenExpr:
g.write(node.X, by)
default:
lint.ExhaustiveTypeSwitch(node)
}
}
// readSelectorExpr reads all elements of a selector expression, including implicit fields.
func (g *graph) readSelectorExpr(sel *ast.SelectorExpr, by types.Object) {
// cover AST-based accesses
g.read(sel.X, by)
g.read(sel.Sel, by)
tsel, ok := g.info.Selections[sel]
if !ok {
return
}
g.readSelection(tsel, by)
}
func (g *graph) readSelection(sel *types.Selection, by types.Object) {
indices := sel.Index()
base := sel.Recv()
for _, idx := range indices[:len(indices)-1] {
// XXX do we need core types here?
field := typeutil.Dereference(base.Underlying()).Underlying().(*types.Struct).Field(idx)
g.use(field, by)
base = field.Type()
}
g.use(sel.Obj(), by)
}
func (g *graph) block(block *ast.BlockStmt, by types.Object) {
if block == nil {
return
}
g.seeScope(block, by, nil)
for _, stmt := range block.List {
g.stmt(stmt, by)
}
}
func isGlobal(obj types.Object) bool {
return obj.Parent() == obj.Pkg().Scope()
}
func (g *graph) decl(decl ast.Decl, by types.Object) {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.IMPORT:
// Nothing to do
case token.CONST:
for _, spec := range decl.Specs {
vspec := spec.(*ast.ValueSpec)
assert(len(vspec.Values) == 0 || len(vspec.Values) == len(vspec.Names))
for i, name := range vspec.Names {
obj := g.info.ObjectOf(name)
g.see(obj, by)
g.read(vspec.Type, obj)
if len(vspec.Values) != 0 {
g.read(vspec.Values[i], obj)
}
if name.Name == "_" {
// (9.9) objects named the blank identifier are used
g.use(obj, by)
} else if token.IsExported(name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed {
g.use(obj, nil)
}
}
}
groups := astutil.GroupSpecs(g.fset, decl.Specs)
for _, group := range groups {
// (10.1) if one constant out of a block of constants is used, mark all of them used
//
// We encode this as a ring. If we have a constant group 'const ( a; b; c )', then we'll produce the
// following graph: a -> b -> c -> a.
var first, prev, last types.Object
for _, spec := range group {
for _, name := range spec.(*ast.ValueSpec).Names {
if name.Name == "_" {
// Having a blank constant in a group doesn't mark the whole group as used
continue
}
obj := g.info.ObjectOf(name)
if first == nil {
first = obj
} else {
g.use(obj, prev)
}
prev = obj
last = obj
}
}
if first != nil && first != last {
g.use(first, last)
}
}
case token.TYPE:
for _, spec := range decl.Specs {
tspec := spec.(*ast.TypeSpec)
obj := g.info.ObjectOf(tspec.Name).(*types.TypeName)
g.see(obj, by)
g.seeScope(tspec, obj, nil)
if !tspec.Assign.IsValid() {
g.namedTypes = append(g.namedTypes, obj)
}
if token.IsExported(tspec.Name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed {
// (1.1) packages use exported named types
g.use(g.info.ObjectOf(tspec.Name), nil)
}
// (2.5) named types use all their type parameters
g.read(tspec.TypeParams, obj)
g.namedType(obj, tspec.Type)
if tspec.Name.Name == "_" {
// (9.9) objects named the blank identifier are used
g.use(obj, by)
}
}
case token.VAR:
// We cannot rely on types.Initializer for package-level variables because
// - initializers are only tracked for variables that are actually initialized
// - we want to see the AST of the type, if specified, not just the rhs
for _, spec := range decl.Specs {
vspec := spec.(*ast.ValueSpec)
for i, name := range vspec.Names {
obj := g.info.ObjectOf(name)
g.see(obj, by)
// variables and constants use their types
g.read(vspec.Type, obj)
if len(vspec.Names) == len(vspec.Values) {
// One value per variable
g.read(vspec.Values[i], obj)
} else if len(vspec.Values) != 0 {
// Multiple variables initialized with a single rhs
// assert(len(vspec.Values) == 1)
if len(vspec.Values) != 1 {
panic(g.fset.PositionFor(vspec.Pos(), false))
}
g.read(vspec.Values[0], obj)
}
if token.IsExported(name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed {
// (1.3) packages use exported variables
g.use(obj, nil)
}
if name.Name == "_" {
// (9.9) objects named the blank identifier are used
g.use(obj, by)
}
}
}
default:
panic(fmt.Sprintf("unexpected token %s", decl.Tok))
}
case *ast.FuncDecl:
obj := g.info.ObjectOf(decl.Name).(*types.Func).Origin()
g.see(obj, nil)
if token.IsExported(decl.Name.Name) && g.opts.ExportedIsUsed {
if decl.Recv == nil {
// (1.2) packages use exported functions
g.use(obj, nil)
}
} else if decl.Name.Name == "init" {
// (1.5) packages use init functions
g.use(obj, nil)
} else if decl.Name.Name == "main" && g.pkg.Name() == "main" {
// (1.7) packages use the main function iff in the main package
g.use(obj, nil)
} else if g.pkg.Path() == "runtime" && runtimeFuncs[decl.Name.Name] {
// (9.8) runtime functions that may be called from user code via the compiler
g.use(obj, nil)
} else if g.pkg.Path() == "runtime/coverage" && runtimeCoverageFuncs[decl.Name.Name] {
// (9.8) runtime functions that may be called from user code via the compiler
g.use(obj, nil)
}
// (4.1) functions use their receivers
g.read(decl.Recv, obj)
g.read(decl.Type, obj)
g.block(decl.Body, obj)
// g.read(decl.Type) will ultimately call g.seeScopes and see parameters that way. But because it relies
// entirely on the AST, it cannot resolve unnamed parameters to types.Object. For that reason we explicitly
// handle arguments here, as well as for FuncLits elsewhere.
//
// g.seeScopes can't get to the types.Signature for this function because there is no mapping from ast.FuncType to
// types.Signature, only from ast.Ident to types.Signature.
//
// This code is only really relevant when Options.ParametersAreUsed is false. Otherwise, all parameters are
// considered used, and if we never see a parameter then no harm done (we still see its type separately).
fn := g.info.TypeOf(decl.Name).(*types.Signature)
for params, i := fn.Params(), 0; i < params.Len(); i++ {
g.see(params.At(i), obj)
if params.At(i).Name() == "" {
g.use(params.At(i), obj)
}
}
if decl.Name.Name == "_" {
// (9.9) objects named the blank identifier are used
g.use(obj, nil)
}
if decl.Doc != nil {
for _, cmt := range decl.Doc.List {
if strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
// (1.6) packages use functions exported to cgo
g.use(obj, nil)
}
}
}
default:
// We do not cover BadDecl, but we shouldn't ever see one of those
lint.ExhaustiveTypeSwitch(decl)
}
}
// seeScope sees all objects in node's scope. If Options.LocalVariablesAreUsed is true, all objects that aren't fields
// are marked as used. Variables set in skipLvars will not be marked as used.
func (g *graph) seeScope(node ast.Node, by types.Object, skipLvars map[*types.Var]struct{}) {
// A note on functions and scopes: for a function declaration, the body's BlockStmt can't be found in
// types.Info.Scopes. Instead, the FuncType can, and that scope will contain receivers, parameters, return
// parameters and immediate local variables.
scope := g.info.Scopes[node]
if scope == nil {
return
}
for _, name := range scope.Names() {
obj := scope.Lookup(name)
g.see(obj, by)
if g.opts.LocalVariablesAreUsed {
if obj, ok := obj.(*types.Var); ok && !obj.IsField() {
if _, ok := skipLvars[obj]; !ok {
g.use(obj, by)
}
}
}
}
}
func (g *graph) stmt(stmt ast.Stmt, by types.Object) {
if stmt == nil {
return
}
for {
// We don't care about labels, so unwrap LabeledStmts. Note that a label can itself be labeled.
if labeled, ok := stmt.(*ast.LabeledStmt); ok {
stmt = labeled.Stmt
} else {
break
}
}
switch stmt := stmt.(type) {
case *ast.AssignStmt:
for _, lhs := range stmt.Lhs {
g.write(lhs, by)
}
for _, rhs := range stmt.Rhs {
// Note: it would be more accurate to have the rhs used by the lhs, but it ultimately doesn't matter,
// because local variables always end up used, anyway.
//
// TODO(dh): we'll have to change that once we allow tracking the usedness of parameters
g.read(rhs, by)
}
case *ast.BlockStmt:
g.block(stmt, by)
case *ast.BranchStmt:
// Nothing to do
case *ast.DeclStmt:
g.decl(stmt.Decl, by)
case *ast.DeferStmt:
g.read(stmt.Call, by)
case *ast.ExprStmt:
g.read(stmt.X, by)
case *ast.ForStmt:
g.seeScope(stmt, by, nil)
g.stmt(stmt.Init, by)
g.read(stmt.Cond, by)
g.stmt(stmt.Post, by)
g.block(stmt.Body, by)
case *ast.GoStmt:
g.read(stmt.Call, by)
case *ast.IfStmt:
g.seeScope(stmt, by, nil)
g.stmt(stmt.Init, by)
g.read(stmt.Cond, by)
g.block(stmt.Body, by)
g.stmt(stmt.Else, by)
case *ast.IncDecStmt:
if g.opts.PostStatementsAreReads {
g.read(stmt.X, by)
g.write(stmt.X, by)
} else {
// We treat post-increment as a write only. This ends up using fields, and sinks in tests, but not other
// variables.
g.write(stmt.X, by)
}
case *ast.RangeStmt:
g.seeScope(stmt, by, nil)
g.write(stmt.Key, by)
g.write(stmt.Value, by)
g.read(stmt.X, by)
g.block(stmt.Body, by)
case *ast.ReturnStmt:
for _, ret := range stmt.Results {
g.read(ret, by)
}
case *ast.SelectStmt:
for _, clause_ := range stmt.Body.List {
clause := clause_.(*ast.CommClause)
g.seeScope(clause, by, nil)
switch comm := clause.Comm.(type) {
case *ast.SendStmt:
g.read(comm.Chan, by)
g.read(comm.Value, by)
case *ast.ExprStmt:
g.read(astutil.Unparen(comm.X).(*ast.UnaryExpr).X, by)
case *ast.AssignStmt:
for _, lhs := range comm.Lhs {
g.write(lhs, by)
}
for _, rhs := range comm.Rhs {
g.read(rhs, by)
}
case nil:
default:
lint.ExhaustiveTypeSwitch(comm)
}
for _, body := range clause.Body {
g.stmt(body, by)
}
}
case *ast.SendStmt:
g.read(stmt.Chan, by)
g.read(stmt.Value, by)
case *ast.SwitchStmt:
g.seeScope(stmt, by, nil)
g.stmt(stmt.Init, by)
g.read(stmt.Tag, by)
for _, clause_ := range stmt.Body.List {
clause := clause_.(*ast.CaseClause)
g.seeScope(clause, by, nil)
for _, expr := range clause.List {
g.read(expr, by)
}
for _, body := range clause.Body {
g.stmt(body, by)
}
}
case *ast.TypeSwitchStmt:
g.seeScope(stmt, by, nil)
g.stmt(stmt.Init, by)
g.stmt(stmt.Assign, by)
for _, clause_ := range stmt.Body.List {
clause := clause_.(*ast.CaseClause)
g.seeScope(clause, by, nil)
for _, expr := range clause.List {
g.read(expr, by)
}
for _, body := range clause.Body {
g.stmt(body, by)
}
}
case *ast.EmptyStmt:
// Nothing to do
default:
lint.ExhaustiveTypeSwitch(stmt)
}
}
// embeddedField sees the field declared by the embedded field node, and marks the type as used by the field.
//
// Embedded fields are special in two ways: they don't have names, so we don't have immediate access to an ast.Ident to
// resolve to the field's types.Var and need to instead walk the AST, and we cannot use g.read on the type because
// eventually we do get to an ast.Ident, and ObjectOf resolves embedded fields to the field they declare, not the type.
// That's why we have code specially for handling embedded fields.
func (g *graph) embeddedField(node ast.Node, by types.Object) *types.Var {
// We need to traverse the tree to find the ast.Ident, but all the nodes we traverse should be used by the object we
// get once we resolve the ident. Collect the nodes and process them once we've found the ident.
nodes := make([]ast.Node, 0, 4)
for {
switch node_ := node.(type) {
case *ast.Ident:
// obj is the field
obj := g.info.ObjectOf(node_).(*types.Var)
// the field is declared by the enclosing type
g.see(obj, by)
for _, n := range nodes {
g.read(n, obj)
}
if tname, ok := g.info.Uses[node_].(*types.TypeName); ok && tname.IsAlias() {
// When embedding an alias we want to use the alias, not what the alias points to.
g.use(tname, obj)
} else {
switch typ := typeutil.Dereference(g.info.TypeOf(node_)).(type) {
case *types.Named:
// (7.2) fields use their types
g.use(typ.Obj(), obj)
case *types.Basic:
// Nothing to do
default:
// Other types are only possible for aliases, which we've already handled
lint.ExhaustiveTypeSwitch(typ)
}
}
return obj
case *ast.StarExpr:
node = node_.X
case *ast.SelectorExpr:
node = node_.Sel
nodes = append(nodes, node_.X)
case *ast.IndexExpr:
node = node_.X
nodes = append(nodes, node_.Index)
case *ast.IndexListExpr:
node = node_.X
default:
lint.ExhaustiveTypeSwitch(node_)
}
}
}
// isNoCopyType reports whether a type represents the NoCopy sentinel
// type. The NoCopy type is a named struct with no fields and exactly
// one method `func Lock()` that is empty.
//
// FIXME(dh): currently we're not checking that the function body is
// empty.
func isNoCopyType(typ types.Type) bool {
st, ok := typ.Underlying().(*types.Struct)
if !ok {
return false
}
if st.NumFields() != 0 {
return false
}
named, ok := types.Unalias(typ).(*types.Named)
if !ok {
return false
}
switch num := named.NumMethods(); num {
case 1, 2:
for i := range num {
meth := named.Method(i)
if meth.Name() != "Lock" && meth.Name() != "Unlock" {
return false
}
sig := meth.Type().(*types.Signature)
if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
return false
}
}
default:
return false
}
return true
}
func (g *graph) namedType(typ *types.TypeName, spec ast.Expr) {
// (2.2) named types use the type they're based on
if st, ok := spec.(*ast.StructType); ok {
var hasHostLayout bool
// Named structs are special in that their unexported fields are only
// used if they're being written to. That is, the fields are not used by
// the named type itself, nor are the types of the fields.
for _, field := range st.Fields.List {
seen := map[*types.Struct]struct{}{}
// For `type x struct { *x; F int }`, don't visit the embedded x
seen[g.info.TypeOf(st).(*types.Struct)] = struct{}{}
var hasExportedField func(t types.Type) bool
hasExportedField = func(T types.Type) bool {
t, ok := typeutil.Dereference(T).Underlying().(*types.Struct)
if !ok {
return false
}
if _, ok := seen[t]; ok {
return false
}
seen[t] = struct{}{}
for field := range t.Fields() {
if field.Exported() {
return true
}
if field.Embedded() && hasExportedField(field.Type()) {
return true
}
}
return false
}
if len(field.Names) == 0 {
fieldVar := g.embeddedField(field.Type, typ)
if token.IsExported(fieldVar.Name()) && g.opts.ExportedIsUsed {
// (6.2) structs use exported fields
g.use(fieldVar, typ)
}
if g.opts.ExportedIsUsed && g.opts.ExportedFieldsAreUsed && hasExportedField(fieldVar.Type()) {
// (6.5) structs use embedded structs that have exported fields (recursively)
g.use(fieldVar, typ)
}
} else {
for _, name := range field.Names {
obj := g.info.ObjectOf(name)
g.see(obj, typ)
// (7.2) fields use their types
//
// This handles aliases correctly because ObjectOf(alias) returns the TypeName of the alias, not
// what the alias points to.
g.read(field.Type, obj)
if name.Name == "_" {
// (9.9) objects named the blank identifier are used
g.use(obj, typ)
} else if token.IsExported(name.Name) && g.opts.ExportedIsUsed {
// (6.2) structs use exported fields
g.use(obj, typ)
}
if isNoCopyType(obj.Type()) {
// (6.1) structs use fields of type NoCopy sentinel
g.use(obj, typ)
}
}
}
// (6.6) if the struct has a field of type structs.HostLayout, then
// this signals that all fields are relevant to match some
// externally specified memory layout.
//
// This augments the 5.2 heuristic of using all fields when
// converting via unsafe.Pointer. For example, 5.2 doesn't currently
// handle conversions involving more than one level of pointer
// indirection (although it probably should). Another example that
// doesn't involve the use of unsafe at all is exporting symbols for
// use by C libraries.
//
// The actual requirements for the use of structs.HostLayout fields
// haven't been determined yet. It's an open question whether named
// types of underlying type structs.HostLayout, aliases of it,
// generic instantiations, or embedding structs that themselves
// contain a HostLayout field count as valid uses of the marker (see
// https://golang.org/issues/66408#issuecomment-2120644459)
//
// For now, we require a struct to have a field of type
// structs.HostLayout or an alias of it, where the field itself may
// be embedded. We don't handle fields whose types are type
// parameters.
fieldType := types.Unalias(g.info.TypeOf(field.Type))
if fieldType, ok := fieldType.(*types.Named); ok {
obj := fieldType.Obj()
if obj.Name() == "HostLayout" && obj.Pkg().Path() == "structs" {
hasHostLayout = true
}
}
}
// For 6.6.
if hasHostLayout {
g.useAllFieldsRecursively(typ.Type(), typ)
}
} else {
g.read(spec, typ)
}
}
func (g *SerializedGraph) color(rootID NodeID, states []nodeState) {
root := g.nodes[rootID]
if states[rootID].seen() {
return
}
states[rootID] |= nodeStateSeen
for _, n := range root.uses {
g.color(n, states)
}
}
type Object struct {
Name string
ShortName string
// OPT(dh): use an enum for the kind
Kind string
Path ObjectPath
Position token.Position
DisplayPosition token.Position
}
func (g *SerializedGraph) Results() Result {
// XXX objectpath does not return paths for unexported objects, which means that if we analyze the same code twice
// (e.g. normal and test variant), then some objects will appear multiple times, but may not be used identically. we
// have to deduplicate based on the token.Position. Actually we have to do that, anyway, because we may flag types
// local to functions. Those are probably always both used or both unused, but we don't want to flag them twice,
// either.
//
// Note, however, that we still need objectpaths to deduplicate exported identifiers when analyzing independent
// packages in whole-program mode, because if package A uses an object from package B, B will have been imported
// from export data, and we will not have column information.
//
// XXX ^ document that design requirement.
states := g.colorAndQuieten()
var res Result
// OPT(dh): can we find meaningful initial capacities for the used and unused slices?
for _, n := range g.nodes[1:] {
state := states[n.id]
if state.seen() {
res.Used = append(res.Used, n.obj)
} else if state.quiet() {
res.Quiet = append(res.Quiet, n.obj)
} else {
res.Unused = append(res.Unused, n.obj)
}
}
return res
}
func (g *SerializedGraph) colorAndQuieten() []nodeState {
states := make([]nodeState, len(g.nodes)+1)
g.color(0, states)
var quieten func(id NodeID)
quieten = func(id NodeID) {
states[id] |= nodeStateQuiet
for _, owned := range g.nodes[id].owns {
quieten(owned)
}
}
for _, n := range g.nodes {
if states[n.id].seen() {
continue
}
for _, owned := range n.owns {
quieten(owned)
}
}
return states
}
// Dot formats a graph in Graphviz dot format.
func (g *SerializedGraph) Dot() string {
b := &strings.Builder{}
states := g.colorAndQuieten()
// Note: We use addresses in our node names. This only works as long as Go's garbage collector doesn't move
// memory around in the middle of our debug printing.
debugNode := func(n Node) {
if n.id == 0 {
fmt.Fprintf(b, "n%d [label=\"Root\"];\n", n.id)
} else {
color := "red"
if states[n.id].seen() {
color = "green"
} else if states[n.id].quiet() {
color = "grey"
}
label := fmt.Sprintf("%s %s\n%s", n.obj.Kind, n.obj.Name, n.obj.Position)
fmt.Fprintf(b, "n%d [label=%q, color=%q];\n", n.id, label, color)
}
for _, e := range n.uses {
fmt.Fprintf(b, "n%d -> n%d;\n", n.id, e)
}
for _, owned := range n.owns {
fmt.Fprintf(b, "n%d -> n%d [style=dashed];\n", n.id, owned)
}
}
fmt.Fprintf(b, "digraph{\n")
for _, v := range g.nodes {
debugNode(v)
}
fmt.Fprintf(b, "}\n")
return b.String()
}
func Graph(fset *token.FileSet,
files []*ast.File,
pkg *types.Package,
info *types.Info,
directives []lint.Directive,
generated map[string]generated.Generator,
opts Options,
) []Node {
g := newGraph(fset, files, pkg, info, directives, generated, opts)
g.entry()
return g.nodes
}
================================================
FILE: unused/unused_test.go
================================================
package unused
import (
"fmt"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/expect"
)
type expectation uint8
const (
shouldBeUsed = iota
shouldBeUnused
shouldBeQuiet
)
func (exp expectation) String() string {
switch exp {
case shouldBeUsed:
return "used"
case shouldBeUnused:
return "unused"
case shouldBeQuiet:
return "quiet"
default:
panic("unreachable")
}
}
type key struct {
ident string
file string
line int
}
func (k key) String() string {
return fmt.Sprintf("%s:%d", k.file, k.line)
}
func relativePath(s string) string {
// This is only used in a test, so we don't care about failures, or the cost of repeatedly calling os.Getwd
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
s, err = filepath.Rel(cwd, s)
if err != nil {
panic(err)
}
return s
}
func relativePosition(pos token.Position) string {
s := pos.Filename
if pos.IsValid() {
if s != "" {
// This is only used in a test, so we don't care about failures, or the cost of repeatedly calling os.Getwd
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
s, err = filepath.Rel(cwd, s)
if err != nil {
panic(err)
}
s += ":"
}
s += fmt.Sprintf("%d", pos.Line)
if pos.Column != 0 {
s += fmt.Sprintf(":%d", pos.Column)
}
}
if s == "" {
s = "-"
}
return s
}
func check(t *testing.T, res *analysistest.Result) {
want := map[key]expectation{}
files := map[string]struct{}{}
isTest := false
for _, f := range res.Pass.Files {
filename := res.Pass.Fset.Position(f.Pos()).Filename
if strings.HasSuffix(filename, "_test.go") {
isTest = true
break
}
}
for _, f := range res.Pass.Files {
filename := res.Pass.Fset.Position(f.Pos()).Filename
if !strings.HasSuffix(filename, ".go") {
continue
}
files[filename] = struct{}{}
notes, err := expect.ExtractGo(res.Pass.Fset, f)
if err != nil {
t.Fatal(err)
}
for _, note := range notes {
posn := res.Pass.Fset.PositionFor(note.Pos, false)
switch note.Name {
case "quiet":
if len(note.Args) != 1 {
t.Fatalf("malformed directive at %s", posn)
}
if !isTest {
want[key{note.Args[0].(string), posn.Filename, posn.Line}] = expectation(shouldBeQuiet)
}
case "quiet_test":
if len(note.Args) != 1 {
t.Fatalf("malformed directive at %s", posn)
}
if isTest {
want[key{note.Args[0].(string), posn.Filename, posn.Line}] = expectation(shouldBeQuiet)
}
case "used":
if len(note.Args) != 2 {
t.Fatalf("malformed directive at %s", posn)
}
if !isTest {
var e expectation
if note.Args[1].(bool) {
e = shouldBeUsed
} else {
e = shouldBeUnused
}
want[key{note.Args[0].(string), posn.Filename, posn.Line}] = e
}
case "used_test":
if len(note.Args) != 2 {
t.Fatalf("malformed directive at %s", posn)
}
if isTest {
var e expectation
if note.Args[1].(bool) {
e = shouldBeUsed
} else {
e = shouldBeUnused
}
want[key{note.Args[0].(string), posn.Filename, posn.Line}] = expectation(e)
}
}
}
}
checkObjs := func(objs []Object, state expectation) {
for _, obj := range objs {
// if t, ok := obj.Type().(*types.Named); ok && t.TypeArgs().Len() != 0 {
// continue
// }
posn := obj.Position
if _, ok := files[posn.Filename]; !ok {
continue
}
// This key isn't great. Because of generics, multiple objects (instantiations of a generic type) exist at
// the same location. This only works because we ignore instantiations, but may lead to confusing test failures.
k := key{obj.ShortName, posn.Filename, posn.Line}
exp, ok := want[k]
if !ok {
t.Errorf("object at %s (%s) shouldn't exist but is %s (tests = %t)", relativePosition(posn), obj.ShortName, state, isTest)
continue
}
if false {
// Sometimes useful during debugging, but too noisy to have enabled for all test failures
t.Logf("%s handled by %q", k, obj)
}
delete(want, k)
if state != exp {
t.Errorf("object at %s (%s) should be %s but is %s (tests = %t)", relativePosition(posn), obj.ShortName, exp, state, isTest)
}
}
}
ures := res.Result.(Result)
checkObjs(ures.Used, shouldBeUsed)
checkObjs(ures.Unused, shouldBeUnused)
checkObjs(ures.Quiet, shouldBeQuiet)
for key, e := range want {
exp := e.String()
t.Errorf("object at %s:%d should be %s but wasn't seen", relativePath(key.file), key.line, exp)
}
}
func TestAll(t *testing.T) {
dirs, err := filepath.Glob(filepath.Join(analysistest.TestData(), "src", "example.com", "*"))
if err != nil {
t.Fatal(err)
}
for i, dir := range dirs {
dirs[i] = filepath.Join("example.com", filepath.Base(dir))
}
results := analysistest.Run(t, analysistest.TestData(), Analyzer.Analyzer, dirs...)
for _, res := range results {
check(t, res)
}
}
================================================
FILE: website/archetypes/default.md
================================================
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---
================================================
FILE: website/assets/js/base.js
================================================
// Overwrites Docsy's main.js, which implements various things we don't use
// - a top nav that fades away, looks cool with the promo block
// - popovers for client-side search
// - tooltips for the icons in the footer
// Pretend that Cash is jQuery
var jQuery = $;
================================================
FILE: website/assets/scss/_styles_project.scss
================================================
// prevent footer from being drawn off-screen on short pages, and prevent the "menu sliding out of view" effect on long pages
.td-sidebar__inner {
height: auto;
}
aside.td-sidebar-toc {
height: unset;
position: unset;
overflow: unset;
}
aside.td-sidebar-toc > .td-toc {
height: auto;
top: 6rem;
position: sticky;
}
body > div.td-outer > div.td-main > div.row.flex-xl-nowrap {
height: 100%;
}
.before-after {
margin-bottom: 0;
}
.before-after + .highlight {
margin-top: 0;
}
:target {
background-color: #ffa;
}
@include media-breakpoint-up(md) {
html {
scroll-padding-top: 5rem;
}
.td-offset-anchor {
scroll-margin-top: -1rem
}
}
// Undo Docsy's "Adjust anchors vs the fixed menu."
@include media-breakpoint-up(md) {
.td-offset-anchor:target {
display: unset;
position: unset;
top: unset;
visibility: unset;
}
h2[id]:before,
h3[id]:before,
h4[id]:before,
h5[id]:before {
display: unset;
content: unset;
margin-top: unset;
height: unset;
visibility: unset;
}
}
// Undo Docsy limiting the width of headers
.td-content {
> h1, >h2 {
@include media-breakpoint-up(lg) {
max-width: unset;
}
}
}
// Always show the navbar, no matter the screen size
.td-sidebar-nav {
display: block !important;
}
button[data-toggle="collapse"][data-target="#td-section-nav"] {
display: none;
}
// Properly indent nested lists even on mobile layouts. Why is Docsy limiting this to the desktop version?
.td-sidebar-nav__section .ul-1 ul {
padding-left: 1.5em;
}
#sponsors-bar {
color: #fff;
background-color: #F9F9F9;
div.sponsor {
margin: 12px;
img {
width: 160px;
}
}
}
.td-sidebar-nav .td-sidebar-link__page {
// use the same weight and color for sections and pages. this is
// fine because we never collapse sections.
color: unset;
font-weight: $font-weight-medium;
}
.td-toc #TableOfContents a {
// The ToC is already feint enough with it's font-weight, let's
// not make it even harder to read
color: unset;
}
@include media-breakpoint-up(md) {
#getting-started-distribution-packages {
dl {
columns: 3;
}
dd {
break-before: avoid;
}
}
}
details {
margin-bottom: 1rem;
}
================================================
FILE: website/assets/scss/_variables_project.scss
================================================
$primary: #30638E;
$secondary: #CE3262;
================================================
FILE: website/build.sh
================================================
#!/bin/sh
set -e
rm -rf ./public
mkdir -p data
mkdir -p content/docs/configuration/default_config
go run ./cmd/generate_checks/generate_checks.go >data/checks.json
go run ./cmd/generate_config/generate_config.go >content/docs/configuration/default_config/index.md
(
cd themes/docsy
# --omit=dev so we don't try to install Hugo as an NPM module
npm install --omit=dev
)
go run github.com/gohugoio/hugo@v0.110.0 --minify
================================================
FILE: website/cmd/generate_checks/generate_checks.go
================================================
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"regexp"
"sort"
"strings"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/quickfix"
"honnef.co/go/tools/simple"
"honnef.co/go/tools/staticcheck"
"honnef.co/go/tools/stylecheck"
)
type Output struct {
Checks map[string]*lint.Documentation
ByCategory map[string][]string
}
func category(check string) string {
idx := strings.IndexAny(check, "0123456789")
return check[:idx+1]
}
func main() {
output := Output{
Checks: map[string]*lint.Documentation{},
ByCategory: map[string][]string{},
}
groups := [][]*lint.Analyzer{
staticcheck.Analyzers,
simple.Analyzers,
stylecheck.Analyzers,
quickfix.Analyzers,
}
for _, group := range groups {
for _, a := range group {
doc := a.Doc.Compile()
doc.Text = convertText(doc.Text)
doc.TextMarkdown = convertText(doc.TextMarkdown)
output.Checks[a.Analyzer.Name] = doc
g := output.ByCategory[category(a.Analyzer.Name)]
output.ByCategory[category(a.Analyzer.Name)] = append(g, a.Analyzer.Name)
}
}
for _, v := range output.ByCategory {
sort.Strings(v)
}
out, err := json.MarshalIndent(output, "", "\t")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
}
func moreCodeFollows(lines []string) bool {
for _, line := range lines {
if line == "" {
continue
}
if strings.HasPrefix(line, " ") {
return true
} else {
return false
}
}
return false
}
var alpha = regexp.MustCompile(`^[a-zA-Z ]+$`)
func convertText(text string) string {
var buf bytes.Buffer
lines := strings.Split(text, "\n")
inCode := false
empties := 0
for i, line := range lines {
if inCode {
if !moreCodeFollows(lines[i:]) {
if inCode {
fmt.Fprintln(&buf, "```")
inCode = false
}
}
}
prevEmpties := empties
if line == "" && !inCode {
empties++
} else {
empties = 0
}
if line == "" {
fmt.Fprintln(&buf)
continue
}
if strings.HasPrefix(line, " ") {
line = line[4:]
if !inCode {
fmt.Fprintln(&buf, "```go")
inCode = true
}
}
onlyAlpha := alpha.MatchString(line)
out := line
if !inCode && prevEmpties >= 2 && onlyAlpha {
fmt.Fprintf(&buf, "#### %s\n", out)
} else {
fmt.Fprint(&buf, out)
fmt.Fprintln(&buf)
}
}
if inCode {
fmt.Fprintln(&buf, "```")
}
return strings.TrimSpace(buf.String())
}
================================================
FILE: website/cmd/generate_config/generate_config.go
================================================
package main
import (
"bytes"
"fmt"
"regexp"
"sort"
"github.com/BurntSushi/toml"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/config"
"honnef.co/go/tools/quickfix"
"honnef.co/go/tools/simple"
"honnef.co/go/tools/staticcheck"
"honnef.co/go/tools/stylecheck"
"honnef.co/go/tools/unused"
)
func main() {
cfg := config.DefaultConfig
checks := []string{"all"}
do := func(analyzers ...*lint.Analyzer) {
for _, a := range analyzers {
if a.Doc.NonDefault {
// Use backticks to quote the check name so TOML doesn't escape them
checks = append(checks, fmt.Sprintf("-{{< check `%s` >}}", a.Analyzer.Name))
}
}
}
do(simple.Analyzers...)
do(staticcheck.Analyzers...)
do(stylecheck.Analyzers...)
do(unused.Analyzer)
do(quickfix.Analyzers...)
sort.Slice(checks[1:], func(i, j int) bool {
return checks[i+1] < checks[j+1]
})
cfg.Checks = checks
buf := bytes.Buffer{}
toml.NewEncoder(&buf).Encode(cfg)
r := regexp.MustCompile(`(?m)^[a-z_]+`)
out := r.ReplaceAllString(buf.String(), "{{< option `$0` >}}")
fmt.Println("---")
fmt.Println("headless: true")
fmt.Println("---")
fmt.Println("```toml")
fmt.Print(out)
fmt.Println("```")
}
================================================
FILE: website/config.toml
================================================
baseURL = "https://staticcheck.dev"
languageCode = 'en-us'
title = 'Staticcheck'
theme = 'docsy'
navbar_logo = false
disableKinds = ['taxonomy', 'term']
disableAliases = true
enableInlineShortcodes = true
[outputs]
home = ["HTML", "REDIR"]
section = ["HTML"]
[mediaTypes]
[mediaTypes."text/netlify"]
delimiter = ""
[outputFormats]
[outputFormats.REDIR]
mediaType = "text/netlify"
baseName = "_redirects"
isPlainText = true
notAlternative = true
[markup.goldmark.renderer]
unsafe = true
[menu]
[[menu.main]]
name = 'GitHub'
pre = " "
url = 'https://github.com/dominikh/go-tools'
weight = 999
[markup]
[markup.highlight]
style = "tango"
[params]
description = 'Staticcheck is a state of the art linter for the Go programming language'
images = ['/img/logo.webp']
title = 'Staticcheck'
================================================
FILE: website/content/_index.md
================================================
---
title: Staticcheck
---
{{< blocks/lead type="section" color="primary" >}}
Staticcheck is a state of the art linter for the Go programming language.
Using static analysis, it finds bugs and performance issues, offers simplifications, and enforces style rules.
{{< /blocks/lead >}}
{{< blocks/section color="white" >}}
{{% blocks/feature title="Correctness" icon="fa-check" %}}
Code obviously has to be correct. Unfortunately, it never
is. Some code is more correct than other, but there'll
always be bugs. Tests catch some, your peers catch others,
and Staticcheck catches some more.
{{% /blocks/feature %}}
{{% blocks/feature title="Simplicity" icon="fa-circle" %}}
After correctness comes simplicity. There are many ways to
skin a cat (but please don't), but some are unnecessarily
elaborate. Staticcheck helps you replace complex code with
simple code.
{{% /blocks/feature %}}
{{% blocks/feature title="Maintainability" icon="fa-screwdriver" %}}
Code is a living thing. If it's not maintained regularly,
it will become stale and unreliable. It won't catch up
when its dependencies move on or guidelines change.
Staticcheck is like a sitter for your code for when you
don't have time.
{{% /blocks/feature %}}
{{% blocks/feature title="Exhaustiveness" icon="fas fa-tasks" url="/docs/checks" %}}
More than
{{< numchecks.inline >}}
{{- $c := len $.Site.Data.checks.Checks -}}
{{- sub $c (mod $c 25) -}}
{{< /numchecks.inline >}}
checks ensure that your code is in tip-top shape.
{{% /blocks/feature %}}
{{% blocks/feature title="Integration" icon="fab fa-github" %}}
Staticcheck can easily be integrated with your code review and CI systems, preventing buggy code from ever getting committed.
{{% /blocks/feature %}}
{{% blocks/feature title="Editor integration" icon="fa-i-cursor" url="https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck-bool" %}}
Staticcheck is part of most major Go editors and has been integrated with gopls, finding bugs and offering automatic fixes.
{{% /blocks/feature %}}
{{< /blocks/section >}}
================================================
FILE: website/content/changes/2017.2.md
================================================
---
title: Staticcheck 2017.2 release notes
linkTitle: 2017.2
weight: -1
---
The 2017.2 release of the staticcheck suite of tools focuses on
reducing friction – fewer false positives, more tools for suppressing
unwanted output, and JSON output for easier integration with other
tools.
## New features
### Linter directives for ignoring problems
In the past, the only ways to ignore reported problems was by using
the `-ignore` flag. This led to overreaching ignore rules
which weren't maintained regularly. Now, `//lint:ignore` and
`//lint:file-ignore` comments can be used to ignore
problems, either on specific lines or file-wide. A full description of
these directives, their syntax and their behavior can be found
in [the documentation]({{< relref "/docs/configuration#ignoring-problems" >}}).
A related change adds the `-show-ignored` command line
flag, which outputs problems that would otherwise be ignored by
directives. This is primarily of use with the JSON output format,
for custom front ends.
### Output formats
All staticcheck tools now support multiple output formats, selectable
with the `-f` flag.
Currently, two formats are supported. The first format is
`text`, which is the default and uses the existing terminal
output format. The other is `json`, which emits JSON. The
output is a stream of objects, allowing for a future streaming output
mode. Each object uses the following example schema:
```json
{
"checker": "staticcheck",
"code": "SA4006",
"location": {
"file": "/usr/lib/go/src/database/sql/sql_test.go",
"line": 2701,
"column": 5
},
"message": "this value of err is never used",
"ignored": false
}
```
### Control over the exit code of megacheck
Megacheck, the tool for running multiple checkers at once, now has
per checker flags for controlling the overall exit code. Previously,
megacheck would exit non-zero if any checker found a problem. Now it
is possible to configure for each checker whether it should cause a
non-zero exit, by using the `-<checker>.exit-non-zero`
flags. This flag defaults to false for _gosimple_ and to true for
the other checkers.
## Changes to checks
### Support for `NoCopy` in _unused_
The _unused_ tool now understands `NoCopy` sentinel types. The
`NoCopy` type, which is canonically a struct with no fields and only a
single, empty `Lock` method, can be used to mark structs as not safe
for copying. By declaring a field of this type, _go vet_ will complain
when it sees instances of the struct being copied.
In the past, _unused_ marked these fields as unused, now it ignores
them.
### Detection of deprecated identifiers
{{< check "SA1019" >}} now
correctly identifies deprecated methods, in addition to fields and
package-level objects. Additionally, staticcheck now keeps track of
when each identifier in the Go standard library was deprecated, so
that using `-go <version>` can correctly
ignore deprecation warnings that don't apply to the targeted Go
version.
### Other
- {{< check "SA4017" >}} no longer reports pure functions that are stubs – functions that immediately panic or return a constant.
- {{< check "SA5007" >}} no longer flags infinite recursion when the function call is spawned as a new goroutine.
- {{< check "SA6002" >}} now recognizes that `unsafe.Pointer` is a pointer type.
- {{< check "S1005" >}} no longer suggests `for range` when targeting a version older than Go 1.4.
- {{< check "S1026" >}} has been removed. In some rare instances, copying a string is necessary, and all common ways of doing this were incorrectly flagged by the check.
## Other changes
- The `-ignore` flag now supports ignoring checks in all packages, by using `*` as the path.
- `//line` directives are now being ignored when reporting problems. That is, problems will always be reported for the actual position in the Go files they occur.
- From now on, only the first compilation error encountered will be reported.
The tools expect to be run on valid Go code and there was little (if any) value in reporting all compilation errors encountered, especially because simple errors can lead to many follow-up errors.
## Staticcheck 2017.2.1 Release Notes {#2017.2.1}
The 2017.2.1 release of the staticcheck suite of tools is the first
bug fix release, fixing one bug.
### Fixed bugs
Staticcheck 2017.2 made the detection of deprecated objects
Go-version aware. Unfortunately, this only worked correctly for
fields and methods, but not package-level objects. This release
fixes that.
## Staticcheck 2017.2.2 Release Notes {#2017.2.2}
The 2017.2.2 release of the staticcheck suite of tools is the second
bug fix release, fixing several bugs.
### Fixed bugs
- _unused_: correctly apply the NoCopy exemption when using the `-exported` flag.
- _keyify_: support external test packages (`package foo_test`)
- _staticcheck_: disable {{< check "SA4005" >}} – the check, in its current form, is prone to false positives and will be reimplemented in a future release.
================================================
FILE: website/content/changes/2019.1.md
================================================
---
title: Staticcheck 2019.1 release notes
linkTitle: 2019.1
weight: -2
---
## Big restructuring
At the core of the 2019.1 release lies the grand restructuring of all of the Staticcheck tools.
All of the individual checkers, as well as megacheck, have been merged into a single tool,
which is simply called `staticcheck`.
From this point forward, `staticcheck` will be _the_ static analyzer for Go code.
It will cover all of the existing categories of checks – bugs, simplifications, performance –
as well as future categories, such as the new style checks.
This change makes a series of simplifications possible.
Per-tool command line flags in megacheck have been replaced with unified flags
(`-checks` and `-fail`)
that operate on arbitrary subsets of checks.
Consumers of the JSON output no longer need to know about different checker names
and can instead rely solely on user-controllable severities.
And not to be neglected: gone is the silly name of _megacheck_.
This change will require some changes to your pipelines.
Even though the gosimple, unused, and megacheck tools still exist, they have been deprecated
and will be removed in the next release of staticcheck.
Additionally, megacheck's `-.exit-non-zero` flags have been rendered inoperable.
Instead, you will have to use the `-fail` flag.
Furthermore,, `-fail` defaults to `all`, meaning all checks will cause non-zero exiting.
Previous versions of megacheck had different defaults for different checkers, trying to guess the user's intention.
Instead of guessing, staticcheck expects you to provide the correct flags.
Since all of the tools have been merged into staticcheck, it will no longer run just one group of checks.
This may lead to additional problems being reported.
To restore the old behavior, you can use the new `-checks` flag.
`-checks "SA*"` will run the same set of checks that the old staticcheck tool did.
The same flag should be used in place of megacheck's – now deprecated – `-.enabled` flags.
Details on all of the command-line flags can be found [in the documentation.]({{< relref "/docs/#cli" >}})
## Configuration files
Staticcheck 2019.1 adds support for per-project configuration files.
With these it will be possible to standardize and codify linter settings, the set of enabled checks, and more.
Please see the [documentation page on configuration]({{< relref "/docs/#configuration" >}}) for all the details!
## Build system integration
Beginning with this release, staticcheck calls out to the tools of the underlying build system
(`go` for most people) to determine the list of Go files to process.
This change should not affect most people.
It does, however, have some implications:
the system that staticcheck runs on needs access to a full Go toolchain –
just the source code of the standard library no longer suffices.
Furthermore, setting `GOROOT` to use a different Go installation no longer works as expected.
Instead, `PATH` has to be modified so that `go` resolves to the desired Go command.
This change has been necessary to support Go modules.
Additionally, it will allow us to support alternative build systems such as Bazel in the future.
## Handling of broken packages
We have redesigned the way staticcheck handles broken packages.
Previously, if you ran `staticcheck ...` and any package wouldn't compile,
staticcheck would refuse to check any packages whatsoever.
Now, it will skip broken packages, as well as any of their dependents, and check only the remaining packages.
Any build errors that are encountered will be reported as problems.
## Checks
### New checks
Staticcheck 2019.1 adds a new category of checks, ST1.
ST1 contains checks for common style violations – poor variable naming, incorrectly formatted comments and the like.
It brings the good parts of [golint](https://github.com/golang/lint) to staticcheck,
and adds some checks of its own.
In addition, some other checks have been added.
{{< check "S1032" >}} recommends replacing `sort.Sort(sort.StringSlice(...))` with `sort.Strings(...)`;
similarly for other types that have helpers for sorting.
{{< check "SA9004" >}} flags groups of constants where only the first one is given an explicit type.
{{< check "SA1025" >}} checks for incorrect uses of `(*time.Timer).Reset`.
### Changed checks
Several checks have been tweaked, either making them more accurate or finding more issues.
{{< check "S1002" >}} no longer applies to code in tests.
While `if aBool == true` is usually an anti-pattern,
it can feel more natural in unit tests,
as it mirrors the `if got != want` pattern.
{{< check "S1005" >}} now flags `for x, _ := range` because of the unnecessary blank assignment.
{{< check "S1007" >}} no longer suggests using raw strings for regular expressions containing backquotes.
{{< check "S1016" >}} now considers the targeted Go version.
It will no longer suggest type conversions between struct types with different field tags
unless Go 1.8 or later is being targeted.
{{< check "SA1000" >}} now checks arguments passed to the `regexp.Match` class of functions.
{{< check "SA1014" >}} now checks arguments passed to `(*encoding/xml.Decoder).DecodeElement`.
{{< check "SA6002" >}} now realizes that unsafe.Pointer is a pointer.
{{< check "U1000" >}} has fewer false positives in the presence of embedding.
### Removed checks
{{< check "S1013" >}} has been removed,
no longer suggesting replacing `if err != nil { return err }; return nil` with `return err`.
This check has been the source of contention and more often than not, it reduced the consistency of the surrounding code.
## Deprecation notices
This release deprecates various features of staticcheck.
These features will be removed in the next release.
As already explained earlier, the _unused_, _gosimple_, and _megacheck_ tools
have been replaced by _staticcheck_.
Similarly, the flags `-.enabled` and `-.exit-non-zero`
have been replaced by `-checks` and `-fail`.
Finally, the `-ignore` flag has been replaced
by [linter directives]({{< relref "/docs/#ignoring-problems" >}}).
## Binary releases
Beginning with this release, we're publishing
[prebuilt binaries to GitHub](https://github.com/dominikh/go-tools/releases).
These releases still require a functioning Go installation in order to operate, however.
## Other changes
We've removed the `-min_confidence` flag.
This flag hasn't been doing anything for years.
A new formatter called [Stylish]({{< relref "/docs/running-staticcheck/cli/formatters" >}}) (usable with `-f stylish`)
provides output that is designed for easier consumption by humans.
Due to the restructuring of checkers, the `checker` field in JSON output has been replaced
with the `severity` field.
## Staticcheck 2019.1.1 Release Notes {#2019.1.1}
The 2019.1.1 release of Staticcheck is the first bug fix release, fixing several bugs and improving performance.
### Changes
- The ST category of checks no longer flag style issues of
aliased types when the aliased type exists in a package
we aren't explicitly checking. This avoids crashes and
erratic error reports.
- Compiler errors now have correct position information.
- A crash in the Stylish reporter has been fixed.
- We no longer flag unused objects that belong to cgo internals.
- The {{< check "U1000" >}} check has been optimized, reducing its memory
usage and runtime.
================================================
FILE: website/content/changes/2019.2.md
================================================
---
title: Staticcheck 2019.2 release notes
linkTitle: 2019.2
weight: -3
---
## Performance improvements {#performance}
Staticcheck 2019.2 brings major performance improvements and a
reduction in memory usage.
Staticcheck has been redesigned to only keep those packages in memory that are actively being processed.
This allows for much larger workspaces to be checked in one go.
While previously it may have been necessary to split a list of packages into many invocations of `staticcheck`,
this is now handled intelligently and efficiently by Staticcheck itself.
In particular, memory usage is now closely tied to parallelism:
having more CPU cores available allows for more packages to be processed in parallel,
which increases the number of packages held in memory at any one time.
Not only does this make good use of available resources –
systems with more CPU cores also tend to have more memory available –
it also exposes a single, easy to use knob for trading execution time for memory use.
By setting `GOMAXPROCS` to a value lower than the number of available cores,
memory usage of Staticcheck will be reduced, at the cost of taking longer to complete.
We've observed reductions in memory usage of 2x to 8x when checking large code bases.
Package
2019.1.1
2019.2¹
Change
net/http
3.543 s / 677 MB
3.747 s / 254 MB
+5.76% / -62.48%
strconv
1.628 s / 294 MB
1.678 s / 118 MB
+3.07% / -59.86%
image/color
1.304 s / 225 MB
1.702 s / 138 MB
+30.52% / -38.67%
std
26.234 s / 3987 MB
19.444 s / 1054 MB
-25.88% / -73.56%
github.com/cockroachdb/cockroach/pkg/...
88.644 s / 15959 MB
93.798 s / 4156 MB
+5.81% / -73.96%
¹: The fact cache was empty for all benchmarks.
In addition, Staticcheck now employs caching to speed up repeated checking of packages.
In the past, when checking a package, all of its dependencies had to be loaded from source and analyzed.
Now, we can make use of Go's build cache, as well as cache our own analysis facts.
This makes Staticcheck behave a lot more like `go build`, where repeated builds are much faster.
| Package | Uncached | Cached | Change |
|------------------------------------------|--------------------|--------------------|------------------------|
| net/http | 3.747 s / 254 MB | 1.545 s / 195 MB | -58.77% / -23.23% |
| strconv | 1.678 s / 118 MB | 0.495 s / 57 MB | -70.5% / -51.69% |
| image/color | 1.702 s / 138 MB | 0.329 s / 31 MB | -80.67% / -77.54% |
| std | 19.444 s / 1054 MB | 15.099 s / 887 MB | -22.35% / -15.84% |
| github.com/cockroachdb/cockroach/pkg/... | 93.798 s / 4156 MB | 47.205 s / 2516 MB | -49.67% / -39.46% |
This combination of improvements not only compensates for the
increased memory usage that 2019.1 introduced, it also brings the
memory usage and execution times way below the levels of those seen in the
2017.2 release, which had previously been our most efficient
release.
It should be noted that all of these improvements are part of the `staticcheck` command itself, not the individual checks.
Tools such as golangci-lint will have to replicate our efforts to benefit from these improvements.
## The go/analysis framework {#go-analysis}
Part of the redesign of Staticcheck involved porting our code to the [go/analysis](https://godoc.org/golang.org/x/tools/go/analysis) framework.
The go/analysis framework is a framework for writing static analysis tools such as Staticcheck and go vet.
It provides an API that enables interoperability between different analyses and analysis drivers – drivers being the code that actually executes analyses.
The intention is that any driver can trivially use any analysis that is implemented using go/analysis.
With the exception of {{< check "U1000" >}}, all of our checks are now go/analysis analyses. Furthermore, the `staticcheck` command is now a go/analysis driver.
With our move to this framework, we enable other drivers to reuse our checks without having to patch them.
This should be of particular interest to golangci-lint, which previously took to patching Staticcheck, sometimes in subtly incorrect ways.
Another high-profile go/analysis driver is gopls, the Go language server. It will now be much easier for gopls to use Staticcheck to analyze code, should it so desire.
Theoretically it would also allow us to use third-party analyses as part of Staticcheck.
Due to quality control reasons, however, we will likely refrain from doing so.
Nonetheless it would be trivial for users to maintain internal forks of `cmd/staticcheck` that use third-party analyses.
## Improvements to the CLI {#cli}
We've made several minor improvements to the command-line interface of `staticcheck` that improve usability and debuggability.
### SIGINFO handler {#cli-siginfo}
Upon receiving the SIGINFO signal – or SIGUSR1 on platforms that lack
SIGINFO – Staticcheck will dump statistics, such as the current phase
and how many packages are left to analyze.
```text
Packages: 37/619 initial, 38/1011 total; Workers: 8/8; Problems: 73
```
### Explaining checks {#cli-explain}
Using the new `-explain` flag, a check's documentation can be displayed right in the terminal,
eliminating the need to visit the website.
```text
$ staticcheck -explain S1007
Simplify regular expression by using raw string literal
Raw string literals use ` instead of " and do not support
any escape sequences. This means that the backslash (\) can be used
freely, without the need of escaping.
Since regular expressions have their own escape sequences, raw strings
can improve their readability.
Before:
regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
After:
regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
Available since
2017.1
```
### -debug.version {#cli-debug-version}
The `-debug.version` flag causes `staticcheck` to print
detailed version information, such as the Go version used to compile
it, as well as the versions of all dependencies if built using Go
modules. This feature is intended for debugging issues, and we will
ask for its output from users who file issues.
```text
$ staticcheck -debug.version
staticcheck (devel, v0.0.0-20190602125119-5a4a2f4a438d)
Compiled with Go version: go1.12.5
Main module:
honnef.co/go/tools@v0.0.0-20190602125119-5a4a2f4a438d (sum: h1:U5vSGN1Bjr0Yd/4pRcp8iRUCs3S5TIPzoAeTEFV2aiU=)
Dependencies:
github.com/BurntSushi/toml@v0.3.1 (sum: h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=)
golang.org/x/tools@v0.0.0-20190530171427-2b03ca6e44eb (sum: h1:mnQlcVx8Qq8L70HV0DxUGuiuAtiEHTwF1gYJE/EL9nU=)
```
### Enabling unused's whole program mode {#cli-unused}
When we merged `unused` into `staticcheck`, we lost the ability to specify the `-exported` flag to report unused exported identifiers.
Staticcheck 2019.2 restores this ability with the new `-unused.whole-program` flag.
### Range information in diagnostics {#cli-ranges}
Many of our checks now emit `[start, end]` ranges for findings instead of just positions.
These ranges can be accessed via the `json` output formatter, as well as by using `go/analysis.Diagnostic` directly, such as in gopls.
Note that not all checks are able to emit range information.
## Installing Staticcheck as a module {#module}
As part of the 2019.2 release, we've turned Staticcheck into a Go module.
From now on, if using Go modules, you can install specific versions of Staticcheck with `go get honnef.co/go/tools/cmd/staticcheck@`,
though do note that older releases do not have a `go.mod` file.
You can still download them as modules, but Go will record indirect dependencies in the main module's `go.mod` file, and no minimum versions are specified.
Staticcheck will not use Semantic Versioning for its releases.
It is our belief that Semver is a poor fit for applications and is more suited towards libraries.
For example, almost every release of Staticcheck has backwards incompatible changes to some APIs that aren't meant for public consumption,
but which we expose nevertheless so that tinkerers can use them.
However, we use so-called _pre-release versions_ of the form `v0.0.0-2019.2`.
These allow us to embed our versioning scheme in that of Semver, with correct sorting and updating of versions.
Furthermore, these versions ensure that `go get ...`,
if not specifying an explicit version (that is, if using the query `latest`),
will install the latest released version of Staticcheck and not the master branch.
While you can use these pre-release version numbers directly, you can also use the canonical versions of the form `2019.2` instead.
The Go tool will automatically translate these versions to the appropriate pre-releases.
To install the master branch, use `go get honnef.co/go/tools/cmd/staticcheck@master`
## Removal of deprecated functionality {#deprecated}
Staticcheck 2019.1 deprecated the `unused`, `gosimple`, and `megacheck`
utilities, as they have been merged into `staticcheck`. Furthermore, it deprecated the `-ignore` flag,
which has been replaced by [linter directives]({{< relref "/docs/#ignoring-problems" >}}).
This release no longer includes these deprecated utilities, nor does
it provide the deprecated flag.
## Checks {#checks}
### New checks {#checks-new}
Numerous new checks have been added in this release:
- {{< check "S1033" >}} flags unnecessary guards around calls to `delete`.
- {{< check "S1034" >}} simplifies type switches involving redundant type assertions.
- {{< check "SA1026" >}} flags attempts at marshaling invalid types.
- {{< check "SA1027" >}} flags incorrectly aligned atomic accesses.
- {{< check "SA4020" >}} flags unreachable case clauses in type switches.
- {{< check "SA4021" >}} flags calls to append with a single argument, as `x = append(y)` is equivalent to `x = y`.
- {{< check "SA5008" >}} flags certain kinds of invalid struct tags.
- {{< check "SA5009" >}} verifies the correctness of Printf calls.
- {{< check "SA6005" >}} flags inefficient string comparisons involving `strings.ToLower`
or `strings.ToUpper` when they can be replaced with `strings.EqualFold`.
- {{< check "SA9005" >}} flags attempts at marshaling structs with no public fields nor custom marshaling.
- {{< check "ST1017" >}} flags so-called [yoda conditions](https://en.wikipedia.org/wiki/Yoda_conditions),
which take the form of `if 42 == x`.
- {{< check "ST1018" >}} flags string literals containing zero-width characters.
### Changed checks {#checks-changed}
Several checks have been improved:
- {{< check "SA1019" >}} now flags imports of deprecated packages.
- {{< check "SA4000" >}} no longer flags comparisons between custom float types. Additionally, it avoids a false positive caused by cgo.
- {{< check "SA4006" >}} no longer flags unused values in code generated by goyacc. This avoids noise caused by the nature of the generated state machine.
- {{< check "ST1005" >}} no longer flags error messages that start with capitalized type names.
- {{< check "ST1006" >}} no longer flags receiver names in generated code.
- {{< check "SA5002" >}} no longer suggests replacing `for false {` with `for {`.
- Added "SIP" and "RTP" as default initialisms to {{< check "ST1003" >}}.
- {{< check "SA1006" >}}, {{< check "SA4003" >}}, {{< check "S1017" >}}, and {{< check "S1020" >}} match more code patterns.
- {{< check "S1021" >}} is less eager to merge declarations and assignments when multiple assignments are involved.
- {{< check "U1000" >}} has been rewritten, eliminating a variety of false positives.
## Sustainable open source and a personal plea {#sustainable-open-source}
Staticcheck is an open source project developed primarily by me, Dominik Honnef, in my free time.
While this model of software development has gotten increasingly common, it is not very sustainable.
Time has to be split between open source work and paid work to sustain one's life.
This is made especially unfortunate by the fact that hundreds of companies rely on open source each day,
but few consider giving back to it, even though it would directly benefit their businesses,
ensuring that the software they rely on keeps being developed.
I have long been soliciting donations for Staticcheck [on Patreon](https://www.patreon.com/dominikh) to make its development more sustainable.
A fair number of individuals have generously pledged their support and I am very grateful to them.
Unfortunately, only few companies support Staticcheck's development, and I'd like for that to change.
To people who are familiar with Patreon, it might've always seemed like an odd choice for a software project.
Patreon focuses on art and creative work, and on individuals supporting said work, not companies.
I am therefore excited to announce my participation in [GitHub Sponsors](https://github.com/sponsors),
a new way of supporting developers, directly on GitHub.
GitHub Sponsors allows you to easily support developers by sponsoring them on a monthly basis, [via a few simple clicks.](https://github.com/users/dominikh/sponsorship)
It is fully integrated with the platform and can use your existing billing information, making it an effortless process.
**To encourage more company sponsorships I offer to display your company's logo prominently on
[Staticcheck's website](https://staticcheck.dev/)**
for
[$250 USD a month](https://github.com/users/dominikh/sponsorship?utf8=%E2%9C%93&tier_id=MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4yNTAy&editing=false),
to show my appreciation for your contribution and to show to the world how much you care about code quality.
Please don't hesitate [contacting me directly](mailto:dominik@honnef.co) if neither GitHub Sponsors nor Patreon seem suitable to you but you'd like to support me nevertheless.
I am sure we can work something out.
## Staticcheck 2019.2.1 release notes {#2019.2.1}
The 2019.2 release has an unfortunate bug that prevents Staticcheck from running on 32-bit architectures, causing it to crash unconditionally.
This release fixes that crash.
## Staticcheck 2019.2.2 release notes {#2019.2.2}
Staticcheck 2019.2.2 contains the following user-visible fixes:
- {{< check "S1008" >}} now skips if/else statements where both branches return the same value.
- {{< check "SA4006" >}} now considers a value read when a switch statement reads it, even if the switch statement has no branches.
- 2019.2 introduced a bug that made it impossible to enable non-default checks via configuration files. This is now possible again.
- 2019.2 introduced a bug that made the `-tags` command line argument ineffective, making it impossible to pass in build tags. This is now possible again.
- From this release onward, we will use pseudo versions of the form
`v0.0.1-.` instead of `v0.0.0-.`.
This fixes an issue where `go get` would prefer an older commit over a newer released version due to the way versions sort.
## Staticcheck 2019.2.3 release notes {#2019.2.3}
Staticcheck 2019.2.3 is a re-release of 2019.2.2.
Its pre-built binaries, which can be found on GitHub,
have been built with Go 1.13, to enable checking of code that uses language features introduced in Go 1.13.
================================================
FILE: website/content/changes/2020.1.md
================================================
---
title: Staticcheck 2020.1 release notes
linkTitle: 2020.1
weight: -4
---
## Introduction to Staticcheck 2020.1 {#introduction}
Staticcheck 2020.1 introduces UI improvements, speed enhancements, and
a number of [new](#checks-new) as well as [improved](#checks-changed) checks. Additionally, it is the
first release to support the upcoming Go 1.14.
## UI improvements {#ui-improvements}
We've improved the output of the `staticcheck` command as well as
Staticcheck's integration with [gopls](https://github.com/golang/tools/tree/master/gopls) to make it easier to understand
the problems that are being reported.
Related information describes the source of a problem, or why
Staticcheck believes that there is a problem. Take the following
piece of code for example:
```go
func fn(x *int) {
if x == nil {
log.Println("x is nil, returning")
}
// lots of code here
log.Println(*x)
}
```
Staticcheck 2020.1 will produce the following output:
```text
foo.go:6:14: possible nil pointer dereference (SA5011)
foo.go:2:5: this check suggests that the pointer can be nil
```
The actual problem that is being reported is the "possible nil pointer
dereference". Staticcheck also explains why it believes that `x` might
be nil, namely the comparison on line 2.
When using the [`text`]({{< relref "/docs/running-staticcheck/cli/formatters#text" >}}) or [`stylish`]({{< relref "/docs/running-staticcheck/cli/formatters#stylish" >}}) formatters, related information will
appear as indented lines. The [`json`]({{< relref "/docs/running-staticcheck/cli/formatters#json" >}}) formatter adds a new field
`related` to problems, containing position information as well as the
message. Editors that use gopls will also display the related
information.
Related information should make it easier to understand why Staticcheck
is flagging code, and how to fix problems.
Integration with gopls has seen some other improvements as well¹. We
now emit better position information that more accurately reflects the
true source of a problem. The most obvious example is that a missing
package comment will no longer underline the entire file. Similarly,
invalid function arguments will be highlighted individually, instead
of highlighting the call as a whole. Finally, some problems can now be
automatically fixed by using quick fixes.
¹: due to the nature of Staticcheck's integration with gopls, gopls
will need to update their dependency on Staticcheck before benefiting
from these changes.
## Better caching {#caching}
The 2019.2 release introduced caching to Staticcheck, greatly speeding
up repeated runs. However, the caching only applied to dependencies;
the packages under analysis still had to be analyzed anew on every
invocation to compute the list of problems. Staticcheck 2020.1
introduces caching of problems found, so that repeat runs for
unchanged packages are virtually instantaneous.
## Checks {#checks}
### New checks {#checks-new}
Numerous new checks have been added in this release:
- {{< check "S1035" >}} flags redundant calls to `net/http.CanonicalHeaderKey`.
- {{< check "S1036" >}} flags unnecessary guards around map accesses.
- {{< check "S1037" >}} flags unnecessarily elaborate ways of sleeping.
- {{< check "S1038" >}} flags unnecessary uses of `fmt.Sprintf`, such as `fmt.Println(fmt.Sprintf(...))`.
- {{< check "S1039" >}} flags uses of `fmt.Sprint` with single string literals.
- {{< check "SA1028" >}} flags uses of `sort.Slice` on non-slices.
- {{< check "SA1029" >}} flags inappropriate keys in calls to context.WithValue.
- {{< check "SA4022" >}} flags comparisons of the kind `if &x == nil`.
- {{< check "SA5010" >}} flags impossible type assertions.
- {{< check "SA5011" >}} flags potential nil pointer dereferences.
- {{< check "ST1019" >}} flags duplicate imports.
- {{< check "ST1020" >}} checks the documentation of exported functions.
- {{< check "ST1021" >}} checks the documentation of exported types.
- {{< check "ST1022" >}} checks the documentation of exported variables and constants.
{{< check "ST1020" >}}, {{< check "ST1021" >}} and {{< check "ST1022" >}} are not enabled by default.
### Changed checks {#checks-changed}
Several checks have been improved:
- {{< check "S1036" >}} detects more kinds of unnecessary guards around map accesses.
- {{< check "S1008" >}} reports more easily understood diagnostics.
- {{< check "S1025" >}} no longer suggests using `v.String()` instead of `fmt.Sprintf("%s", v)` when `v` is a `reflect.Value`. `fmt` gives special treatment to `reflect.Value` and the two results differ.
- {{< check "SA1015" >}} no longer flags uses of `time.Tick` in packages that implement [Cobra](https://github.com/spf13/cobra) commands.
- {{< check "SA1019" >}} no longer misses references to deprecated packages when said packages have been vendored.
- {{< check "SA4000" >}} no longer flags comparisons of the kind `x == x` and `x != x` when `x` has a compound type involving floats.
- {{< check "SA4003" >}} no longer flags comparisons of the kind `x <= 0` when `x` is an unsigned integer. While it is true that `x <= 0` can be written more specifically as `x == 0`, this is not a helpful suggestion in reality. A lot of people use `x <= 0` as a defensive measure, in case `x` ever becomes signed. Also, unlike all the other warnings made in the check, `x <= 0` is neither a tautology nor a contradiction, it is merely less precise than it could be.
- {{< check "SA4016" >}} now detects silly bitwise ops of the form `x & k` where `k` is defined as `const k = iota`.
- {{< check "SA4018" >}} no longer flags self-assignments involving side effects; for example, it won't flag `x[fn()] = x[fn()]` if `fn` isn't pure.
- {{< check "SA5008" >}} now permits duplicate instances of various struct tags used by `github.com/jessevdk/go-flags`.
- {{< check "SA5009" >}} now correctly recognizes that `unsafe.Pointer` is a pointer type that can be used with verbs such as `%p`. Furthermore, it validates calls to `golang.org/x/xerrors.Errorf`.
- {{< check "SA5009" >}} now understands `fmt.Printf` verbs that were changed and added in Go 1.13. Specifically, it now recognizes the new `%O` verb, and allows the use of `%x` and `%X` on floats and complex numbers.
- {{< check "ST1003" >}} has learned about several new initialisms.
- {{< check "ST1011" >}} no longer misses variable declarations with inferred types.
- {{< check "ST1016" >}} now ignores the names of method receivers of methods declared in generated files.
- {{< check "ST1020" >}}, {{< check "ST1021" >}}, and {{< check "ST1022" >}} no longer enforce comment style in generated code.
## General bug fixes {#bugs}
The following bugs were fixed:
- A race condition in the {{< check "U1000" >}} check could occasionally lead to sporadic false positives.
- Some files generated by _goyacc_ weren't recognized as being generated.
- `staticcheck` no longer fails to check packages that consist exclusively of tests.
## Staticcheck 2020.1.1 release notes {#2020.1.1}
The 2020.1 release neglected to update the version string stored in
the binary, causing `staticcheck -version` to incorrectly emit `(no version)`.
## Staticcheck 2020.1.2 release notes {#2020.1.2}
The 2020.1.1 release incorrectly identified itself as version 2020.1.
## Staticcheck 2020.1.3 release notes {#2020.1.3}
This release fixes two bugs involving `//lint:ignore` directives:
- When ignoring U1000 and checking a package that contains tests,
Staticcheck would incorrectly complain that the linter directive
didn't match any problems, even when it did.
- On repeated runs, the position information for a this linter directive didn't match anything report
would either be missing, or be wildly incorrect.
## Staticcheck 2020.1.4 release notes {#2020.1.4}
This release adds special handling for imports of the
deprecated `github.com/golang/protobuf/proto` package.
[github.com/golang/protobuf](https://github.com/golang/protobuf)
has deprecated the `proto` package, but
their `protoc-gen-go` still imports the package and uses
one of its constants, to enforce a weak dependency on a
sufficiently new version of the legacy package .
Staticcheck would flag the import of this deprecated package in all
code generated by protoc-gen-go. Instead of forcing the project to
change their project structure, we choose to ignore such imports in
code generated by protoc-gen-go. The import still gets flagged in code
not generated by protoc-gen-go.
You can find more information about this in the [upstream issue](https://github.com/golang/protobuf/issues/1077).
## Staticcheck 2020.1.5 release notes {#2020.1.5}
This release fixes a [crash in the pattern matching engine]({{< issueref 806 >}})
and a [false positive in SA4006]({{< issueref 733 >}}).
## Staticcheck 2020.1.6 release notes {#2020.1.6}
This release makes the following fixes and improvements:
- Staticcheck no longer panics when encountering files that have the following comment: `// Code generated DO NOT EDIT.`
- {{< check "SA4016" >}} no longer panics when checking bitwise operations that involve dot-imported identifiers.
- Fixed the suggested fix offered by {{< check "S1004" >}}.
- Fixed a false positive involving byte arrays in {{< check "SA5009" >}}.
- Fixed a false positive involving named byte slice types in {{< check "SA5009" >}}.
- Added another heuristic to avoid flagging function names in error messages in {{< check "ST1005" >}}.
- {{< check "SA3000" >}} will no longer flag missing calls to `os.Exit` in `TestMain` functions if targeting Go 1.15 or newer.
================================================
FILE: website/content/changes/2020.2.md
================================================
---
title: Staticcheck 2020.2 release notes
linkTitle: "2020.2 (v0.1.0)"
weight: -5
---
## Performance improvements {#performance-improvements}
The primary focus of this release is a major improvement in performance, significantly reducing memory usage while also reducing runtimes.
Uncached, GOMAXPROCS=1
Package
2020.1.6
2020.2
Delta
Stats
image/color
2.41s ±19%
2.00s ±14%
-17.08%
p=0.000, n=10+10
k8s.io/kubernetes/pkg/...
276s ± 1%
219s ± 1%
-20.62%
p=0.000, n=10+10
net/http
6.18s ± 1%
5.61s ± 5%
-9.21%
p=0.000, n=8+10
std
49.5s ± 1%
42.5s ± 1%
-14.04%
p=0.000, n=9+10
strconv
2.49s ± 9%
2.19s ±12%
-12.08%
p=0.001, n=10+10
image/color
167MB ±26%
146MB ±19%
-12.62%
p=0.043, n=10+10
k8s.io/kubernetes/pkg/...
2.14GB ± 1%
0.45GB ±13%
-79.09%
p=0.000, n=10+10
net/http
216MB ± 6%
166MB ±18%
-23.11%
p=0.000, n=10+10
std
972MB ± 3%
284MB ± 9%
-70.82%
p=0.000, n=8+10
strconv
155MB ±21%
139MB ±29%
~
p=0.063, n=10+10
Cached, GOMAXPROCS=1
Package
2020.1.6
2020.2
Delta
Stats
image/color
160ms ± 0%
107ms ± 7%
-33.13%
p=0.000, n=8+10
k8s.io/kubernetes/pkg/...
12.7s ± 1%
6.9s ± 1%
-45.26%
p=0.000, n=9+10
net/http
370ms ± 0%
230ms ± 0%
-37.84%
p=0.000, n=8+8
std
2.52s ± 1%
1.31s ± 1%
-48.13%
p=0.000, n=10+9
strconv
164ms ± 4%
110ms ± 0%
-32.93%
p=0.000, n=10+10
image/color
38.6MB ± 4%
20.8MB ± 1%
-45.96%
p=0.000, n=9+10
k8s.io/kubernetes/pkg/...
863MB ± 4%
283MB ± 2%
-67.28%
p=0.000, n=10+10
net/http
70.5MB ± 5%
25.8MB ± 2%
-63.48%
p=0.000, n=10+9
std
243MB ±16%
73MB ± 8%
-70.00%
p=0.000, n=10+10
strconv
37.2MB ± 2%
21.3MB ± 1%
-42.76%
p=0.000, n=9+10
Uncached, GOMAXPROCS=32
Package
2020.1.6
2020.2
Delta
Stats
image/color
1.19s ±21%
1.06s ±12%
~
p=0.115, n=10+8
k8s.io/kubernetes/pkg/...
27.0s ± 2%
22.4s ± 2%
-16.96%
p=0.000, n=10+10
net/http
2.24s ±11%
2.23s ±10%
~
p=0.870, n=10+10
std
7.14s ± 5%
5.10s ± 9%
-28.56%
p=0.000, n=10+9
strconv
1.24s ±26%
1.18s ±21%
~
p=0.753, n=10+10
image/color
143MB ± 7%
141MB ± 6%
~
p=0.515, n=8+10
k8s.io/kubernetes/pkg/...
5.77GB ± 6%
2.76GB ± 4%
-52.25%
p=0.000, n=10+10
net/http
284MB ±10%
226MB ±14%
-20.38%
p=0.000, n=10+10
std
1.74GB ±10%
1.15GB ±14%
-34.11%
p=0.000, n=10+10
strconv
148MB ±18%
144MB ±16%
~
p=0.579, n=10+10
Cached, GOMAXPROCS=32
Package
2020.1.6
2020.2
Delta
Stats
image/color
96.0ms ± 6%
80.0ms ± 0%
-16.67%
p=0.000, n=10+9
k8s.io/kubernetes/pkg/...
4.64s ± 1%
3.88s ± 0%
-16.22%
p=0.000, n=9+8
net/http
216ms ± 3%
167ms ± 4%
-22.69%
p=0.000, n=10+10
std
1.09s ± 2%
0.96s ± 2%
-12.20%
p=0.000, n=10+10
strconv
100ms ± 0%
87ms ± 8%
-13.00%
p=0.000, n=9+10
image/color
46.4MB ± 3%
24.1MB ± 5%
-48.08%
p=0.000, n=8+10
k8s.io/kubernetes/pkg/...
1.38GB ± 9%
0.27GB ± 1%
-80.29%
p=0.000, n=10+10
net/http
80.7MB ±12%
31.4MB ± 2%
-61.16%
p=0.000, n=10+8
std
363MB ±12%
75MB ± 7%
-79.30%
p=0.000, n=10+10
strconv
48.5MB ± 6%
24.4MB ± 3%
-49.72%
p=0.000, n=10+10
See commit [5cfc85b70e7b778eb76fd7338e538d7c9af21e4e](https://github.com/dominikh/go-tools/commit/5cfc85b70e7b778eb76fd7338e538d7c9af21e4e)
for details on how these improvements have been achieved.
Furthermore, Staticcheck 2020.2 will skip very large packages (currently packages that are 50 MiB or larger),
under the assumption that these packages contain bundled assets and aren't worth analyzing.
This might further reduce Staticcheck's memory usage in your projects.
## Changes to the detection of unused code {#unused}
### Removal of whole-program mode and changes to the handling of exported identifiers {#unused-whole-program}
The [aforementioned performance improvements](#performance-improvements) necessitate some changes to the _U1000_ check (also known as _unused_).
The most visible change is the removal of the _whole program_ mode.
This mode, which analyzed an entire program and reported unused code even if it is exported,
did not work well with the kind of caching that we use in Staticcheck.
Even in previous versions, it didn't always work correctly and may have caused flaky results,
depending on the state of the cache and the order of `staticcheck` invocations.
The whole-program mode may be revived in the future as a standalone tool,
with the understanding that this mode of operation is inherently more expensive than `staticcheck`.
In the meantime, if you depend on this functionality and can tolerate its bugs, you should continue using Staticcheck 2020.1.
As part of improving the correctness of U1000, changes were made to the normal mode as well.
In particular, **all** exported package-level identifiers will be considered used from now on,
even if these identifiers are declared in `package main` or tests, even if they are otherwise unused.
Exported identifiers in `package main` can be used in ways invisible to us, for example via the `plugin` build mode.
For tests, we would run into the same kind of issues as we did with the whole program mode.
### Improvements {#unused-improvements}
The `//lint:ignore` directive now works more intelligently with the U1000 check.
In previous versions, the directive would only suppress the output of a diagnostic. For example, for the following example
```go
package pkg
//lint:ignore U1000 This is fine.
func fn1() { fn2() }
func fn2() {}
```
Staticcheck would emit the following output:
```text
foo.go:6:6: func fn2 is unused (U1000)
```
as it would only suppress the diagnostic for `fn1`.
Beginning with this release, the directive instead actively marks the identifier as used,
which means that any transitively used code will also be considered used, and no diagnostic will be reported for `fn2`.
Similarly, the `//lint:file-ignore` directive will consider everything in a file used, which may transitively mark code in other files used, too.
## UI improvements {#ui-improvements}
We've made some minor improvements to the output and behavior of the `staticcheck` command:
- the command now prints instructions on how to look up documentation for checks
- output of the `-explain` flag includes a link to the online documentation
- a warning is emitted when a package pattern matches no packages
- unmatched ignore directives cause `staticcheck` to exit with a non-zero status code
## Changes to versioning scheme {#versioning-improvements}
Staticcheck releases have two version numbers: one meant for human consumption and one meant for consumption by machines, via Go modules.
For example, the previous release was both `2020.1.6` (for humans) and `v0.0.1-2020.1.6` (for machines).
In previous releases, we've tried to include the human version in the machine version, by using the `v0.0.1-` scheme.
However, this scheme had various drawbacks.
For this and future releases we've switched to a more standard scheme for machine versions: `v0..`.
Minor will increase by one for every feature release of Staticcheck,
and patch will increase by one for every bugfix release of Staticcheck,
resetting to zero on feature releases.
For example, this release is both `2020.2` and `v0.1.0`.
A hypothetical `2020.2.1` would be `v0.1.1`, and `2021.1` will be `v0.2.0`.
This new versioning scheme fixes various issues when trying to use Staticcheck as a Go module.
It will also allow us to make true pre-releases in the future.
Documentation on the website, as well as the output of `staticcheck -version`, will include both version numbers, to make it easier to associate the two.
For detailed information on how we arrived at this decision, see the discussion on [issue 777]({{< issueref 777 >}}).
## Checks {#checks}
### New checks {#checks-new}
The following new checks have been added:
- {{< check "SA4023" >}} flags impossible comparisons of interface values with untyped nils
- {{< check "SA5012" >}} flags function calls with slice arguments that aren't the right length
- {{< check "SA9006" >}} flags dubious bit shifts of fixed size integers
### Changed checks {#checks-changed}
Several checks have been improved:
- {{< check "S1030" >}} no longer recommends replacing `m[string(buf.Bytes())]` with `m[buf.String()]`, as the former gets optimized by the compiler
- {{< check "S1008" >}} no longer incorrectly suggests that the negation of `>=` is `<=`
- {{< check "S1029" >}} and {{ check "SA6003" }} now also check custom types with underlying type `string`
- {{< check "SA1019" >}} now recognizes deprecation notices that aren't in the last paragraph of a comment
- {{< check "SA1019" >}} now emits more precise diagnostics for deprecated code in the standard library
- {{< check "SA4006" >}} no longer flags assignments where the value is a typed nil
- {{< check "SA5011" >}} is now able to detect more functions that never return, thus reducing the number of false positives
- {{< check "SA9004" >}} no longer assumes that constants belong to the same group when they have different types
- Automatic fixes for {{< check "SA9004" >}} inside gopls no longer incorrectly duplicate comments
- {{< check "ST1003" >}} no longer complains about ALL_CAPS in variable names that don't contain any letters
- Incorrect position information in various checks have been fixed
- Crashes in various checks have been fixed
## Staticcheck 2020.2.1 release notes {#2020.2.1}
This release eliminates some false negatives as well as false positives, makes the `staticcheck` command less noisy and fixes a potential security issue.
- {{< check "SA4020" >}} no longer claims that `case nil` is an unreachable case in a type switch.
- {{< check "S1025" >}} no longer marks uses of `Printf` as unnecessary when the printed types implement the `fmt.Formatter` interface.
- Various checks may now detect bugs in conditional code that were previously missed. This was a regression introduced in Staticcheck 2020.1.
- The `staticcheck` command no longer reminds the user of the `-explain` flag every time problems are found. This was deemed too noisy.
- We've updated our dependency on `golang.org/x/tools` to guard against arbitrary code execution on Windows.
Note that to be fully safe, you will also have to update your installation of Go.
See the [Command PATH security in Go](https://blog.golang.org/path-security) article by the Go authors for more information on this potential vulnerability.
## Staticcheck 2020.2.2 release notes {#2020.2.2}
This release fixes a rare crash in Staticcheck, reduces the number of false positives, and adds support for Go 1.16's `io/fs.FileMode` type.
- {{< check "SA9002" >}} now supports the new `io/fs.FileMode` type in addition to `os.FileMode`.
- Various checks no longer crash when analyzing nested function calls involving multiple return values.
- {{< check "SA1006" >}} no longer flags `Printf` calls of the form `Printf(fn())` when `fn` has multiple return values.
- Staticcheck now understands the effects of `github.com/golang/glog` on a function's control flow.
## Staticcheck 2020.2.3 release notes {#2020.2.3}
This release fixes a false positive in U1000. See [issue 942]({{< issueref 942 >}}) for an example.
## Staticcheck 2020.2.4 release notes {#2020.2.4}
This release fixes the following issues:
- A false positive in {{< check "S1017" >}} when the `len` function has been shadowed
- A bug in Staticcheck's intermediate representation
that would lead to nonsensical reports claiming that a value isn't being used
when it is definitely being used
(see issues [949]({{< issueref 949 >}}) and [981]({{< issueref 981 >}}) for more information)
- A rare crash (see issue [972]({{< issueref 972 >}}) for more information)
================================================
FILE: website/content/changes/2021.1.md
================================================
---
title: Staticcheck 2021.1 release notes
linkTitle: "2021.1 (v0.2.0)"
weight: -6
---
## UI improvements {#ui-improvements}
The new `-list-checks` flag lists all available checks, showing each check's identifier and one-line description.
You can use the existing `-explain` flag to find out more about each individual check.
### Targeted Go version {#targeted-go-version}
Some checks in Staticcheck adjust their behavior based on the targeted Go version. For example, the suggestion to use `for range` instead of `for _ = range` does not apply to Go 1.3 and earlier.
In the past, the default Go version that was targeted was the version that Staticcheck had been compiled with. For most users, this meant that it targeted the latest Go release.
Going forward, we will default to the Go version declared in `go.mod` via the `go` directive.
Even though this value does not exactly correspond to the module's minimum supported Go version, it is still a better apprximation than "whatever Go version Staticcheck has been compiled with",
and should work fine for most users.
As before, the targeted Go version can be explicitly set by using the `-go` flag.
## Checks {#checks}
### New checks {#checks-new}
The following new checks have been added:
- {{< check "S1040" >}} flags type assertions from an interface type to itself
- {{< check "SA1030" >}} flags invalid arguments to various functions in the `strconv` package
- {{< check "SA4005" >}} flags assignments to fields on value receivers that intended the receiver to be a pointer instead
- {{< check "SA4024" >}} flags pointless comparisons of the values of `len` and `cap` with zero
- {{< check "SA4025" >}} flags suspicious integer division that results in zero, such as `2 / 3`
- {{< check "SA4026" >}} flags constant expressions that try to express [negative zero](https://en.wikipedia.org/wiki/Signed_zero)
- {{< check "SA4027" >}} flags no-op attempts at modifying a `(*net/url.URL)`'s query string
- {{< check "ST1023" >}} flags variable declarations of the form `var x T = v` where the type `T` is redundant; this check is disabled by default
### Changed checks {#checks-changed}
The following checks have been improved:
- {{< check "S1025" >}} now recommends converting byte slices to strings instead of using `fmt.Sprintf`
- {{< check "S1008" >}} includes fewer unnecessary parentheses and double negations in its suggested fixes
- {{< check "S1017" >}} is now able to flag calls that use string literals and integer literals
- {{< check "SA9005" >}} now includes the value's type in its output
- {{< check "ST1000" >}}, {{< check "ST1020" >}}, {{< check "ST1021" >}}, and {{< check "ST1022" >}} no longer flag effectively empty comments, including those that consist entirely of directives
## Restructured documentation {#documentation}
The documentation on [the website](https://staticcheck.dev) has been restructured and hopefully made more approachable.
Instead of being one long document, it is now split into multiple smaller articles.
In the future, more articles that look at specific aspects of Staticcheck will be added.
## Better integration with gopls {#gopls}
Several behind the scenes changes prepare this release for better integration with [gopls](https://github.com/golang/tools/blob/master/gopls/README.md).
This will include more accurate severities for diagnostics as well as numerous new refactorings.
These improvements will be part of a future gopls release.
## Deletion of rdeps {#rdeps}
The rdeps tool has been deleted.
This was a GOPATH-centric tool that allowed finding all reverse dependencies of a Go package.
Both the move to Go modules as well as the emergence of much better tooling for inspecting dependencies (such as [goda](https://github.com/loov/goda)) has made rdeps redundant.
## Staticcheck 2021.1.1 release notes {#2021.1.1}
This release adds support for new language features in Go 1.17,
namely conversions from slices to array pointers,
the unsafe.Add function,
and the unsafe.Slice function.
Additionally, it fixes the following false positives:
- {{< check "ST1000" >}} no longer flags package docs that start with whitespace if they're otherwise well-formed.
- {{< check "SA5002" >}} no longer prints one too many percent signs in its message.
- {{< check "SA4000" >}} no longer flags comparisons between floats.
- {{< check "SA4010" >}} no longer flags appends to slices that might share their backing array with other slices.
- {{< check "SA5011" >}} no longer infers possible nil pointer dereferences from comparisons done outside of control flow constructs.
This avoids false positives when using `assert`-style functions.
See [issue 1022]({{< issueref 1022 >}}) for a concrete example.
- {{< check "S1020" >}} no longer flags nested if statements when the inner statement has an else branch.
- {{< check "SA5011" >}} no longer claims that indexing a nil slice will cause a nil pointer dereference.
## Staticcheck 2021.1.2 release notes {#2021.1.2}
This release fixes the following false positives:
- {{< check "SA4000" >}} no longer flags operations involving the `math/rand` package. This is to allow patterns such as `rand.Intn(2) - rand.Intn(2)`.
- {{< check "S1019" >}} no longer recommends replacing `make(map[X]Y, 0)` with `make(map[X]Y)` – the latter may allocate a small starting size.
- {{< check "SA1026" >}} now more accurately implements the behavior of `encoding/json` and `encoding/xml`, which will lead to fewer false positives involving method sets or embedding.
It might also find bugs that it previously missed.
- Checks that are sensitive to control flow, such as {{< check "SA5011" >}}, now recognize functions from `k8s.io/klog` and `go.uber.org/zap` that affect control flow (`Panic` and `Fatal` related functions.)
Furthermore, it fixes the following crashes:
- Using `//lint:ignore U1000` on a type alias would crash.
- Converting a slice to an array pointer, where the array type is a named type, would crash.
================================================
FILE: website/content/changes/2022.1.md
================================================
---
title: Staticcheck 2022.1 release notes
linkTitle: "2022.1 (v0.3.0)"
weight: -7
---
## Improvements {#improvements}
This release adds support for Go 1.18 and type parameters (generics).
Furthermore, it adds two new flags for handling build tags, `-matrix` and `-merge`. Their use is extensively documented
on the new [Build tags]({{< relref "/docs/running-staticcheck/cli/build-tags" >}}) page. Their intended use is for
avoiding false positives when dealing with different build tags.
Not tied directly to this release, but worth mentioning regardless: Staticcheck has an [official GitHub
Action](https://github.com/dominikh/staticcheck-action) now, which may simplify your CI pipeline.
Minor changes include slightly nicer output from `staticcheck -explain`, better error messages, and allowing whitespace in flags like `-checks`.
## Checks {#checks}
### New checks {#checks-new}
The following new checks have been added:
- {{< check "SA4028" >}} flags `x % 1`, which always yields zero, and is sometimes accidentally used instead of `x % 2`
- {{< check "SA4029" >}} flags misuses of `sort.IntSlice` and related types
- {{< check "SA4030" >}} flags misuses of `math/rand` that always generate zeros
- {{< check "SA4031" >}} flags comparisons of never-nil values against nil
- {{< check "SA9007" >}} flags attempts at deleting system directories
- {{< check "SA9008" >}} flags accidental shadowing in the else branches of type assertions
### Changed checks {#checks-changed}
The following checks have been improved:
- {{< check "S1001" >}} now simplifies more loops
- {{< check "S1038" >}} now simplifies formatted printing in `log` and `testing`, in addition to `fmt`
- {{< check "SA1019" >}} no longer flags deprecated API in the Go standard library if it doesn't know when the API was
deprecated. This is to avoid false positives when using older versions of Staticcheck on newer versions of Go, in
particular Go's `master` branch.
- {{< check "SA1020" >}} no longer flags `net/http.ListenAndServe` with a completely empty address
- {{< check "ST1001" >}} various packages of `github.com/mmcloughlin/avo` have been whitelisted by default
- {{< check "ST1008" >}} no longer flags functions that return `(..., error, bool)`
- {{< check "ST1018" >}} no longer flags emoji sequences
- {{< check "ST1023" >}} no longer makes erroneous suggestions
- Numerous checks have a better understanding of integer literals and can detect mistakes involving unconventional
literals such as `---1` instead of `-1`
- Some runtime crashes have been fixed
## Staticcheck 2022.1.1 release notes {#2022.1.1}
This release addresses the following false positives, crashes, and infinite loops:
- {{< check "SA1026" >}} and {{< check "SA5008" >}} no longer get stuck in infinite loops when code attempts to marshal cyclic pointer types ({{< issue "1202" >}})
- U1000 no longer crashes when code contains mutually recursive type instantiations ({{< issue "1247" >}})
- U1000 no longer crashes when generic functions use composite literals of type parameter types ({{< commit "0ccdb5c9dad7e96a8e3a3136738192491b37dbdb" >}})
- {{< check "ST1021" >}} now understands type names that are also English articles ({{< issue "1187" >}})
- {{< check "SA4023" >}} no longer gets confused by the nilness of type parameters ({{< issue "1242" >}})
- Some checks no longer crash when trying to generate automated code fixes that involve function literals ({{< issue "1134" >}})
- {{< check "SA1026" >}} no longer claims that `encoding/json` cannot marshal generic maps ([golang/go#52467](https://golang.org/issue/52467))
- The `binary` format has been improved to handle OS-specific file paths correctly, in turn making the `-merge` flag work more reliably ({{< commit "1846305a946b13d350894512c7ac1e5ed71dc331" >}})
- When using the `-merge` or `-matrix` flags, diagnostics reported by {{< check "SA4008" >}} now have to occur in all runs to be reported, reducing the number of false positives ({{< commit "0e678cbe1c8b3f09ac481673453886b1afc9906a" >}})
- U1000 now understands struct type conversions involving type parameters, reducing the number of false positives ({{< commit "90804df0287d9265e565bcabbe19568efbe374fa" >}})
## Staticcheck 2022.1.2 release notes {#2022.1.2}
This release addresses the following false positives, crashes, infinite loops, and performance issues:
- For certain packages that contain tens of thousands of types and methods, such as those generated by
[ygot](https://github.com/openconfig/ygot), Staticcheck now finishes [much
faster](https://github.com/openconfig/featureprofiles/pull/181#issuecomment-1119250596).
- Several infinite loops when handling recursive type parameters have been fixed
- {{< check "S1009" >}} no longer mistakes user-defined functions named `len` for the builtin ({{< issue "1181" >}})
- {{< check "ST1015" >}} no longer reorders `switch` statements if their order is significant due to the use of `fallthrough` ({{< issue "1188" >}})
- {{< check "SA1013" >}} now detects constants more robustly, avoiding both false negatives and false positives.
Furthermore, it makes sure that offending methods implement io.Seeker and doesn't just rely on the name `Seek` ({{< issue "1213" >}}).
- {{< check "SA5008" >}} now understands more third-party extensions to `json` struct tags
- A crash involving functions named `_` has been fixed ({{< issue "1268" >}})
- A crash involving slicing type parameters of type `string | []byte` has been fixed ({{< issue "1270" >}})
- {{< check "SA1019" >}} now handles imports of deprecated standard library packages in the same way it handles other
deprecated API, taking the targeted Go version into consideration ({{< issue "1117" >}})
Additionally it is strongly recommended to use Go 1.18.2 for building Staticcheck, as it fixes further generics-related
bugs in the type checker.
## Staticcheck 2022.1.3 release notes {#2022.1.3}
This release addresses the following issues:
- Staticcheck maintains a cache to speed up repeated runs. This cache needs to be pruned regularly to keep its size in
check. This is meant to happen automatically, but it hasn't since Staticcheck 2020.2. This release corrects that ({{<
issue "1283" >}}.)
- Some type sets containing both bidirectional and unidirectional channels would lead to panics ({{< issue "1304" >}})
================================================
FILE: website/content/changes/2023.1.md
================================================
---
title: Staticcheck 2023.1 release notes
linkTitle: "2023.1 (v0.4.0)"
weight: -8
---
Staticcheck 2023.1 adds support for Go 1.20, brings minor improvements to various checks, and replaces U1000
with a new implementation.
## Checks {#checks}
### Changed checks {#checks-changed}
The following checks have been improved:
- The wording of {{< check "S1001" >}} has been made clearer for cases involving arrays. Furthermore, it no longer
suggests using `copy` when the function has been shadowed.
- {{< check "S1011" >}} now recognizes index-based loops ({{< issue "881" >}}).
- {{< check "SA1019" >}} no longer flags tests (internal or external) that use deprecated API from the package under
test ({{< issue "1285" >}}). Furthermore, entire declaration groups (such as groups of constants) can now be marked as
deprecated ({{< issue "1313" >}}).
- {{< check "SA4017" >}} now detects more functions, including those in the `time` package ({{< issue "1353" >}}).
Additionally, its wording has been made clearer.
- {{< check "SA5010" >}} no longer gets confused by type assertions involving generic types ({{< issue "1354" >}}).
- {{< check "ST1005" >}} no longer flags errors that start with alpha-numeric acronyms such as `P384`.
- Improvements to our intermediate representation may allow various checks to find more problems.
Staticcheck now knows about version 2 of the `k8s.io/klog` package, in particular which functions abort control flow
({{< issue "1307" >}}).
In addition to these minor improvements, U1000 has been rewritten from the ground up, operating on a
program representation more suited to the task. In practice this means that there will be fewer false positives and more
true positives.
Overall, the rewrite fixes at least eight known bugs, both ones that have been a nuisance for a while,
as well as ones newly introduced by generics
({{< issue "507" >}}, {{< issue "633" >}}, {{< issue "810" >}}, {{< issue "812" >}}, {{< issue "1199" >}}, {{< issue
"1249" >}}, {{< issue "1282" >}}, {{< issue "1333" >}}).
## Staticcheck 2023.1.1 release notes {#2023.1.1}
This release fixes a crash, a false positive in U1000 ({{< issue "1360" >}}) and improves the way deprecated API is
flagged ({{< issue "1318" >}}).
When targeting a Go version that is older than the version that deprecated an API, {{< check "SA1019" >}} will no longer
flag the use even if there is already an alternative available in the targeted Go version.
For example, `math/rand.Seed` has been deprecated in Go 1.20, but an alternative has existed since Go 1.0. In the past,
we would flag uses of `Seed` even if targeting e.g. Go 1.19, to encourage better forwards compatibility. This can lead
to unnecessary churn, however, because the correct change may depend on the Go version in use. For example, for `Seed`
before Go 1.20, the alternative is to use a separate instance of `math/rand.Rand`, whereas in Go 1.20, a possible
alternative is to simply drop the call to `Seed`.
## Staticcheck 2023.1.2 release notes {#2023.1.2}
This release fixes a bug that prevented the `binary` formatter from working ({{< issue "1370" >}}).
## Staticcheck 2023.1.3 release notes {#2023.1.3}
This release fixes the following bugs:
- A crash when embedding type aliases of unnamed types ({{< issue "1361" >}})
- A false positive in U1000, claiming that type aliases are unused ({{< issue "1365" >}})
- A bug in the `binary` formatter that prevented correct merging behavior for some checks ({{< issue "1372" >}})
## Staticcheck 2023.1.4 release notes {#2023.1.4}
This release **adds support for Go 1.21** and fixes the following bugs:
- Three crashes when encountering unnecessarily parenthesized statements ({{< issue "1393" >}}, {{< issue "1400" >}})
- Unnecessarily high memory usage when analyzing composite literals such as `[]int{1<<31: 1}` ({{< issue "1393" >}})
- A false positive in {{< check "S1011" >}} when appending to a dynamic left-hand side ({{< issue "1399" >}})
- A crash involving generics ({{< issue "1410" >}})
- A false positive in {{< check "SA9001" >}} involving control flow statements ({{< issue "488" >}})
- A false positive in {{< check "ST1003" >}}, complaining about the names of fuzz functions ({{< issue "1420" >}}))
## Staticcheck 2023.1.5 release notes {#2023.1.5}
This release fixes the following bug:
- A crash involving methods named `_`
## Staticcheck 2023.1.6 release notes {#2023.1.6}
This release fixes the following bugs:
- A crash when using the upcoming Go 1.22 ({{< issue "1442" >}})
- A false positive in {{< check "SA9005" >}} when embedding basic types ({{< issue "1443" >}})
## Staticcheck 2023.1.7 release notes {#2023.1.7}
This release fixes some minor issues in Staticcheck's intermediate representation. Furthermore, it improves the way {{<
check "QF1003" >}} generates suggested fixes, working around constraints in the language server protocol.
The released binaries for this version have been built with Go 1.22 and should no longer panic when checking code
targeting Go 1.22.
================================================
FILE: website/content/changes/2024.1.md
================================================
---
title: Staticcheck 2024.1 release notes
linkTitle: "2024.1 (v0.5.0)"
weight: -9
---
## Backwards incompatible changes
Staticcheck 2024.1 contains the following backwards incompatible changes:
- The `keyify` utility has been removed. The recommended alternative is gopls.
- `staticcheck -merge` now exits with a non-zero status if any problems have
been found.
## Improved Go 1.22 support
This release updates Staticcheck's database of deprecated standard library APIs
to cover the Go 1.22 release. Furthermore, checks have been updated to correctly
handle the new "for" loop variable scoping behavior as well as ranging over
integers.
## Added Go 1.23 support
Staticcheck 2024.1 has full support for iterators / range-over-func.
Furthermore, {{< check "SA1015" >}} will skip any code targeting Go 1.23 or
newer, as it is now possible to use `time.Tick` without leaking memory.
## Improved handling of Go versions
Go 1.21 more rigorously defined the meaning of the `go` directive in `go.mod`
files, as well as its interactions with `//go:build go1.N` build constraints.
The `go` directive now specifies a _minimum_ Go version for the module.
Furthermore, it sets the language version that is in effect, which may change
the semantics of Go code. For example, before Go 1.22, loop variables were
reused across iterations, but since Go 1.22, loop variables only exist for the
duration of an iteration. Modules that specify `go 1.22` will use the new
semantics, while modules that specify an older version will not.
Individual files can both upgrade and downgrade their language version by using
`//go:build go1.N` directives. In a module that requires Go 1.22, a file
specifying Go 1.21 will experience the old loop variable semantics, and vice
versa. Because the Go module as a whole still specifies a minimum version, even
files specifying an older version will have access to the standard library of
the minimum version.
Staticcheck 2024.1 takes all of this into consideration when analyzing the
behavior of Go code, when determining which checks are applicable, and when
making suggestions. Older versions of Staticcheck were already aware of Go
versions, but 2024.1 works on a more fine-grained, per-file basis, and
differentiates between the pre- and post-1.21 semantics of the `go` directive.
The `-go` command line flag continues to exist. It will override any
module-based version selection. This is primarily useful for Go modules that
target older Go versions (because here, the `go` directive didn't specify a
_minimum_ version), or when working outside of Go modules.
To prevent misinterpreting code, Staticcheck now refuses to analyze modules that
require a version of Go that is newer than that with which Staticcheck was
built.
## Checks
### New checks
The following checks have been added:
- {{< check "SA1031" >}} flags overlapping destination and source slices passed
to certain encoding functions.
- {{< check "SA1032" >}} flags calls to
[`errors.Is`](https://pkg.go.dev/errors#Is) where the two arguments have been
swapped.
- {{< check "SA4032" >}} flags impossible comparisons of
[runtime.GOOS](https://pkg.go.dev/runtime#GOOS) and
[runtime.GOARCH](https://pkg.go.dev/runtime#GOARCH) based on the file's build
tags.
- {{< check "SA6006" >}} flags `io.WriteString(w, string(b))` as it would be
both simpler and more efficient to use `w.Write(b)`.
- {{< check "SA9009" >}} flags comments that look like they intend to be
compiler directives but which aren't due to extraneous whitespace.
### Changed checks
The following checks have been improved:
- {{< check "QF1001" >}} no longer panics on expressions involving "key: value"
pairs ({{< issue "1484" >}}).
- {{< check "S1008" >}} now understands that some built-in functions never
return negative values. For example, it now negates `len(x) > 0` as `len(x) ==
0` ({{< issue "1422" >}}).
- {{< check "S1009" >}} now flags unnecessary nil checks that involve selector
expressions ({{< issue "1527" >}}).
- {{< check "S1017" >}} no longer flags `if else` branches ({{< issue "1447"
>}}).
- {{< check "SA1006" >}} now detects more Printf-like functions from the
standard library ({{< issue "1528" >}}).
- {{< check "SA1015" >}} now skips any code targeting Go 1.23 or newer ({{<
issue "1558" >}}).
- {{< check "SA1029" >}} now flags uses of the empty struct (`struct{}`) as
context keys ({{< issue "1504" >}}).
- {{< check "SA4003" >}} now flags pointless integer comparisons that involve
literals, not just constants from the `math` package ({{< issue "1470" >}}).
- {{< check "SA4015" >}} now supports conversions that involve generics.
- {{< check "SA4023" >}} no longer panics on type sets that contain arrays ({{<
issue "1397" >}}).
- {{< check "SA5001" >}} now emits a clearer message ({{< issue "1489" >}}).
- {{< check "SA9003" >}} has been disabled by default because of too many noisy
positives ({{< issue "321" >}}).
- {{< check "ST1000" >}} now permits punctuation following the package name, as
in "Package pkg, which ..." ({{< issue "1452" >}}).
- {{< check "ST1018" >}} now accepts variation selectors in emoji and certain
Arabic formatting characters in string literals ({{< issue "1456" >}}).
- {{< check "ST1020" >}} no longer flags comments that start with a deprecation
notice ({{< issue "1378" >}}).
- {{< check "U1000" >}} handles generic interfaces slightly better, reducing the
number of false positives.
- Due to improvements in the intermediate representation, various checks may now
detect more problems.
## Miscellaneous changes and fixes
- The `keyify` utility has been deleted. This functionality is provided by gopls
nowadays.
- `staticcheck -merge` now exits with a non-zero exit status if any problems
were found. This matches the behavior of non-merge uses.
- Malformed `staticcheck.conf` files now cause more useful errors to be emitted.
- Labeled statements with blank labels no longer cause panics.
- Functions with named return parameters that never actually return no longer
cause panics ({{< issue "1533" >}}).
## Staticcheck 2024.1.1 release notes {#2024.1.1}
This release fixes the detection of the used Go version when Go was compiled
with experimental features such as `rangefunc` or `boringcrypto` ({{< issue
"1586" >}}).
================================================
FILE: website/content/changes/2025.1.md
================================================
---
title: Staticcheck 2025.1 release notes
linkTitle: "2025.1 (v0.6.0)"
weight: -10
---
## Added Go 1.24 support
This release adds support for Go 1.24.
## Checks
### Changed checks
The following checks have been improved:
- {{< check "U1000" >}} treats all fields in a struct as used if the struct has
a field of type `structs.HostLayout`.
- {{< check "S1009" >}} now emits a clearer message.
- {{< check "S1008" >}} no longer recommends simplifying branches that contain
comments ({{< issue "704" >}}, {{< issue "1488" >}}).
- {{< check "S1009" >}} now flags another redundant nil check ({{< issue "1605" >}}).
- {{< check "QF1002" >}} now emits a valid automatic fix for switches that use
initialization statements ({{< issue "1613" >}}).
## Staticcheck 2025.1.1 release notes {#2025.1.1}
This is a re-release of 2025.1 but with prebuilt binaries that have been built
with Go 1.24.1.
================================================
FILE: website/content/changes/2026.1.md
================================================
---
title: Staticcheck 2026.1 release notes
linkTitle: "2026.1 (v0.7.0)"
weight: -11
---
## Improved Go 1.25 and Go 1.26 support
This release updates Staticcheck’s database of deprecated standard library APIs
to cover the Go 1.25 and Go 1.26 releases, as well as to add some
`crypto/elliptic` deprecations from Go 1.21 that were missing. Furthermore, it
adds support for `new(expr)`, which was added in Go 1.26.
## Other changes
- Version mismatch checks have been relaxed and no longer care about mismatches
in the patch level. For example, Staticcheck built with Go 1.26.0 will be able
to check code using Go 1.26.1.
- Staticcheck no longer opens `staticcheck.conf` files that aren't regular files
(or symlinks to regular files). See [this gomodfs
issue](https://github.com/tailscale/gomodfs/issues/17) for the motivation
behind this change.
- Staticcheck now exits with a non-zero status code if it encountered an
invalid configuration file.
## Checks
### Changed checks
The following checks have been improved:
- {{< check "SA1026" >}} no longer panics when checking code that tries to
marshal named functions ({{< issue "1660" >}}).
- {{< check "SA4000" >}} no longer flags `var _ = T{} == T{}`, a pattern used to
ensure that type `T` is comparable ({{< issue "1670" >}}).
- {{< check "SA4000" >}} now correctly skips structs containing floats.
- {{< check "SA4000" >}} now skips functions from the `math/rand/v2` package.
- {{< check "SA4003" >}} now skips over generated files.
- {{< check "SA4030" >}} now also checks uses of `math/rand/v2`.
- {{< check "SA5008" >}} has been updated with better support for `encoding/json/v2`.
- {{< check "SA5010" >}} no longer tries to reason about generics, to avoid
false positives.
- {{< check "ST1019" >}} no longer flags duplicate imports of unsafe, mainly to
play nice with cgo.
- {{< check "ST1003" >}} and {{< check "QF1002" >}} now emit more concise
positions, benefitting users of gopls ({{< issue 1647 >}}).
- {{< check "ST1019" >}} now allows importing the same package twice, once using
a blank import ({{< issue "1688" >}}).
- {{< check "QF1008" >}} no longer offers to delete all embedded fields from a
selector expression. Even when two fields are individually superfluous,
removing both might change the semantics of the code ({{< issue "1682" >}}).
- {{< check "QF1012" >}} now detects more uses of `bytes.Buffer` ({{< issue "1097" >}}).
- A bug in the intermediate representation was fixed, affecting the behavior of
various checks ({{< issue "1654" >}}).
================================================
FILE: website/content/changes/_index.md
================================================
---
title: Release notes
type: docs
outputs:
- html
- RSS
cascade:
type: docs
---
================================================
FILE: website/content/contact.md
================================================
---
title: "Contact"
menu:
main:
weight: 3
pre:
---
{{< blocks/section type="section" color="white" >}}
Please contact me at dominik@honnef.co should you have any questions regarding this website or the services offered.
The following information are provided to comply with § 5 Telemediengesetz.
Dominik Honnef
Bahnhofstraße 65
45770 Marl
Germany
Email: dominik@honnef.co
Phone: +49 151 178 067 90
USt-IdNr: DE298968211
{{< /blocks/section >}}
================================================
FILE: website/content/docs/_index.md
================================================
---
title: "Welcome to Staticcheck"
linkTitle: "Documentation"
menu:
main:
weight: 1
pre:
---
Staticcheck is a state of the art linter for the [Go programming language](https://go.dev/).
Using static analysis, it finds bugs and performance issues, offers simplifications, and enforces style rules.
Each of the
[
{{< numchecks.inline >}}
{{- $c := len $.Site.Data.checks.Checks -}}
{{- sub $c (mod $c 25) -}}
{{< /numchecks.inline >}}+
]({{< relref "/docs/checks" >}}) checks has been designed to be fast, precise and useful.
When Staticcheck flags code, you can be sure that it isn't wasting your time with unactionable warnings.
Unlike many other linters, Staticcheck focuses on checks that produce few to no false positives.
It's the ideal candidate for running in CI without risking spurious failures.
Staticcheck aims to be trivial to adopt.
It behaves just like the official `go` tool and requires no learning to get started with.
Just run `staticcheck ./...` on your code in addition to `go vet ./...`.
While checks have been designed to be useful out of the box,
they still provide [configuration]({{< relref "/docs/configuration" >}}) where necessary, to fine-tune to your needs, without overwhelming you with hundreds of options.
Staticcheck can be used from the command line, in CI,
and even [directly from your editor](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck-bool).
Staticcheck is open source and offered completely free of charge. [Sponsors]({{< relref "/sponsors" >}}) guarantee its continued development.
}}">
================================================
FILE: website/content/docs/changes.md
================================================
---
title: Release notes
manualLinkRelref: "/changes"
---
================================================
FILE: website/content/docs/checks.html
================================================
---
title: "Checks"
description: "Explanations for all checks in Staticcheck"
menu:
main:
weight: 2
pre:
---
{{< all-checks.inline >}}
{{ define "category-list" }}
{{ range $name := index $.p.Site.Data.checks.ByCategory $.cat }}
{{ $check := index $.p.Site.Data.checks.Checks $name }}
{{ $name }}
{{ $check.TitleMarkdown | markdownify }}
{{ end }}
{{ end }}
{{ define "category" }}
{{ range $name := index $.p.Site.Data.checks.ByCategory .cat }}
{{ $check := index $.p.Site.Data.checks.Checks $name }}
{{ $name }} - {{ $check.TitleMarkdown | markdownify }}{{ if $check.NonDefault }} non-default {{ end }}
{{ $check.TextMarkdown | $.p.Page.RenderString (dict "display" "block") }}
{{ if $check.Before }}
Before:
{{ highlight $check.Before "go" "" }}
{{ end }}
{{ if $check.After }}
After:
{{ highlight $check.After "go" "" }}
{{ end }}
Available since
{{ $check.Since }}
{{ if $check.Options }}
Options
{{ range $opt := $check.Options -}}
{{ $opt }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ $categoryNames := slice "SA" "SA1" "SA2" "SA3" "SA4" "SA5" "SA6" "SA9" "S" "S1" "ST" "ST1" "QF" "QF1" }}
{{ $categories := dict
"SA" (dict "name" "SA" "title" "staticcheck")
"SA1" (dict "name" "SA1" "title" "Various misuses of the standard library")
"SA2" (dict "name" "SA2" "title" "Concurrency issues")
"SA3" (dict "name" "SA3" "title" "Testing issues")
"SA4" (dict "name" "SA4" "title" "Code that isn't really doing anything")
"SA5" (dict "name" "SA5" "title" "Correctness issues")
"SA6" (dict "name" "SA6" "title" "Performance issues")
"SA9" (dict "name" "SA9" "title" "Dubious code constructs that have a high probability of being wrong")
"S" (dict "name" "S" "title" "simple")
"S1" (dict "name" "S1" "title" "Code simplifications")
"ST" (dict "name" "ST" "title" "stylecheck")
"ST1" (dict "name" "ST1" "title" "Stylistic issues")
"QF" (dict "name" "QF" "title" "quickfix")
"QF1" (dict "name" "QF1" "title" "Quickfixes")
}}
Check
Short description
{{ range $name := $categoryNames }}
{{ $cat := index $categories $name }}
{{ $cat.name }} {{ $cat.title }}
{{ template "category-list" (dict "p" $ "cat" $cat.name) }}
{{ end }}
{{ define "category-header" }}
{{ .name }} – {{ .title }}
{{ end }}
{{ define "subcategory-header" }}
{{ .name }} – {{ .title }}
{{ end }}
{{ template "category-header" (index $categories "SA") }}
The SA category of checks, codenamed staticcheck, contains all checks that are concerned with the correctness of code.
{{ template "subcategory-header" (index $categories "SA1") }}
Checks in this category deal with misuses of the standard library.
This tends to involve incorrect function arguments
or violating other invariants laid out by the standard library's documentation.
{{ template "category" (dict "p" $ "cat" "SA1") }}
{{ template "subcategory-header" (index $categories "SA2") }}
Checks in this category find concurrency bugs.
{{ template "category" (dict "p" $ "cat" "SA2") }}
{{ template "subcategory-header" (index $categories "SA3") }}
Checks in this category find issues in tests and benchmarks.
{{ template "category" (dict "p" $ "cat" "SA3") }}
{{ template "subcategory-header" (index $categories "SA4") }}
Checks in this category point out code that doesn't have any meaningful effect on a program's execution.
Usually this means that the programmer thought the code would do one thing while in reality it does something else.
{{ template "category" (dict "p" $ "cat" "SA4") }}
{{ template "subcategory-header" (index $categories "SA5") }}
Checks in this category find assorted bugs and crashes.
{{ template "category" (dict "p" $ "cat" "SA5") }}
{{ template "subcategory-header" (index $categories "SA6") }}
Checks in this category find code that can be trivially made faster.
{{ template "category" (dict "p" $ "cat" "SA6") }}
{{ template "subcategory-header" (index $categories "SA9") }}
Checks in this category find code that is probably wrong.
Unlike checks in the other SA categories,
checks in SA9 have a slight chance of reporting false positives.
However, even false positives will point at code that is confusing and that should probably be refactored.
{{ template "category" (dict "p" $ "cat" "SA9") }}
{{ template "category-header" (index $categories "S") }}
The S category of checks, codenamed simple, contains all checks that are concerned with simplifying code.
{{ template "subcategory-header" (index $categories "S1") }}
Checks in this category find code that is unnecessarily complex and that can be trivially simplified.
{{ template "category" (dict "p" $ "cat" "S1") }}
{{ template "category-header" (index $categories "ST") }}
The ST category of checks, codenamed stylecheck, contains all checks that are concerned with stylistic issues.
{{ template "subcategory-header" (index $categories "ST1") }}
The rules contained in this category are primarily derived from the Go wiki and represent community consensus.
Some checks are very pedantic and disabled by default.
You may want to tweak which checks from this category run , based on your project's needs.
{{ template "category" (dict "p" $ "cat" "ST1") }}
{{ template "category-header" (index $categories "QF") }}
The QF category of checks, codenamed quickfix, contains checks that are used as part of gopls for automatic refactorings.
In the context of gopls, diagnostics of these checks will usually show up as hints, sometimes as information-level diagnostics.
{{ template "subcategory-header" (index $categories "QF1") }}
{{ template "category" (dict "p" $ "cat" "QF1") }}
{{< /all-checks.inline >}}
================================================
FILE: website/content/docs/configuration/_index.md
================================================
---
title: "Configuration"
description: "Tweak Staticcheck to your requirements"
weight: 3
---
Staticcheck tries to provide a good out-of-the-box experience, but it also offers a number of options to fine-tune it to your specific needs.
## Command-line flags {#cli-flags}
Staticcheck uses command-line flags for settings that are specific to single invocations and that may change depending on the context (local development, continuous integration etc).
This includes which build tags to set and which output format to use.
All of the CLI flags are explained in the [Command-line interface]({{< relref "/docs/running-staticcheck/cli" >}}) article.
## Configuration files {#configuration-files}
Staticcheck uses configuration files for settings that apply to all users of Staticcheck on a given project.
Configuration files can choose which checks to run as well as tweak the behavior of individual checks.
Configuration files are named `staticcheck.conf` and apply to subtrees of packages. Consider the following tree of Go packages and configuration files:
```plain
.
├── net
│ ├── cgi
│ ├── http
│ │ ├── parser
│ │ └── staticcheck.conf // config 3
│ └── staticcheck.conf // config 2
├── staticcheck.conf // config 1
└── strconv
```
Config 1 will apply to all packages, config 2 will apply to `./net/...` and config 3 will apply to `./net/http/...`.
When multiple configuration files apply to a package (for example, all three configs will apply to `./net/http`) they will be merged, with settings in files deeper in the package tree overriding rules higher up the tree.
### Configuration format {#configuration-format}
Staticcheck configuration files are named `staticcheck.conf` and contain [TOML](https://github.com/toml-lang/toml).
Any set option will override the same option from further up the package tree,
whereas unset options will inherit their values.
Additionally, the special value `"inherit"` can be used to inherit values.
This is especially useful for array values, as it allows adding and removing values to the inherited option.
For example, the option `checks = ["inherit", "ST1000"]` will inherit the enabled checks and additionally enable ST1000.
The special value `"all"` matches all possible values.
this is used when enabling or disabling checks.
Values prefixed with a minus sign, such as `"-S1000"` will exclude values from a list.
This can be used in combination with `"all"` to express "all but",
or in combination with `"inherit"` to remove values from the inherited option.
### Configuration options {#configuration-options}
A list of all options and their explanations can be found on the [Options]({{< relref "/docs/configuration/options" >}}) page.
### Example configuration {#example-configuration}
The following example configuration is the textual representation of Staticcheck's default configuration.
{{% content "default_config.md" %}}
## Ignoring problems with linter directives {#ignoring-problems}
In general, you shouldn't have to ignore problems reported by Staticcheck.
Great care is taken to minimize the number of false positives and subjective suggestions.
Dubious code should be rewritten and genuine false positives should be reported so that they can be fixed.
The reality of things, however, is that not all corner cases can be taken into consideration.
Sometimes code just has to look weird enough to confuse tools,
and sometimes suggestions, though well-meant, just aren't applicable.
For those rare cases, there are several ways of ignoring unwanted problems.
### Line-based linter directives {#line-based-linter-directives}
The most fine-grained way of ignoring reported problems is to annotate the offending lines of code with linter directives.
The `//lint:ignore Check1[,Check2,...,CheckN] reason` directive
ignores one or more checks on the following line of code.
The `reason` is a required field
that must describe why the checks should be ignored for that line of code.
This field acts as documentation for other people (including future you) reading the code.
Let's consider the following example,
which intentionally checks that the results of two identical function calls are not equal:
```go
func TestNewEqual(t *testing.T) {
if errors.New("abc") == errors.New("abc") {
t.Errorf(`New("abc") == New("abc")`)
}
}
```
{{< check "SA4000" >}} will flag this code,
pointing out that the left and right side of `==` are identical –
usually indicative of a typo and a bug.
To silence this problem, we can use a linter directive:
```go
func TestNewEqual(t *testing.T) {
//lint:ignore SA4000 we want to make sure that no two results of errors.New are ever the same
if errors.New("abc") == errors.New("abc") {
t.Errorf(`New("abc") == New("abc")`)
}
}
```
### Maintenance of linter directives {#maintenance-of-linter-directives}
It is crucial to update or remove outdated linter directives when code has been changed.
Staticcheck helps you with this by making unnecessary directives a problem of its own.
For example, for this (admittedly contrived) snippet of code
```go
//lint:ignore SA1000 we love invalid regular expressions!
regexp.Compile(".+")
```
Staticcheck will report the following:
```plain
tmp.go:1:2: this linter directive didn't match anything; should it be removed?
```
Checks that have been disabled via configuration files will not cause directives to be considered unnecessary.
### File-based linter directives {#file-based-linter-directives}
In some cases, you may want to disable checks for an entire file.
For example, code generation may leave behind a lot of unused code,
as it simplifies the generation process.
Instead of manually annotating every instance of unused code,
the code generator can inject a single, file-wide ignore directive to ignore the problem.
File-based linter directives look a lot like line-based ones:
```go
//lint:file-ignore U1000 Ignore all unused code, it's generated
```
The only difference is that these comments aren't associated with any specific line of code.
Conventionally, these comments should be placed near the top of the file.
Unlike line-based directives, file-based ones will not be flagged for being unnecessary.
================================================
FILE: website/content/docs/configuration/options.md
================================================
---
title: "Options"
description: "Explanations for all options"
aliases:
- /docs/options
---
## checks {#checks}
This option sets which [checks]({{< relref "/docs/checks" >}}) should be enabled.
By default, most checks will be enabled, except for those that are too opinionated or that only apply to packages in certain domains.
All supported checks can be enabled with `"all"`.
Subsets of checks can be enabled via prefixes and the `*` glob; for example, `"S*"`, `"SA*"` and `"SA1*"` will
enable all checks in the S, SA and SA1 subgroups respectively.
Individual checks can be enabled by their full IDs.
To disable checks, prefix them with a minus sign. This works on all of the previously mentioned values.
Default value: `["all", "-{{< check "ST1000" >}}", "-{{< check "ST1003" >}}", "-{{< check "ST1016" >}}", "-{{< check "ST1020" >}}", "-{{< check "ST1021" >}}", "-{{< check "ST1022" >}}"]`
## initialisms {#initialisms}
{{< check "ST1003" >}} checks, among other
things, for the correct capitalization of initialisms. The
set of known initialisms can be configured with this option.
Default value: `["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"]`
## dot_import_whitelist {#dot_import_whitelist}
By default, {{< check "ST1001" >}} forbids
all uses of dot imports in non-test packages. This
setting allows setting a whitelist of import paths that can
be dot-imported anywhere.
Default value: `[]`
## http_status_code_whitelist {#http_status_code_whitelist}
{{< check "ST1013" >}} recommends using constants from the `net/http` package
instead of hard-coding numeric HTTP status codes. This
setting specifies a list of numeric status codes that this
check does not complain about.
Default value: `["200", "400", "404", "500"]`
================================================
FILE: website/content/docs/faq.md
================================================
---
title: "Frequently Asked Questions"
---
{{% faq/list %}}
{{% faq/question id="false-positives" question="Staticcheck is wrong, what should I do?" %}}
First, make sure that Staticcheck is actually wrong.
It can find very subtle bugs, and what may look like a false positive at first glance is usually a genuine bug.
There is a long list of competent programmers
[who got it wrong before.](https://github.com/dominikh/go-tools/issues?q=is%3Aissue+label%3Afalse-positive+label%3Ainvalid+is%3Aclosed)
However, sometimes Staticcheck _is_ wrong and you want to suppress a warning to get on with your work.
In that case, you can use [ignore directives to ignore specific problems]({{< relref "/docs/configuration/#ignoring-problems" >}}).
You should also [report the false positive](https://github.com/dominikh/go-tools/issues/new?assignees=&labels=false-positive%2C+needs-triage&template=1_false_positive.md&title=) so that we can fix it.
We don't expect users to have to ignore many problems, and we always aim to avoid false positives.
Some checks, particularly those in the `ST` (stylecheck) category, may not be applicable to your code base at all. In that case, you should disable the check using the
[`checks` option]({{< relref "/docs/configuration/options#checks" >}})
in your [configuration]({{< relref "/docs/configuration/#configuration-files" >}}).
{{% /faq/question %}}
{{% faq/question id="go-version" question="Staticcheck's suggestions don't apply to my version of Go" %}}
You can [specify the version of Go your code should work with.]({{< relref "/docs/configuration/#targeting-go-versions" >}})
{{% /faq/question %}}
{{% /faq/list %}}
================================================
FILE: website/content/docs/getting-started.md
================================================
---
title: "Getting started"
description: "Quickly get started using Staticcheck"
weight: 1
aliases:
- /docs/install
---
## Installation
Beginning with Go 1.17, the simplest way of installing Staticcheck is by running:
```
go install honnef.co/go/tools/cmd/staticcheck@latest
```
This will install the latest version of Staticcheck to `$GOPATH/bin`. To find out where `$GOPATH` is, run `go env GOPATH`.
Instead of `@latest`, you can also use a specific version, such as `@2020.2.1`.
If you'd like to be notified of new releases, you can use [GitHub's Releases only watches](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/viewing-your-subscriptions#configuring-your-watch-settings-for-an-individual-repository).
### Binary releases
We publish binary releases for the most common operating systems and CPU architectures.
These can be downloaded from [GitHub](https://github.com/dominikh/go-tools/releases).
### Distribution packages
Many package managers include Staticcheck, allowing you to install it with your usual commands, such as `apt install`.
Note, however, that you might not always get new releases in a timely manner.
What follows is a non-exhaustive list of the package names in various package repositories.
Arch Linux
: [staticcheck](https://archlinux.org/packages/extra/x86_64/staticcheck/)
Debian
: [go-staticcheck](https://packages.debian.org/go-staticcheck)
Fedora
: [golang-honnef-tools](https://fedora.pkgs.org/33/fedora-x86_64/golang-honnef-tools-2020.1.5-2.fc33.x86_64.rpm.html)
Homebrew
: [staticcheck](https://formulae.brew.sh/formula/staticcheck)
MacPorts
: [staticcheck](https://ports.macports.org/port/staticcheck/summary)
NixOS
: go-tools
Scoop
: [staticcheck](https://github.com/ScoopInstaller/Main/blob/master/bucket/staticcheck.json)
## Running Staticcheck
The `staticcheck` command works much like `go build` or `go vet` do.
It supports all of the same package patterns.
For example, `staticcheck .` will check the current package, and `staticcheck ./...` will check all packages.
For more details on specifying packages to check, see `go help packages`.
Therefore, to start using Staticcheck, just run it on your code: `staticcheck ./...`.
It will print any issues it finds, or nothing at all if your code is squeaky clean.
Read the [Running Staticcheck]({{< relref "/docs/running-staticcheck" >}}) articles to learn more about running Staticcheck.
================================================
FILE: website/content/docs/running-staticcheck/_index.md
================================================
---
title: Running Staticcheck
weight: 2
aliases:
- /docs/run
---
================================================
FILE: website/content/docs/running-staticcheck/ci/_index.md
================================================
---
title: Continuous integration
description: How to run Staticcheck in CI
---
================================================
FILE: website/content/docs/running-staticcheck/ci/github-actions/index.md
================================================
---
title: GitHub Actions
description: Running Staticcheck in GitHub Actions
aliases:
- /docs/running-staticcheck/github-actions
---
We publish [our own action](https://github.com/marketplace/actions/staticcheck) for [GitHub Actions](https://github.com/features/actions),
which makes it very simple to run Staticcheck in CI on GitHub.
## Examples
At its simplest, just add `dominikh/staticcheck-action` as a step in your existing workflow.
A minimal workflow might look like this:
```yaml
name: "CI"
on: ["push", "pull_request"]
jobs:
ci:
name: "Run CI"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: dominikh/staticcheck-action@v1.2.0
with:
version: "2022.1.1"
```
A more advanced example that runs tests, go vet and Staticcheck on multiple OSs and Go versions looks like this:
```yaml
name: "CI"
on: ["push", "pull_request"]
jobs:
ci:
name: "Run CI"
strategy:
fail-fast: false
matrix:
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
go: ["1.16.x", "1.17.x"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1
- uses: WillAbides/setup-go-faster@v1.7.0
with:
go-version: ${{ matrix.go }}
- run: "go test ./..."
- run: "go vet ./..."
- uses: dominikh/staticcheck-action@v1.2.0
with:
version: "2022.1.1"
install-go: false
cache-key: ${{ matrix.go }}
```
Note that this example could benefit from further improvements, such as caching of Go's build cache.
## Managing Go
By default, `staticcheck-action` installs Go so that it can install and run Staticcheck.
It also saves and restores Go's build cache (in addition to Staticcheck's own cache) to speed up future runs.
This is intended for trivial jobs that only run Staticcheck, not other steps such as `go test`.
For more complicated jobs, it is strongly recommended that you set `install-go` to `false`,
install Go yourself (e.g. by using [`actions/setup-go`](https://github.com/actions/setup-go) or [`WillAbides/setup-go-faster`](https://github.com/WillAbides/setup-go-faster)),
and save and restore the Go build cache, for improved performance.
When installing Go, make sure the version meets Staticcheck's minimum requirements.
A given Staticcheck release supports the last two versions of Go (such as Go 1.16 and Go 1.17) at the time of release.
The action itself requires at least Go 1.16.
## Options
### `version`
Which version of Staticcheck to use.
Because new versions of Staticcheck introduce new checks that may break your build,
it is recommended to pin to a specific version and to update Staticheck consciously.
It defaults to `latest`, which installs the latest released version of Staticcheck.
### `min-go-version`
Minimum version of Go to support. This affects the diagnostics reported by Staticcheck,
avoiding suggestions that are not applicable to older versions of Go.
If unset, this will default to the Go version specified in your go.mod.
See https://staticcheck.dev/docs/running-staticcheck/cli/#go for more information.
### `build-tags`
Go build tags that get passed to Staticcheck via the `-tags` flag.
### `install-go`
Whether the action should install a suitable version of Go to install and run Staticcheck.
If Staticcheck is the only action in your job, this option can usually be left on its default value of `true`.
If your job already installs Go prior to running Staticcheck, for example to run unit tests, it is best to set this option to `false`.
The latest release of Staticcheck works with the last two minor releases of Go.
The action itself requires at least Go 1.16.
### `cache-key`
String to include in the cache key, in addition to the default, which is `runner.os`.
This is useful when using multiple Go versions.
================================================
FILE: website/content/docs/running-staticcheck/cli/_index.md
================================================
---
title: Command-line interface
description: How to use the `staticcheck` command
---
The `staticcheck` command is the primary way of running Staticcheck.
At its core, the `staticcheck` command works a lot like `go vet` or `go build`.
It accepts the same package patterns (see `go help packages` for details),
it outputs problems in the same format,
it supports a `-tags` flag for specifying which build tags to use, and so on.
Overall, it is meant to feel like another `go` command.
However, it also comes with several of its own flags to support some of its unique functionality.
This article will focus on explaining that unique functionality.
## Explaining checks {#explain}
You can use `staticcheck -explain ` to get a helpful description of a check.
Every diagnostic that staticcheck reports is annotated with the identifier of the specific check that found the issue. For example, in
```text
foo.go:1248:4: unnecessary use of fmt.Sprintf (S1039)
```
the check's identifier is S1039. Running `staticcheck -explain S1039` will output the following:
```text
Unnecessary use of fmt.Sprint
Calling fmt.Sprint with a single string argument is unnecessary and identical to using the string directly.
Available since
2020.1
Online documentation
https://staticcheck.dev/docs/checks#S1039
```
The output includes a one-line summary, one or more paragraphs of helpful text, the first version of Staticcheck that the check appeared in, and a link to online documentation, which contains the same information as the output of `staticcheck -explain`.
## Selecting an output format {#format}
Staticcheck can format its output in a number of ways, by using the `-f` flag.
See this [list of formatters]({{< relref "/docs/running-staticcheck/cli/formatters" >}}) for a list of all formatters.
## Targeting Go versions {#go}
Some of Staticcheck's analyses adjust their behavior based on the targeted Go version.
For example, the suggestion that one use `for range xs` instead of `for _ = range xs` only applies to Go 1.4 and later, as it won't compile with versions of Go older than that.
By default, Staticcheck targets the Go version declared in `go.mod` via the `go` directive.
For Go 1.21 and newer, that directive specifies the minimum required version of Go.
For older versions of Go, the directive technically specifies the maximum version of language features that the module
can use, which means it might be higher than the minimum required version. In those cases, you can manually overwrite
the targeted Go version by using the `-go` command line flag. For example, `staticcheck -go 1.0 ./...` will only make
suggestions that work with Go 1.0.
The targeted Go version limits both language features and parts of the standard library that will be recommended.
## Excluding tests {#tests}
By default, Staticcheck analyses packages as well as their tests.
By passing `-tests=false`, one can skip the analysis of tests.
This is primarily useful for the {{< check "U1000" >}} check, as it allows finding code that is only used by tests and would otherwise be unused.
================================================
FILE: website/content/docs/running-staticcheck/cli/build-tags/index.md
================================================
---
title: Build tags
description: How to correctly check code that uses build tags
---
## Introduction
In Go, files can have build tags, which control when said files will be part of a package.
For example, two files might contain alternate implementations of the same function, targeting Linux and Windows respectively.
Due to this, a single import path really refers to a collection of packages, with any particular package being chosen by a combination of build tags.
Even if your code doesn't make use of build tags, any of your transitive dependencies might.
Therefore, running `staticcheck my/package` really only checks one variant of `my/package`.
For more information on Go's build tags, see [`go help buildconstraint`](https://pkg.go.dev/cmd/go#hdr-Build_constraints).
## Implications
Checking packages using a single set of build tags can lead to both false positives and false negatives.
The reason for false negatives is straightforward: if some code is being excluded by build tags, then we won't check it.
False positives can be a bit more involved. Consider the following package, in [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format):
```go
-- api_linux.go --
package pkg
func Entry() { foo() }
-- api_windows.go --
package pkg
func Entry() { bar() }
-- shared.go --
package pkg
func foo() {}
func bar() {}
```
If we don't check the Windows build, then the function `bar` seems unused.
Similarly, if we don't check the Linux build, `foo` seems unused.
```terminal
$ GOOS=linux staticcheck
shared.go:4:6: func bar is unused (U1000)
$ GOOS=windows staticcheck
shared.go:3:6: func foo is unused (U1000)
```
Only when we check both builds do we see that both functions are in fact used.
Arguably, `foo` and `bar` should live in files with matching build tags to avoid this.
However, in reality, code bases can be complex, and it isn't always clear which sets of build tags make use of what code.
After all, if we always knew, we wouldn't have dead code to begin with.
Another example involves control flow. Consider this package, again in txtar format:
```go
-- api_linux.go --
package pkg
func dieIfUnsupported() { panic("unsupported") }
-- api_windows.go --
package pkg
func dieIfUnsupported() {}
-- shared.go --
package pkg
func foo() {}
func Entry() {
dieIfUnsupported()
foo()
}
```
Here, `dieIfUnsupported` panics unconditionally on Linux, but not on Windows.
Because Staticcheck takes control flow into consideration, this means that `foo` is unused on Linux but used on Windows.
Several checks have this sort of false positive, not just U1000.
## Solution
The solution to this problem is to run Staticcheck multiple times with different build tags and to merge the results.
At first glance, one might think that Staticcheck should be able to do this fully automatically: look at all build tags, find all unique combinations, and check them all.
However, this doesn't scale.
To be correct, Staticcheck would have to take dependencies and their tags into consideration, too.
Virtually all code depends on the Go standard library, and the Go standard library supports a plethora of operating systems, architectures, and a number of tags such as `netgo`.
All in all, there are thousands of unique combinations.
Checking all of these would take far too long.
However, the number of build configurations you care about is probably much smaller.
Your software probably supports 2-3 operating systems on 1-2 architectures,
and maybe has a debug and a release build.
This makes for a lot fewer combinations that need to be checked.
These are probably the same combinations you're already checking in CI, too, by running their tests.
This will become useful in a bit.
### The `-merge` flag
Using the `-merge` flag, Staticcheck can merge the results of multiple runs.
It decides on a per-check basis whether any run or all runs have to have reported an issue for it to be valid.
It also takes into consideration which files were checked by which run, to reduce false negatives.
In order to use `-merge`, the runs to be merged have to use the `-f binary` flag.
This outputs results in a binary format containing all information required by `-merge`.
When using `-merge`, arguments are interpreted as file names instead of import paths, so that `staticcheck -merge file1 file2` will read the files `file1` and `file2`, which must contain the output of `staticcheck -f binary` runs, and merge them.
```terminal
$ GOOS=linux staticcheck -f binary >file1
$ GOOS=windows staticcheck -f binary >file2
$ staticcheck -merge file1 file2
...
```
Alternatively, if no arguments are passed, `staticcheck -merge` will read from standard input instead.
This allows for workflows like
```
(
GOOS=linux staticcheck -f binary
GOOS=windows staticcheck -f binary
) | staticcheck -merge
```
This multi-step workflow of generating per-run output and merging it makes it possible to run Staticcheck on different systems before merging the results, which might be especially required when using cgo.
### The `-matrix` flag
With the `-matrix` flag, you can instruct Staticcheck to check multiple build configurations at once and merge the results.
In other words, it automates running Staticcheck multiple times and merging results afterwards.
This is useful when all configurations can be checked on a single system, for example because you don't use cgo.
When using the `-matrix` flag, Staticcheck reads a build matrix from standard input.
The build matrix uses a line-based format, where each non-empty line specifies a build name, environment variables and command-line flags.
A line is of the format `: [environment variables] [flags]`, for example `linux_debug: GOOS=linux -tags=debug -some-flag="some value"`.
Environment variables and flags get passed to `go` when Staticcheck analyzes code, so you can use all flags that `go` supports, such as `-tags` or `-gcflags`, although few flags other than `-tags` are really useful.
Valid build names consist of letters, numbers and underscores.
Here is an example of using a build matrix:
```terminal
$ staticcheck -matrix <
---
{{< blocks/section type="section" color="white" >}}
# Supporting Staticcheck's open source development
Staticcheck is an open source project that is provided free of charge and without any expectations.
Nevertheless, working on it requires a considerable time investment.
Some very generous people choose to support its development
by donating money on [GitHub Sponsors](https://github.com/users/dominikh/sponsorship) or [Patreon](https://www.patreon.com/dominikh).
While this is in no way expected of them, it is tremendously appreciated.
In addition to these individuals, a number of companies also
decide to support Staticcheck through monetary means.
Their contributions to open source ensure the viability and future development of Staticcheck.
Their support, too, is greatly appreciated.
{{< sponsors.inline >}}
{{ with $sponsors := $.Site.Data.sponsors.sponsors }}
The companies supporting Staticcheck are, in alphabetical order:
{{ range $sponsor := sort $sponsors "name" "asc" }}
{{ if $sponsor.enabled }}
{{ $sponsor.name }}
{{ end }}
{{ end }}
{{ end }}
{{< /sponsors.inline >}}
If your company would like to support Staticcheck, please check out [GitHub Sponsors](https://github.com/users/dominikh/sponsorship)
or [get in touch with me directly.](mailto:dominik@honnef.co)
For [$250 USD a month](https://github.com/users/dominikh/sponsorship?utf8=%E2%9C%93&tier_id=MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4yNTAy&editing=false),
we will proudly display your logo on the project's homepage,
showing the world that you truly care about code quality and open source.
Finally, every single user, individual and company alike, is to be thanked for using Staticcheck, providing feedback, requesting features and in general caring about code quality.
Without its users, there would be no Staticcheck.
{{< /blocks/section >}}
================================================
FILE: website/go.mod
================================================
module honnef.co/go/tools/website
go 1.25.0
replace honnef.co/go/tools => ../
require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
honnef.co/go/tools v0.0.0-00010101000000-000000000000
)
require (
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054 // indirect
)
================================================
FILE: website/go.sum
================================================
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054 h1:CHVDrNHx9ZoOrNN9kKWYIbT5Rj+WF2rlwPkhbQQ5V4U=
golang.org/x/tools v0.40.1-0.20260108161641-ca281cf95054/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
================================================
FILE: website/layouts/_internal/twitter_cards.html
================================================
{{- with $.Params.images -}}
{{ else -}}
{{- $images := $.Resources.ByType "image" -}}
{{- $featured := $images.GetMatch "*feature*" -}}
{{- if not $featured }}{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }}{{ end -}}
{{- with $featured -}}
{{- else -}}
{{- with $.Site.Params.images -}}
{{- end -}}
{{- end -}}
{{- end }}
{{ with .Site.Social.twitter -}}
{{ end -}}
================================================
FILE: website/layouts/changes/list.rss.xml
================================================
{{ .Site.Title }} – {{ .Title }}
{{ .Permalink }}
Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}
Hugo -- gohugo.io
{{ with .Site.LanguageCode }}
{{.}}
{{end}}
{{ with .Site.Author.email }}
{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}
{{end}}
{{ with .Site.Author.email }}
{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}
{{end}}
{{ with .Site.Copyright }}
{{.}}
{{end}}
{{ if not .Date.IsZero }}
{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf " " .Permalink .MediaType | safeHTML }}
{{ end }}
{{ if not $.Section }}
{{ $sections := .Site.Params.rss_sections | default (slice "blog") }}
{{ .Scratch.Set "rss_pages" (first 50 (where $.Site.RegularPages "Type" "in" $sections )) }}
{{ else }}
{{ .Scratch.Set "rss_pages" (first 50 $.Pages) }}
{{ end }}
{{ range (.Scratch.Get "rss_pages") }}
-
{{ $.Page.Title }}: {{ .Title }}
{{ .Permalink }}
{{ if not .Date.IsZero }}
{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}
{{ end }}
{{ with .Site.Author.email }}
{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}
{{end}}
{{ .Permalink }}
{{ .Content | html }}
{{ end }}
================================================
FILE: website/layouts/index.redir
================================================
{{ range $p := .Site.Pages }}
{{- range .Aliases -}}
{{ . }} {{ $p.RelPermalink }}
{{ end }}
{{- end -}}
/pricing /sponsors 301
/docs/staticcheck -> /docs/checks 301
/docs/gosimple /docs/checks 301
/issues/* https://github.com/dominikh/go-tools/issues/:splat 301
http://staticcheck.io/* https://staticcheck.dev/:splat 301!
https://staticcheck.io/* https://staticcheck.dev/:splat 301!
http://www.staticcheck.io/* https://staticcheck.dev/:splat 301!
https://www.staticcheck.io/* https://staticcheck.dev/:splat 301!
================================================
FILE: website/layouts/partials/breadcrumb.html
================================================
{{ $isSingle := true -}}
{{ with .Parent -}}
{{ $isSingle = .IsHome -}}
{{ end -}}
{{ .Scratch.Set "breadcrumbMaxDepth" 0 }}
{{ template "breadcrumbcount" (dict "p1" . "scratch" .Scratch) }}
{{ define "breadcrumbcount" }}
{{ .scratch.Set "breadcrumbMaxDepth" (add 1 (.scratch.Get "breadcrumbMaxDepth")) }}
{{ if .p1.Parent }}
{{ if not .p1.Parent.IsHome }}
{{ template "breadcrumbcount" (dict "p1" .p1.Parent "scratch" .scratch) }}
{{ end }}
{{ else if not .IsHome }}
{{ template "breadcrumbcount" (dict "p1" .p1.Site.Home "scratch" .scratch) }}
{{ end }}
{{ end }}
{{- template "breadcrumbnav" (dict "p1" . "p2" . "depth" 0 "scratch" .Scratch) }}
{{- define "breadcrumbnav" -}}
{{ if .p1.Parent -}}
{{ if not .p1.Parent.IsHome -}}
{{ template "breadcrumbnav" (dict "p1" .p1.Parent "p2" .p2 "depth" (add .depth 1) "scratch" .scratch) -}}
{{ end -}}
{{ else if not .p1.IsHome -}}
{{ template "breadcrumbnav" (dict "p1" .p1.Site.Home "p2" .p2 "depth" (add .depth 1) "scratch" .scratch) -}}
{{ end -}}
{{ $isActive := eq .p1 .p2 }}
{{- .p1.LinkTitle -}}
{{- end -}}
================================================
FILE: website/layouts/partials/footer.html
================================================
{{ define "partials/sponsor-logo-resize" }}
{{- if gt .img.Width .width -}}
{{- $rimg := .img.Resize (printf "%dx" .width) -}}
{{- $rimg.RelPermalink }} {{ $rimg.Width -}}w,
{{- end -}}
{{ end }}
{{ define "partials/sponsor-logo" }}
{{ $img := resources.Get .logo }}
{{ end }}
{{ with $sponsors := $.Site.Data.sponsors.sponsors }}
{{ end }}
{{ $links := .Site.Params.links }}
{{ range $cp := .Site.Data.copyrights.copyrights }}
{{ $cp | safeHTML }}
{{ end }}
{{ with .Site.Params.privacy_policy }}
{{ T "footer_privacy_policy" }} {{ end }}
{{ if not .Site.Params.ui.footer_about_disable }}
{{ with .Site.GetPage "about" }}
{{ .Title }}
{{ end }}
{{ end }}
{{ define "footer-links-block" }}
{{ end }}
================================================
FILE: website/layouts/partials/hooks/head-end.html
================================================
================================================
FILE: website/layouts/partials/navbar.html
================================================
{{ $cover := and (.HasShortcode "blocks/cover") (not .Site.Params.ui.navbar_translucent_over_cover_disable) }}
{{ if .Site.Params.ui.navbar_logo }}{{ with resources.Get "icons/logo.svg" }}{{ ( . | minify).Content | safeHTML }}{{ end }}{{ end }} {{ .Site.Title }}
{{ $p := . }}
{{ range .Site.Menus.main }}
{{ $active := or ($p.IsMenuCurrent "main" .) ($p.HasMenuCurrent "main" .) }}
{{ with .Page }}
{{ $active = or $active ( $.IsDescendant .) }}
{{ end }}
{{ $pre := .Pre }}
{{ $post := .Post }}
{{ $url := urls.Parse .URL }}
{{ $baseurl := urls.Parse $.Site.Params.Baseurl }}
{{ with .Pre}}{{ $pre }}{{ end }}{{ .Name }} {{ with .Post}}{{ $post }}{{ end }}
{{ end }}
{{ if .Site.Params.versions }}
{{ partial "navbar-version-selector.html" . }}
{{ end }}
{{ if (gt (len .Site.Home.Translations) 0) }}
{{ partial "navbar-lang-selector.html" . }}
{{ end }}
{{ partial "search-input.html" . }}
================================================
FILE: website/layouts/shortcodes/check.html
================================================
{{ $name := .Get 0 -}}
{{ $check := index $.Site.Data.checks.Checks $name -}}
{{ with .Get 0 }}{{$name}} {{ end -}}
================================================
FILE: website/layouts/shortcodes/commit.html
================================================
{{ with .Get 0 }}{{ . }} {{ end -}}
================================================
FILE: website/layouts/shortcodes/content.html
================================================
{{$file := .Get 0}}
{{ with .Site.GetPage $file }}{{ .Content | markdownify }}{{ end }}
================================================
FILE: website/layouts/shortcodes/details.html
================================================
{{ .Get 0 }}
{{ .Inner | markdownify }}
================================================
FILE: website/layouts/shortcodes/faq/list.html
================================================
{{ .Inner }}
================================================
FILE: website/layouts/shortcodes/faq/question.md
================================================
##
{{ .Get "question" }} {#{{ .Get "id" }}}
================================================
FILE: website/layouts/shortcodes/issue.html
================================================
{{ with .Get 0 }}issue {{ . }} {{ end -}}
================================================
FILE: website/layouts/shortcodes/issueref.html
================================================
https://staticcheck.dev/issues/{{ .Get 0 }}
================================================
FILE: website/layouts/shortcodes/option.html
================================================
{{ .Get 0 }} {{"" -}}
================================================
FILE: website/package.json
================================================
{
"dependencies": {
"autoprefixer": "^10.4.13",
"postcss": "^8.4.31",
"postcss-cli": "^10.1.0"
}
}
================================================
FILE: website/shell.nix
================================================
{ pkgs ? import {} }:
pkgs.mkShell {
# nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs; [ postcss-cli nodejs ];
}
================================================
FILE: website/static/_headers
================================================
/img/*
cache-control: public
cache-control: max-age=604800
cache-control: immutable
/webfonts/*
cache-control: public
cache-control: max-age=604800
cache-control: immutable
/scss/*.css
cache-control: public
cache-control: max-age=604800
cache-control: immutable
/js/*.js
cache-control: public
cache-control: max-age=604800
cache-control: immutable
================================================
FILE: website/website.go
================================================
package website