1
0

Update and fix staticcheck

This commit is contained in:
kolaente
2020-05-29 22:15:21 +02:00
parent aae1bc3cab
commit a525787ab7
100 changed files with 12353 additions and 7912 deletions

View File

@ -1,15 +0,0 @@
# Contributing to staticcheck
## Before filing an issue:
### Are you having trouble building staticcheck?
Check you have the latest version of its dependencies. Run
```
go get -u honnef.co/go/tools/staticcheck
```
If you still have problems, consider searching for existing issues before filing a new issue.
## Before sending a pull request:
Have you understood the purpose of staticcheck? Make sure to carefully read `README`.

View File

@ -1,525 +1,267 @@
package staticcheck
import (
"flag"
"honnef.co/go/tools/facts"
"honnef.co/go/tools/internal/passes/buildssa"
"honnef.co/go/tools/internal/passes/buildir"
"honnef.co/go/tools/lint/lintutil"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
func newFlagSet() flag.FlagSet {
fs := flag.NewFlagSet("", flag.PanicOnError)
fs.Var(lintutil.NewVersionFlag(), "go", "Target Go version")
return *fs
func makeCallCheckerAnalyzer(rules map[string]CallCheck, extraReqs ...*analysis.Analyzer) *analysis.Analyzer {
reqs := []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile}
reqs = append(reqs, extraReqs...)
return &analysis.Analyzer{
Run: callChecker(rules),
Requires: reqs,
}
}
var Analyzers = map[string]*analysis.Analyzer{
"SA1000": {
Name: "SA1000",
Run: callChecker(checkRegexpRules),
Doc: Docs["SA1000"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
var Analyzers = lintutil.InitializeAnalyzers(Docs, map[string]*analysis.Analyzer{
"SA1000": makeCallCheckerAnalyzer(checkRegexpRules),
"SA1001": {
Name: "SA1001",
Run: CheckTemplate,
Doc: Docs["SA1001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1002": {
Name: "SA1002",
Run: callChecker(checkTimeParseRules),
Doc: Docs["SA1002"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1003": {
Name: "SA1003",
Run: callChecker(checkEncodingBinaryRules),
Doc: Docs["SA1003"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1002": makeCallCheckerAnalyzer(checkTimeParseRules),
"SA1003": makeCallCheckerAnalyzer(checkEncodingBinaryRules),
"SA1004": {
Name: "SA1004",
Run: CheckTimeSleepConstant,
Doc: Docs["SA1004"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1005": {
Name: "SA1005",
Run: CheckExec,
Doc: Docs["SA1005"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1006": {
Name: "SA1006",
Run: CheckUnsafePrintf,
Doc: Docs["SA1006"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1007": {
Name: "SA1007",
Run: callChecker(checkURLsRules),
Doc: Docs["SA1007"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1007": makeCallCheckerAnalyzer(checkURLsRules),
"SA1008": {
Name: "SA1008",
Run: CheckCanonicalHeaderKey,
Doc: Docs["SA1008"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1010": {
Name: "SA1010",
Run: callChecker(checkRegexpFindAllRules),
Doc: Docs["SA1010"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1011": {
Name: "SA1011",
Run: callChecker(checkUTF8CutsetRules),
Doc: Docs["SA1011"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1010": makeCallCheckerAnalyzer(checkRegexpFindAllRules),
"SA1011": makeCallCheckerAnalyzer(checkUTF8CutsetRules),
"SA1012": {
Name: "SA1012",
Run: CheckNilContext,
Doc: Docs["SA1012"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1013": {
Name: "SA1013",
Run: CheckSeeker,
Doc: Docs["SA1013"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1014": {
Name: "SA1014",
Run: callChecker(checkUnmarshalPointerRules),
Doc: Docs["SA1014"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1014": makeCallCheckerAnalyzer(checkUnmarshalPointerRules),
"SA1015": {
Name: "SA1015",
Run: CheckLeakyTimeTick,
Doc: Docs["SA1015"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1016": {
Name: "SA1016",
Run: CheckUntrappableSignal,
Doc: Docs["SA1016"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA1017": {
Name: "SA1017",
Run: callChecker(checkUnbufferedSignalChanRules),
Doc: Docs["SA1017"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1018": {
Name: "SA1018",
Run: callChecker(checkStringsReplaceZeroRules),
Doc: Docs["SA1018"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1017": makeCallCheckerAnalyzer(checkUnbufferedSignalChanRules),
"SA1018": makeCallCheckerAnalyzer(checkStringsReplaceZeroRules),
"SA1019": {
Name: "SA1019",
Run: CheckDeprecated,
Doc: Docs["SA1019"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Deprecated},
Flags: newFlagSet(),
},
"SA1020": {
Name: "SA1020",
Run: callChecker(checkListenAddressRules),
Doc: Docs["SA1020"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1021": {
Name: "SA1021",
Run: callChecker(checkBytesEqualIPRules),
Doc: Docs["SA1021"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Deprecated, facts.Generated},
},
"SA1020": makeCallCheckerAnalyzer(checkListenAddressRules),
"SA1021": makeCallCheckerAnalyzer(checkBytesEqualIPRules),
"SA1023": {
Name: "SA1023",
Run: CheckWriterBufferModified,
Doc: Docs["SA1023"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
},
"SA1024": {
Name: "SA1024",
Run: callChecker(checkUniqueCutsetRules),
Doc: Docs["SA1024"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1024": makeCallCheckerAnalyzer(checkUniqueCutsetRules),
"SA1025": {
Name: "SA1025",
Run: CheckTimerResetReturnValue,
Doc: Docs["SA1025"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
},
"SA1026": {
Name: "SA1026",
Run: callChecker(checkUnsupportedMarshal),
Doc: Docs["SA1026"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA1027": {
Name: "SA1027",
Run: callChecker(checkAtomicAlignment),
Doc: Docs["SA1027"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA1026": makeCallCheckerAnalyzer(checkUnsupportedMarshal),
"SA1027": makeCallCheckerAnalyzer(checkAtomicAlignment),
"SA1028": makeCallCheckerAnalyzer(checkSortSliceRules),
"SA1029": makeCallCheckerAnalyzer(checkWithValueKeyRules),
"SA2000": {
Name: "SA2000",
Run: CheckWaitgroupAdd,
Doc: Docs["SA2000"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA2001": {
Name: "SA2001",
Run: CheckEmptyCriticalSection,
Doc: Docs["SA2001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA2002": {
Name: "SA2002",
Run: CheckConcurrentTesting,
Doc: Docs["SA2002"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA2003": {
Name: "SA2003",
Run: CheckDeferLock,
Doc: Docs["SA2003"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA3000": {
Name: "SA3000",
Run: CheckTestMainExit,
Doc: Docs["SA3000"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA3001": {
Name: "SA3001",
Run: CheckBenchmarkN,
Doc: Docs["SA3001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4000": {
Name: "SA4000",
Run: CheckLhsRhsIdentical,
Doc: Docs["SA4000"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.TokenFile, facts.Generated},
Flags: newFlagSet(),
},
"SA4001": {
Name: "SA4001",
Run: CheckIneffectiveCopy,
Doc: Docs["SA4001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4002": {
Name: "SA4002",
Run: CheckDiffSizeComparison,
Doc: Docs["SA4002"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA4003": {
Name: "SA4003",
Run: CheckExtremeComparison,
Doc: Docs["SA4003"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4004": {
Name: "SA4004",
Run: CheckIneffectiveLoop,
Doc: Docs["SA4004"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4006": {
Name: "SA4006",
Run: CheckUnreadVariableValues,
Doc: Docs["SA4006"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, facts.Generated},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.Generated},
},
"SA4008": {
Name: "SA4008",
Run: CheckLoopCondition,
Doc: Docs["SA4008"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4009": {
Name: "SA4009",
Run: CheckArgOverwritten,
Doc: Docs["SA4009"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4010": {
Name: "SA4010",
Run: CheckIneffectiveAppend,
Doc: Docs["SA4010"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4011": {
Name: "SA4011",
Run: CheckScopedBreak,
Doc: Docs["SA4011"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4012": {
Name: "SA4012",
Run: CheckNaNComparison,
Doc: Docs["SA4012"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA4013": {
Name: "SA4013",
Run: CheckDoubleNegation,
Doc: Docs["SA4013"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4014": {
Name: "SA4014",
Run: CheckRepeatedIfElse,
Doc: Docs["SA4014"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4015": {
Name: "SA4015",
Run: callChecker(checkMathIntRules),
Doc: Docs["SA4015"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA4015": makeCallCheckerAnalyzer(checkMathIntRules),
"SA4016": {
Name: "SA4016",
Run: CheckSillyBitwiseOps,
Doc: Docs["SA4016"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, facts.TokenFile},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.TokenFile},
},
"SA4017": {
Name: "SA4017",
Run: CheckPureFunctions,
Doc: Docs["SA4017"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, facts.Purity},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.Purity},
},
"SA4018": {
Name: "SA4018",
Run: CheckSelfAssignment,
Doc: Docs["SA4018"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.TokenFile},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.TokenFile, facts.Purity},
},
"SA4019": {
Name: "SA4019",
Run: CheckDuplicateBuildConstraints,
Doc: Docs["SA4019"].String(),
Requires: []*analysis.Analyzer{facts.Generated},
Flags: newFlagSet(),
},
"SA4020": {
Name: "SA4020",
Run: CheckUnreachableTypeCases,
Doc: Docs["SA4020"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA4021": {
Name: "SA4021",
Run: CheckSingleArgAppend,
Doc: Docs["SA4021"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.TokenFile},
Flags: newFlagSet(),
},
"SA5000": {
Name: "SA5000",
Run: CheckNilMaps,
Doc: Docs["SA5000"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5001": {
Name: "SA5001",
Run: CheckEarlyDefer,
Doc: Docs["SA5001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA5002": {
Name: "SA5002",
Run: CheckInfiniteEmptyLoop,
Doc: Docs["SA5002"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA5003": {
Name: "SA5003",
Run: CheckDeferInInfiniteLoop,
Doc: Docs["SA5003"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA5004": {
Name: "SA5004",
Run: CheckLoopEmptyDefault,
Doc: Docs["SA5004"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA5005": {
Name: "SA5005",
Run: CheckCyclicFinalizer,
Doc: Docs["SA5005"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5007": {
Name: "SA5007",
Run: CheckInfiniteRecursion,
Doc: Docs["SA5007"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA5008": {
Name: "SA5008",
Run: CheckStructTags,
Doc: Docs["SA5008"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA5009": {
Name: "SA5009",
Run: callChecker(checkPrintfRules),
Doc: Docs["SA5009"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
"SA5009": makeCallCheckerAnalyzer(checkPrintfRules),
"SA5010": {
Run: CheckImpossibleTypeAssertion,
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile},
},
"SA5011": {
Run: CheckMaybeNil,
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6000": {
Name: "SA6000",
Run: callChecker(checkRegexpMatchLoopRules),
Doc: Docs["SA6000"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
},
"SA6000": makeCallCheckerAnalyzer(checkRegexpMatchLoopRules),
"SA6001": {
Name: "SA6001",
Run: CheckMapBytesKey,
Doc: Docs["SA6001"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
},
"SA6002": {
Name: "SA6002",
Run: callChecker(checkSyncPoolValueRules),
Doc: Docs["SA6002"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6002": makeCallCheckerAnalyzer(checkSyncPoolValueRules),
"SA6003": {
Name: "SA6003",
Run: CheckRangeStringRunes,
Doc: Docs["SA6003"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer},
},
"SA6005": {
Name: "SA6005",
Run: CheckToLowerToUpperComparison,
Doc: Docs["SA6005"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA9001": {
Name: "SA9001",
Run: CheckDubiousDeferInChannelRangeLoop,
Doc: Docs["SA9001"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA9002": {
Name: "SA9002",
Run: CheckNonOctalFileMode,
Doc: Docs["SA9002"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
"SA9003": {
Name: "SA9003",
Run: CheckEmptyBranch,
Doc: Docs["SA9003"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, facts.TokenFile, facts.Generated},
Flags: newFlagSet(),
Requires: []*analysis.Analyzer{buildir.Analyzer, facts.TokenFile, facts.Generated},
},
"SA9004": {
Name: "SA9004",
Run: CheckMissingEnumTypesInDeclaration,
Doc: Docs["SA9004"].String(),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Flags: newFlagSet(),
},
// Filtering generated code because it may include empty structs generated from data models.
"SA9005": {
Name: "SA9005",
Run: callChecker(checkNoopMarshal),
Doc: Docs["SA9005"].String(),
Requires: []*analysis.Analyzer{buildssa.Analyzer, valueRangesAnalyzer, facts.Generated, facts.TokenFile},
Flags: newFlagSet(),
"SA9005": makeCallCheckerAnalyzer(checkNoopMarshal, facts.Generated),
"SA4022": {
Run: CheckAddressIsNil,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
}
})

View File

@ -4,12 +4,12 @@ import (
"go/ast"
"strings"
. "honnef.co/go/tools/lint/lintdsl"
"honnef.co/go/tools/code"
)
func buildTags(f *ast.File) [][]string {
var out [][]string
for _, line := range strings.Split(Preamble(f), "\n") {
for _, line := range strings.Split(code.Preamble(f), "\n") {
if !strings.HasPrefix(line, "+build ") {
continue
}

View File

@ -3,22 +3,22 @@ package staticcheck
import "honnef.co/go/tools/lint"
var Docs = map[string]*lint.Documentation{
"SA1000": &lint.Documentation{
"SA1000": {
Title: `Invalid regular expression`,
Since: "2017.1",
},
"SA1001": &lint.Documentation{
"SA1001": {
Title: `Invalid template`,
Since: "2017.1",
},
"SA1002": &lint.Documentation{
"SA1002": {
Title: `Invalid format in time.Parse`,
Since: "2017.1",
},
"SA1003": &lint.Documentation{
"SA1003": {
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
@ -29,7 +29,7 @@ Before Go 1.8, bool wasn't supported, either.`,
Since: "2017.1",
},
"SA1004": &lint.Documentation{
"SA1004": {
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)
@ -41,12 +41,12 @@ 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
'n * time.Nanosecond' to signal to Staticcheck that you did mean to sleep
for some amount of nanoseconds.`,
Since: "2017.1",
},
"SA1005": &lint.Documentation{
"SA1005": {
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
@ -69,7 +69,7 @@ Windows, will have a /bin/sh program:
Since: "2017.1",
},
"SA1006": &lint.Documentation{
"SA1006": {
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
@ -93,12 +93,12 @@ and pass the string as an argument.`,
Since: "2017.1",
},
"SA1007": &lint.Documentation{
"SA1007": {
Title: `Invalid URL in net/url.Parse`,
Since: "2017.1",
},
"SA1008": &lint.Documentation{
"SA1008": {
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
@ -123,39 +123,39 @@ http.CanonicalHeaderKey.`,
Since: "2017.1",
},
"SA1010": &lint.Documentation{
"SA1010": {
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",
},
"SA1011": &lint.Documentation{
"SA1011": {
Title: `Various methods in the strings package expect valid UTF-8, but invalid input is provided`,
Since: "2017.1",
},
"SA1012": &lint.Documentation{
"SA1012": {
Title: `A nil context.Context is being passed to a function, consider using context.TODO instead`,
Since: "2017.1",
},
"SA1013": &lint.Documentation{
"SA1013": {
Title: `io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second`,
Since: "2017.1",
},
"SA1014": &lint.Documentation{
"SA1014": {
Title: `Non-pointer value passed to Unmarshal or Decode`,
Since: "2017.1",
},
"SA1015": &lint.Documentation{
"SA1015": {
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`,
Since: "2017.1",
},
"SA1016": &lint.Documentation{
"SA1016": {
Title: `Trapping a signal that cannot be trapped`,
Text: `Not all signals can be intercepted by a process. Speficially, on
UNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are
@ -164,7 +164,7 @@ kernel. It is therefore pointless to try and handle these signals.`,
Since: "2017.1",
},
"SA1017": &lint.Documentation{
"SA1017": {
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
@ -175,24 +175,24 @@ signal value, a buffer of size 1 is sufficient.`,
Since: "2017.1",
},
"SA1018": &lint.Documentation{
"SA1018": {
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",
},
"SA1019": &lint.Documentation{
"SA1019": {
Title: `Using a deprecated function, variable, constant or field`,
Since: "2017.1",
},
"SA1020": &lint.Documentation{
"SA1020": {
Title: `Using an invalid host:port pair with a net.Listen-related function`,
Since: "2017.1",
},
"SA1021": &lint.Documentation{
"SA1021": {
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
@ -202,13 +202,13 @@ be used, as it takes both representations into account.`,
Since: "2017.1",
},
"SA1023": &lint.Documentation{
"SA1023": {
Title: `Modifying the buffer in an io.Writer implementation`,
Text: `Write must not modify the slice data, even temporarily.`,
Since: "2017.1",
},
"SA1024": &lint.Documentation{
"SA1024": {
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
@ -223,17 +223,17 @@ In order to remove one string from another, use strings.TrimPrefix instead.`,
Since: "2017.1",
},
"SA1025": &lint.Documentation{
"SA1025": {
Title: `It is not possible to use (*time.Timer).Reset's return value correctly`,
Since: "2019.1",
},
"SA1026": &lint.Documentation{
"SA1026": {
Title: `Cannot marshal channels or functions`,
Since: "2019.2",
},
"SA1027": &lint.Documentation{
"SA1027": {
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
@ -245,12 +245,32 @@ in a struct.`,
Since: "2019.2",
},
"SA2000": &lint.Documentation{
"SA1028": {
Title: `sort.Slice can only be used on slices`,
Text: `The first argument of sort.Slice must be a slice.`,
Since: "2020.1",
},
"SA1029": {
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",
},
"SA2000": {
Title: `sync.WaitGroup.Add called inside the goroutine, leading to a race condition`,
Since: "2017.1",
},
"SA2001": &lint.Documentation{
"SA2001": {
Title: `Empty critical section, did you mean to defer the unlock?`,
Text: `Empty critical sections of the kind
@ -271,17 +291,17 @@ rare false positive.`,
Since: "2017.1",
},
"SA2002": &lint.Documentation{
"SA2002": {
Title: `Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed`,
Since: "2017.1",
},
"SA2003": &lint.Documentation{
"SA2003": {
Title: `Deferred Lock right after locking, likely meant to defer Unlock instead`,
Since: "2017.1",
},
"SA3000": &lint.Documentation{
"SA3000": {
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,
@ -292,7 +312,7 @@ os.Exit(m.Run()).`,
Since: "2017.1",
},
"SA3001": &lint.Documentation{
"SA3001": {
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
@ -301,102 +321,102 @@ falsify results.`,
Since: "2017.1",
},
"SA4000": &lint.Documentation{
"SA4000": {
Title: `Boolean expression has identical expressions on both sides`,
Since: "2017.1",
},
"SA4001": &lint.Documentation{
"SA4001": {
Title: `&*x gets simplified to x, it does not copy x`,
Since: "2017.1",
},
"SA4002": &lint.Documentation{
"SA4002": {
Title: `Comparing strings with known different sizes has predictable results`,
Since: "2017.1",
},
"SA4003": &lint.Documentation{
"SA4003": {
Title: `Comparing unsigned values against negative values is pointless`,
Since: "2017.1",
},
"SA4004": &lint.Documentation{
"SA4004": {
Title: `The loop exits unconditionally after one iteration`,
Since: "2017.1",
},
"SA4005": &lint.Documentation{
"SA4005": {
Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`,
Since: "2017.1",
},
"SA4006": &lint.Documentation{
"SA4006": {
Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`,
Since: "2017.1",
},
"SA4008": &lint.Documentation{
"SA4008": {
Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`,
Since: "2017.1",
},
"SA4009": &lint.Documentation{
"SA4009": {
Title: `A function argument is overwritten before its first use`,
Since: "2017.1",
},
"SA4010": &lint.Documentation{
"SA4010": {
Title: `The result of append will never be observed anywhere`,
Since: "2017.1",
},
"SA4011": &lint.Documentation{
"SA4011": {
Title: `Break statement with no effect. Did you mean to break out of an outer loop?`,
Since: "2017.1",
},
"SA4012": &lint.Documentation{
"SA4012": {
Title: `Comparing a value against NaN even though no value is equal to NaN`,
Since: "2017.1",
},
"SA4013": &lint.Documentation{
"SA4013": {
Title: `Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.`,
Since: "2017.1",
},
"SA4014": &lint.Documentation{
"SA4014": {
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",
},
"SA4015": &lint.Documentation{
"SA4015": {
Title: `Calling functions like math.Ceil on floats converted from integers doesn't do anything useful`,
Since: "2017.1",
},
"SA4016": &lint.Documentation{
"SA4016": {
Title: `Certain bitwise operations, such as x ^ 0, do not do anything useful`,
Since: "2017.1",
},
"SA4017": &lint.Documentation{
"SA4017": {
Title: `A pure function's return value is discarded, making the call pointless`,
Since: "2017.1",
},
"SA4018": &lint.Documentation{
"SA4018": {
Title: `Self-assignment of variables`,
Since: "2017.1",
},
"SA4019": &lint.Documentation{
"SA4019": {
Title: `Multiple, identical build constraints in the same file`,
Since: "2017.1",
},
"SA4020": &lint.Documentation{
"SA4020": {
Title: `Unreachable case clause in a type switch`,
Text: `In a type switch like the following
@ -467,27 +487,33 @@ and therefore doSomething()'s return value implements both.`,
Since: "2019.2",
},
"SA4021": &lint.Documentation{
"SA4021": {
Title: `x = append(y) is equivalent to x = y`,
Since: "2019.2",
},
"SA5000": &lint.Documentation{
"SA4022": {
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",
},
"SA5000": {
Title: `Assignment to nil map`,
Since: "2017.1",
},
"SA5001": &lint.Documentation{
"SA5001": {
Title: `Defering Close before checking for a possible error`,
Since: "2017.1",
},
"SA5002": &lint.Documentation{
"SA5002": {
Title: `The empty for loop (for {}) spins and can block the scheduler`,
Since: "2017.1",
},
"SA5003": &lint.Documentation{
"SA5003": {
Title: `Defers in infinite loops will never execute`,
Text: `Defers are scoped to the surrounding function, not the surrounding
block. In a function that never returns, i.e. one containing an
@ -495,12 +521,12 @@ infinite loop, defers will never execute.`,
Since: "2017.1",
},
"SA5004": &lint.Documentation{
"SA5004": {
Title: `for { select { ... with an empty default branch spins`,
Since: "2017.1",
},
"SA5005": &lint.Documentation{
"SA5005": {
Title: `The finalizer references the finalized object, preventing garbage collection`,
Text: `A finalizer is a function associated with an object that runs when the
garbage collector is ready to collect said object, that is when the
@ -516,12 +542,12 @@ to zero before the object is being passed to the finalizer.`,
Since: "2017.1",
},
"SA5006": &lint.Documentation{
"SA5006": {
Title: `Slice index out of bounds`,
Since: "2017.1",
},
"SA5007": &lint.Documentation{
"SA5007": {
Title: `Infinite recursive call`,
Text: `A function that calls itself recursively needs to have an exit
condition. Otherwise it will recurse forever, until the system runs
@ -535,22 +561,112 @@ should be used instead.`,
Since: "2017.1",
},
"SA5008": &lint.Documentation{
"SA5008": {
Title: `Invalid struct tag`,
Since: "2019.2",
},
"SA5009": &lint.Documentation{
"SA5009": {
Title: `Invalid Printf call`,
Since: "2019.2",
},
"SA6000": &lint.Documentation{
"SA5010": {
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",
},
"SA5011": {
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",
},
"SA6000": {
Title: `Using regexp.Match or related in a loop, should use regexp.Compile`,
Since: "2017.1",
},
"SA6001": &lint.Documentation{
"SA6001": {
Title: `Missing an optimization opportunity when indexing maps by byte slices`,
Text: `Map keys must be comparable, which precludes the use of byte slices.
@ -580,7 +696,7 @@ f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`,
Since: "2017.1",
},
"SA6002": &lint.Documentation{
"SA6002": {
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.
@ -597,7 +713,7 @@ that discuss this problem.`,
Since: "2017.1",
},
"SA6003": &lint.Documentation{
"SA6003": {
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
@ -619,7 +735,7 @@ the slice of runes.`,
Since: "2017.1",
},
"SA6005": &lint.Documentation{
"SA6005": {
Title: `Inefficient string comparison with strings.ToLower or strings.ToUpper`,
Text: `Converting two strings to the same case and comparing them like so
@ -643,22 +759,22 @@ https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`,
Since: "2019.2",
},
"SA9001": &lint.Documentation{
"SA9001": {
Title: `Defers in range loops may not run when you expect them to`,
Since: "2017.1",
},
"SA9002": &lint.Documentation{
"SA9002": {
Title: `Using a non-octal os.FileMode that looks like it was meant to be in octal.`,
Since: "2017.1",
},
"SA9003": &lint.Documentation{
"SA9003": {
Title: `Empty body in an if or else branch`,
Since: "2017.1",
},
"SA9004": &lint.Documentation{
"SA9004": {
Title: `Only the first constant has an explicit type`,
Text: `In a constant declaration such as the following:
@ -750,7 +866,7 @@ as EnumSecond has no explicit type, and thus defaults to int.`,
Since: "2019.1",
},
"SA9005": &lint.Documentation{
"SA9005": {
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

View File

@ -1,25 +0,0 @@
package staticcheck
import (
"reflect"
"golang.org/x/tools/go/analysis"
"honnef.co/go/tools/internal/passes/buildssa"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/staticcheck/vrp"
)
var valueRangesAnalyzer = &analysis.Analyzer{
Name: "vrp",
Doc: "calculate value ranges of functions",
Run: func(pass *analysis.Pass) (interface{}, error) {
m := map[*ssa.Function]vrp.Ranges{}
for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
vr := vrp.BuildGraph(ssafn).Solve()
m[ssafn] = vr
}
return m, nil
},
Requires: []*analysis.Analyzer{buildssa.Analyzer},
ResultType: reflect.TypeOf(map[*ssa.Function]vrp.Ranges{}),
}

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,8 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
. "honnef.co/go/tools/lint/lintdsl"
"honnef.co/go/tools/ssa"
"honnef.co/go/tools/staticcheck/vrp"
"honnef.co/go/tools/code"
"honnef.co/go/tools/ir"
)
const (
@ -27,10 +26,10 @@ const (
type Call struct {
Pass *analysis.Pass
Instr ssa.CallInstruction
Instr ir.CallInstruction
Args []*Argument
Parent *ssa.Function
Parent *ir.Function
invalids []string
}
@ -44,22 +43,21 @@ type Argument struct {
invalids []string
}
type Value struct {
Value ir.Value
}
func (arg *Argument) Invalid(msg string) {
arg.invalids = append(arg.invalids, msg)
}
type Value struct {
Value ssa.Value
Range vrp.Range
}
type CallCheck func(call *Call)
func extractConsts(v ssa.Value) []*ssa.Const {
func extractConsts(v ir.Value) []*ir.Const {
switch v := v.(type) {
case *ssa.Const:
return []*ssa.Const{v}
case *ssa.MakeInterface:
case *ir.Const:
return []*ir.Const{v}
case *ir.MakeInterface:
return extractConsts(v.X)
default:
return nil
@ -118,20 +116,6 @@ func ValidateURL(v Value) error {
return nil
}
func IntValue(v Value, z vrp.Z) bool {
r, ok := v.Range.(vrp.IntInterval)
if !ok || !r.IsKnown() {
return false
}
if r.Lower != r.Upper {
return false
}
if r.Lower.Cmp(z) == 0 {
return true
}
return false
}
func InvalidUTF8(v Value) bool {
for _, c := range extractConsts(v.Value) {
if c.Value == nil {
@ -149,13 +133,21 @@ func InvalidUTF8(v Value) bool {
}
func UnbufferedChannel(v Value) bool {
r, ok := v.Range.(vrp.ChannelInterval)
if !ok || !r.IsKnown() {
// 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 r.Size.Lower.Cmp(vrp.NewZ(0)) == 0 &&
r.Size.Upper.Cmp(vrp.NewZ(0)) == 0 {
return true
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
}
@ -169,7 +161,7 @@ func Pointer(v Value) bool {
}
func ConvertedFromInt(v Value) bool {
conv, ok := v.Value.(*ssa.Convert)
conv, ok := v.Value.(*ir.Convert)
if !ok {
return false
}
@ -193,7 +185,7 @@ func validEncodingBinaryType(pass *analysis.Pass, typ types.Type) bool {
types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid:
return true
case types.Bool:
return IsGoVersion(pass, 8)
return code.IsGoVersion(pass, 8)
}
return false
case *types.Struct:
@ -232,8 +224,10 @@ func CanBinaryMarshal(pass *analysis.Pass, v Value) bool {
func RepeatZeroTimes(name string, arg int) CallCheck {
return func(call *Call) {
arg := call.Args[arg]
if IntValue(arg.Value, vrp.NewZ(0)) {
arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name))
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))
}
}
}
}
@ -293,8 +287,8 @@ func ValidHostPort(v Value) bool {
// ConvertedFrom reports whether value v was converted from type typ.
func ConvertedFrom(v Value, typ string) bool {
change, ok := v.Value.(*ssa.ChangeType)
return ok && IsType(change.X.Type(), typ)
change, ok := v.Value.(*ir.ChangeType)
return ok && code.IsType(change.X.Type(), typ)
}
func UniqueStringCutset(v Value) bool {

View File

@ -1,73 +0,0 @@
package vrp
import (
"fmt"
"honnef.co/go/tools/ssa"
)
type ChannelInterval struct {
Size IntInterval
}
func (c ChannelInterval) Union(other Range) Range {
i, ok := other.(ChannelInterval)
if !ok {
i = ChannelInterval{EmptyIntInterval}
}
if c.Size.Empty() || !c.Size.IsKnown() {
return i
}
if i.Size.Empty() || !i.Size.IsKnown() {
return c
}
return ChannelInterval{
Size: c.Size.Union(i.Size).(IntInterval),
}
}
func (c ChannelInterval) String() string {
return c.Size.String()
}
func (c ChannelInterval) IsKnown() bool {
return c.Size.IsKnown()
}
type MakeChannelConstraint struct {
aConstraint
Buffer ssa.Value
}
type ChannelChangeTypeConstraint struct {
aConstraint
X ssa.Value
}
func NewMakeChannelConstraint(buffer, y ssa.Value) Constraint {
return &MakeChannelConstraint{NewConstraint(y), buffer}
}
func NewChannelChangeTypeConstraint(x, y ssa.Value) Constraint {
return &ChannelChangeTypeConstraint{NewConstraint(y), x}
}
func (c *MakeChannelConstraint) Operands() []ssa.Value { return []ssa.Value{c.Buffer} }
func (c *ChannelChangeTypeConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *MakeChannelConstraint) String() string {
return fmt.Sprintf("%s = make(chan, %s)", c.Y().Name(), c.Buffer.Name())
}
func (c *ChannelChangeTypeConstraint) String() string {
return fmt.Sprintf("%s = changetype(%s)", c.Y().Name(), c.X.Name())
}
func (c *MakeChannelConstraint) Eval(g *Graph) Range {
i, ok := g.Range(c.Buffer).(IntInterval)
if !ok {
return ChannelInterval{NewIntInterval(NewZ(0), PInfinity)}
}
if i.Lower.Sign() == -1 {
i.Lower = NewZ(0)
}
return ChannelInterval{i}
}
func (c *ChannelChangeTypeConstraint) Eval(g *Graph) Range { return g.Range(c.X) }

View File

@ -1,476 +0,0 @@
package vrp
import (
"fmt"
"go/token"
"go/types"
"math/big"
"honnef.co/go/tools/ssa"
)
type Zs []Z
func (zs Zs) Len() int {
return len(zs)
}
func (zs Zs) Less(i int, j int) bool {
return zs[i].Cmp(zs[j]) == -1
}
func (zs Zs) Swap(i int, j int) {
zs[i], zs[j] = zs[j], zs[i]
}
type Z struct {
infinity int8
integer *big.Int
}
func NewZ(n int64) Z {
return NewBigZ(big.NewInt(n))
}
func NewBigZ(n *big.Int) Z {
return Z{integer: n}
}
func (z1 Z) Infinite() bool {
return z1.infinity != 0
}
func (z1 Z) Add(z2 Z) Z {
if z2.Sign() == -1 {
return z1.Sub(z2.Negate())
}
if z1 == NInfinity {
return NInfinity
}
if z1 == PInfinity {
return PInfinity
}
if z2 == PInfinity {
return PInfinity
}
if !z1.Infinite() && !z2.Infinite() {
n := &big.Int{}
n.Add(z1.integer, z2.integer)
return NewBigZ(n)
}
panic(fmt.Sprintf("%s + %s is not defined", z1, z2))
}
func (z1 Z) Sub(z2 Z) Z {
if z2.Sign() == -1 {
return z1.Add(z2.Negate())
}
if !z1.Infinite() && !z2.Infinite() {
n := &big.Int{}
n.Sub(z1.integer, z2.integer)
return NewBigZ(n)
}
if z1 != PInfinity && z2 == PInfinity {
return NInfinity
}
if z1.Infinite() && !z2.Infinite() {
return Z{infinity: z1.infinity}
}
if z1 == PInfinity && z2 == PInfinity {
return PInfinity
}
panic(fmt.Sprintf("%s - %s is not defined", z1, z2))
}
func (z1 Z) Mul(z2 Z) Z {
if (z1.integer != nil && z1.integer.Sign() == 0) ||
(z2.integer != nil && z2.integer.Sign() == 0) {
return NewBigZ(&big.Int{})
}
if z1.infinity != 0 || z2.infinity != 0 {
return Z{infinity: int8(z1.Sign() * z2.Sign())}
}
n := &big.Int{}
n.Mul(z1.integer, z2.integer)
return NewBigZ(n)
}
func (z1 Z) Negate() Z {
if z1.infinity == 1 {
return NInfinity
}
if z1.infinity == -1 {
return PInfinity
}
n := &big.Int{}
n.Neg(z1.integer)
return NewBigZ(n)
}
func (z1 Z) Sign() int {
if z1.infinity != 0 {
return int(z1.infinity)
}
return z1.integer.Sign()
}
func (z1 Z) String() string {
if z1 == NInfinity {
return "-∞"
}
if z1 == PInfinity {
return "∞"
}
return fmt.Sprintf("%d", z1.integer)
}
func (z1 Z) Cmp(z2 Z) int {
if z1.infinity == z2.infinity && z1.infinity != 0 {
return 0
}
if z1 == PInfinity {
return 1
}
if z1 == NInfinity {
return -1
}
if z2 == NInfinity {
return 1
}
if z2 == PInfinity {
return -1
}
return z1.integer.Cmp(z2.integer)
}
func MaxZ(zs ...Z) Z {
if len(zs) == 0 {
panic("Max called with no arguments")
}
if len(zs) == 1 {
return zs[0]
}
ret := zs[0]
for _, z := range zs[1:] {
if z.Cmp(ret) == 1 {
ret = z
}
}
return ret
}
func MinZ(zs ...Z) Z {
if len(zs) == 0 {
panic("Min called with no arguments")
}
if len(zs) == 1 {
return zs[0]
}
ret := zs[0]
for _, z := range zs[1:] {
if z.Cmp(ret) == -1 {
ret = z
}
}
return ret
}
var NInfinity = Z{infinity: -1}
var PInfinity = Z{infinity: 1}
var EmptyIntInterval = IntInterval{true, PInfinity, NInfinity}
func InfinityFor(v ssa.Value) IntInterval {
if b, ok := v.Type().Underlying().(*types.Basic); ok {
if (b.Info() & types.IsUnsigned) != 0 {
return NewIntInterval(NewZ(0), PInfinity)
}
}
return NewIntInterval(NInfinity, PInfinity)
}
type IntInterval struct {
known bool
Lower Z
Upper Z
}
func NewIntInterval(l, u Z) IntInterval {
if u.Cmp(l) == -1 {
return EmptyIntInterval
}
return IntInterval{known: true, Lower: l, Upper: u}
}
func (i IntInterval) IsKnown() bool {
return i.known
}
func (i IntInterval) Empty() bool {
return i.Lower == PInfinity && i.Upper == NInfinity
}
func (i IntInterval) IsMaxRange() bool {
return i.Lower == NInfinity && i.Upper == PInfinity
}
func (i1 IntInterval) Intersection(i2 IntInterval) IntInterval {
if !i1.IsKnown() {
return i2
}
if !i2.IsKnown() {
return i1
}
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
i3 := NewIntInterval(MaxZ(i1.Lower, i2.Lower), MinZ(i1.Upper, i2.Upper))
if i3.Lower.Cmp(i3.Upper) == 1 {
return EmptyIntInterval
}
return i3
}
func (i1 IntInterval) Union(other Range) Range {
i2, ok := other.(IntInterval)
if !ok {
i2 = EmptyIntInterval
}
if i1.Empty() || !i1.IsKnown() {
return i2
}
if i2.Empty() || !i2.IsKnown() {
return i1
}
return NewIntInterval(MinZ(i1.Lower, i2.Lower), MaxZ(i1.Upper, i2.Upper))
}
func (i1 IntInterval) Add(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
return NewIntInterval(l1.Add(l2), u1.Add(u2))
}
func (i1 IntInterval) Sub(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
return NewIntInterval(l1.Sub(u2), u1.Sub(l2))
}
func (i1 IntInterval) Mul(i2 IntInterval) IntInterval {
if i1.Empty() || i2.Empty() {
return EmptyIntInterval
}
x1, x2 := i1.Lower, i1.Upper
y1, y2 := i2.Lower, i2.Upper
return NewIntInterval(
MinZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
MaxZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
)
}
func (i1 IntInterval) String() string {
if !i1.IsKnown() {
return "[⊥, ⊥]"
}
if i1.Empty() {
return "{}"
}
return fmt.Sprintf("[%s, %s]", i1.Lower, i1.Upper)
}
type IntArithmeticConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
Op token.Token
Fn func(IntInterval, IntInterval) IntInterval
}
type IntAddConstraint struct{ *IntArithmeticConstraint }
type IntSubConstraint struct{ *IntArithmeticConstraint }
type IntMulConstraint struct{ *IntArithmeticConstraint }
type IntConversionConstraint struct {
aConstraint
X ssa.Value
}
type IntIntersectionConstraint struct {
aConstraint
ranges Ranges
A ssa.Value
B ssa.Value
Op token.Token
I IntInterval
resolved bool
}
type IntIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewIntArithmeticConstraint(a, b, y ssa.Value, op token.Token, fn func(IntInterval, IntInterval) IntInterval) *IntArithmeticConstraint {
return &IntArithmeticConstraint{NewConstraint(y), a, b, op, fn}
}
func NewIntAddConstraint(a, b, y ssa.Value) Constraint {
return &IntAddConstraint{NewIntArithmeticConstraint(a, b, y, token.ADD, IntInterval.Add)}
}
func NewIntSubConstraint(a, b, y ssa.Value) Constraint {
return &IntSubConstraint{NewIntArithmeticConstraint(a, b, y, token.SUB, IntInterval.Sub)}
}
func NewIntMulConstraint(a, b, y ssa.Value) Constraint {
return &IntMulConstraint{NewIntArithmeticConstraint(a, b, y, token.MUL, IntInterval.Mul)}
}
func NewIntConversionConstraint(x, y ssa.Value) Constraint {
return &IntConversionConstraint{NewConstraint(y), x}
}
func NewIntIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
return &IntIntersectionConstraint{
aConstraint: NewConstraint(y),
ranges: ranges,
A: a,
B: b,
Op: op,
}
}
func NewIntIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &IntIntervalConstraint{NewConstraint(y), i}
}
func (c *IntArithmeticConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *IntConversionConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *IntIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
func (s *IntIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *IntArithmeticConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s", c.Y().Name(), c.A.Name(), c.Op, c.B.Name())
}
func (c *IntConversionConstraint) String() string {
return fmt.Sprintf("%s = %s(%s)", c.Y().Name(), c.Y().Type(), c.X.Name())
}
func (c *IntIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
}
func (c *IntIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *IntArithmeticConstraint) Eval(g *Graph) Range {
i1, i2 := g.Range(c.A).(IntInterval), g.Range(c.B).(IntInterval)
if !i1.IsKnown() || !i2.IsKnown() {
return IntInterval{}
}
return c.Fn(i1, i2)
}
func (c *IntConversionConstraint) Eval(g *Graph) Range {
s := &types.StdSizes{
// XXX is it okay to assume the largest word size, or do we
// need to be platform specific?
WordSize: 8,
MaxAlign: 1,
}
fromI := g.Range(c.X).(IntInterval)
toI := g.Range(c.Y()).(IntInterval)
fromT := c.X.Type().Underlying().(*types.Basic)
toT := c.Y().Type().Underlying().(*types.Basic)
fromB := s.Sizeof(c.X.Type())
toB := s.Sizeof(c.Y().Type())
if !fromI.IsKnown() {
return toI
}
if !toI.IsKnown() {
return fromI
}
// uint<N> -> sint/uint<M>, M > N: [max(0, l1), min(2**N-1, u2)]
if (fromT.Info()&types.IsUnsigned != 0) &&
toB > fromB {
n := big.NewInt(1)
n.Lsh(n, uint(fromB*8))
n.Sub(n, big.NewInt(1))
return NewIntInterval(
MaxZ(NewZ(0), fromI.Lower),
MinZ(NewBigZ(n), toI.Upper),
)
}
// sint<N> -> sint<M>, M > N; [max(-∞, l1), min(2**N-1, u2)]
if (fromT.Info()&types.IsUnsigned == 0) &&
(toT.Info()&types.IsUnsigned == 0) &&
toB > fromB {
n := big.NewInt(1)
n.Lsh(n, uint(fromB*8))
n.Sub(n, big.NewInt(1))
return NewIntInterval(
MaxZ(NInfinity, fromI.Lower),
MinZ(NewBigZ(n), toI.Upper),
)
}
return fromI
}
func (c *IntIntersectionConstraint) Eval(g *Graph) Range {
xi := g.Range(c.A).(IntInterval)
if !xi.IsKnown() {
return c.I
}
return xi.Intersection(c.I)
}
func (c *IntIntervalConstraint) Eval(*Graph) Range { return c.I }
func (c *IntIntersectionConstraint) Futures() []ssa.Value {
return []ssa.Value{c.B}
}
func (c *IntIntersectionConstraint) Resolve() {
r, ok := c.ranges[c.B].(IntInterval)
if !ok {
c.I = InfinityFor(c.Y())
return
}
switch c.Op {
case token.EQL:
c.I = r
case token.GTR:
c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
case token.GEQ:
c.I = NewIntInterval(r.Lower, PInfinity)
case token.LSS:
// TODO(dh): do we need 0 instead of NInfinity for uints?
c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
case token.LEQ:
c.I = NewIntInterval(NInfinity, r.Upper)
case token.NEQ:
c.I = InfinityFor(c.Y())
default:
panic("unsupported op " + c.Op.String())
}
}
func (c *IntIntersectionConstraint) IsKnown() bool {
return c.I.IsKnown()
}
func (c *IntIntersectionConstraint) MarkUnresolved() {
c.resolved = false
}
func (c *IntIntersectionConstraint) MarkResolved() {
c.resolved = true
}
func (c *IntIntersectionConstraint) IsResolved() bool {
return c.resolved
}

View File

@ -1,273 +0,0 @@
package vrp
// TODO(dh): most of the constraints have implementations identical to
// that of strings. Consider reusing them.
import (
"fmt"
"go/types"
"honnef.co/go/tools/ssa"
)
type SliceInterval struct {
Length IntInterval
}
func (s SliceInterval) Union(other Range) Range {
i, ok := other.(SliceInterval)
if !ok {
i = SliceInterval{EmptyIntInterval}
}
if s.Length.Empty() || !s.Length.IsKnown() {
return i
}
if i.Length.Empty() || !i.Length.IsKnown() {
return s
}
return SliceInterval{
Length: s.Length.Union(i.Length).(IntInterval),
}
}
func (s SliceInterval) String() string { return s.Length.String() }
func (s SliceInterval) IsKnown() bool { return s.Length.IsKnown() }
type SliceAppendConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
}
type SliceSliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type ArraySliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type SliceIntersectionConstraint struct {
aConstraint
X ssa.Value
I IntInterval
}
type SliceLengthConstraint struct {
aConstraint
X ssa.Value
}
type MakeSliceConstraint struct {
aConstraint
Size ssa.Value
}
type SliceIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewSliceAppendConstraint(a, b, y ssa.Value) Constraint {
return &SliceAppendConstraint{NewConstraint(y), a, b}
}
func NewSliceSliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &SliceSliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewArraySliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &ArraySliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewSliceIntersectionConstraint(x ssa.Value, i IntInterval, y ssa.Value) Constraint {
return &SliceIntersectionConstraint{NewConstraint(y), x, i}
}
func NewSliceLengthConstraint(x, y ssa.Value) Constraint {
return &SliceLengthConstraint{NewConstraint(y), x}
}
func NewMakeSliceConstraint(size, y ssa.Value) Constraint {
return &MakeSliceConstraint{NewConstraint(y), size}
}
func NewSliceIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &SliceIntervalConstraint{NewConstraint(y), i}
}
func (c *SliceAppendConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *SliceSliceConstraint) Operands() []ssa.Value {
ops := []ssa.Value{c.X}
if c.Lower != nil {
ops = append(ops, c.Lower)
}
if c.Upper != nil {
ops = append(ops, c.Upper)
}
return ops
}
func (c *ArraySliceConstraint) Operands() []ssa.Value {
ops := []ssa.Value{c.X}
if c.Lower != nil {
ops = append(ops, c.Lower)
}
if c.Upper != nil {
ops = append(ops, c.Upper)
}
return ops
}
func (c *SliceIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *SliceLengthConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (c *MakeSliceConstraint) Operands() []ssa.Value { return []ssa.Value{c.Size} }
func (s *SliceIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *SliceAppendConstraint) String() string {
return fmt.Sprintf("%s = append(%s, %s)", c.Y().Name(), c.A.Name(), c.B.Name())
}
func (c *SliceSliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *ArraySliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *SliceIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s.%t ⊓ %s", c.Y().Name(), c.X.Name(), c.Y().(*ssa.Sigma).Branch, c.I)
}
func (c *SliceLengthConstraint) String() string {
return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}
func (c *MakeSliceConstraint) String() string {
return fmt.Sprintf("%s = make(slice, %s)", c.Y().Name(), c.Size.Name())
}
func (c *SliceIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *SliceAppendConstraint) Eval(g *Graph) Range {
l1 := g.Range(c.A).(SliceInterval).Length
var l2 IntInterval
switch r := g.Range(c.B).(type) {
case SliceInterval:
l2 = r.Length
case StringInterval:
l2 = r.Length
default:
return SliceInterval{}
}
if !l1.IsKnown() || !l2.IsKnown() {
return SliceInterval{}
}
return SliceInterval{
Length: l1.Add(l2),
}
}
func (c *SliceSliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
ur := g.Range(c.X).(SliceInterval).Length
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return SliceInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return SliceInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *ArraySliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
var l int64
switch typ := c.X.Type().(type) {
case *types.Array:
l = typ.Len()
case *types.Pointer:
l = typ.Elem().(*types.Array).Len()
}
ur := NewIntInterval(NewZ(l), NewZ(l))
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return SliceInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return SliceInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *SliceIntersectionConstraint) Eval(g *Graph) Range {
xi := g.Range(c.X).(SliceInterval)
if !xi.IsKnown() {
return c.I
}
return SliceInterval{
Length: xi.Length.Intersection(c.I),
}
}
func (c *SliceLengthConstraint) Eval(g *Graph) Range {
i := g.Range(c.X).(SliceInterval).Length
if !i.IsKnown() {
return NewIntInterval(NewZ(0), PInfinity)
}
return i
}
func (c *MakeSliceConstraint) Eval(g *Graph) Range {
i, ok := g.Range(c.Size).(IntInterval)
if !ok {
return SliceInterval{NewIntInterval(NewZ(0), PInfinity)}
}
if i.Lower.Sign() == -1 {
i.Lower = NewZ(0)
}
return SliceInterval{i}
}
func (c *SliceIntervalConstraint) Eval(*Graph) Range { return SliceInterval{c.I} }

View File

@ -1,258 +0,0 @@
package vrp
import (
"fmt"
"go/token"
"go/types"
"honnef.co/go/tools/ssa"
)
type StringInterval struct {
Length IntInterval
}
func (s StringInterval) Union(other Range) Range {
i, ok := other.(StringInterval)
if !ok {
i = StringInterval{EmptyIntInterval}
}
if s.Length.Empty() || !s.Length.IsKnown() {
return i
}
if i.Length.Empty() || !i.Length.IsKnown() {
return s
}
return StringInterval{
Length: s.Length.Union(i.Length).(IntInterval),
}
}
func (s StringInterval) String() string {
return s.Length.String()
}
func (s StringInterval) IsKnown() bool {
return s.Length.IsKnown()
}
type StringSliceConstraint struct {
aConstraint
X ssa.Value
Lower ssa.Value
Upper ssa.Value
}
type StringIntersectionConstraint struct {
aConstraint
ranges Ranges
A ssa.Value
B ssa.Value
Op token.Token
I IntInterval
resolved bool
}
type StringConcatConstraint struct {
aConstraint
A ssa.Value
B ssa.Value
}
type StringLengthConstraint struct {
aConstraint
X ssa.Value
}
type StringIntervalConstraint struct {
aConstraint
I IntInterval
}
func NewStringSliceConstraint(x, lower, upper, y ssa.Value) Constraint {
return &StringSliceConstraint{NewConstraint(y), x, lower, upper}
}
func NewStringIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
return &StringIntersectionConstraint{
aConstraint: NewConstraint(y),
ranges: ranges,
A: a,
B: b,
Op: op,
}
}
func NewStringConcatConstraint(a, b, y ssa.Value) Constraint {
return &StringConcatConstraint{NewConstraint(y), a, b}
}
func NewStringLengthConstraint(x ssa.Value, y ssa.Value) Constraint {
return &StringLengthConstraint{NewConstraint(y), x}
}
func NewStringIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
return &StringIntervalConstraint{NewConstraint(y), i}
}
func (c *StringSliceConstraint) Operands() []ssa.Value {
vs := []ssa.Value{c.X}
if c.Lower != nil {
vs = append(vs, c.Lower)
}
if c.Upper != nil {
vs = append(vs, c.Upper)
}
return vs
}
func (c *StringIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
func (c StringConcatConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
func (c *StringLengthConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
func (s *StringIntervalConstraint) Operands() []ssa.Value { return nil }
func (c *StringSliceConstraint) String() string {
var lname, uname string
if c.Lower != nil {
lname = c.Lower.Name()
}
if c.Upper != nil {
uname = c.Upper.Name()
}
return fmt.Sprintf("%s[%s:%s]", c.X.Name(), lname, uname)
}
func (c *StringIntersectionConstraint) String() string {
return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
}
func (c StringConcatConstraint) String() string {
return fmt.Sprintf("%s = %s + %s", c.Y().Name(), c.A.Name(), c.B.Name())
}
func (c *StringLengthConstraint) String() string {
return fmt.Sprintf("%s = len(%s)", c.Y().Name(), c.X.Name())
}
func (c *StringIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
func (c *StringSliceConstraint) Eval(g *Graph) Range {
lr := NewIntInterval(NewZ(0), NewZ(0))
if c.Lower != nil {
lr = g.Range(c.Lower).(IntInterval)
}
ur := g.Range(c.X).(StringInterval).Length
if c.Upper != nil {
ur = g.Range(c.Upper).(IntInterval)
}
if !lr.IsKnown() || !ur.IsKnown() {
return StringInterval{}
}
ls := []Z{
ur.Lower.Sub(lr.Lower),
ur.Upper.Sub(lr.Lower),
ur.Lower.Sub(lr.Upper),
ur.Upper.Sub(lr.Upper),
}
// TODO(dh): if we don't truncate lengths to 0 we might be able to
// easily detect slices with high < low. we'd need to treat -∞
// specially, though.
for i, l := range ls {
if l.Sign() == -1 {
ls[i] = NewZ(0)
}
}
return StringInterval{
Length: NewIntInterval(MinZ(ls...), MaxZ(ls...)),
}
}
func (c *StringIntersectionConstraint) Eval(g *Graph) Range {
var l IntInterval
switch r := g.Range(c.A).(type) {
case StringInterval:
l = r.Length
case IntInterval:
l = r
}
if !l.IsKnown() {
return StringInterval{c.I}
}
return StringInterval{
Length: l.Intersection(c.I),
}
}
func (c StringConcatConstraint) Eval(g *Graph) Range {
i1, i2 := g.Range(c.A).(StringInterval), g.Range(c.B).(StringInterval)
if !i1.Length.IsKnown() || !i2.Length.IsKnown() {
return StringInterval{}
}
return StringInterval{
Length: i1.Length.Add(i2.Length),
}
}
func (c *StringLengthConstraint) Eval(g *Graph) Range {
i := g.Range(c.X).(StringInterval).Length
if !i.IsKnown() {
return NewIntInterval(NewZ(0), PInfinity)
}
return i
}
func (c *StringIntervalConstraint) Eval(*Graph) Range { return StringInterval{c.I} }
func (c *StringIntersectionConstraint) Futures() []ssa.Value {
return []ssa.Value{c.B}
}
func (c *StringIntersectionConstraint) Resolve() {
if (c.A.Type().Underlying().(*types.Basic).Info() & types.IsString) != 0 {
// comparing two strings
r, ok := c.ranges[c.B].(StringInterval)
if !ok {
c.I = NewIntInterval(NewZ(0), PInfinity)
return
}
switch c.Op {
case token.EQL:
c.I = r.Length
case token.GTR, token.GEQ:
c.I = NewIntInterval(r.Length.Lower, PInfinity)
case token.LSS, token.LEQ:
c.I = NewIntInterval(NewZ(0), r.Length.Upper)
case token.NEQ:
default:
panic("unsupported op " + c.Op.String())
}
} else {
r, ok := c.ranges[c.B].(IntInterval)
if !ok {
c.I = NewIntInterval(NewZ(0), PInfinity)
return
}
// comparing two lengths
switch c.Op {
case token.EQL:
c.I = r
case token.GTR:
c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
case token.GEQ:
c.I = NewIntInterval(r.Lower, PInfinity)
case token.LSS:
c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
case token.LEQ:
c.I = NewIntInterval(NInfinity, r.Upper)
case token.NEQ:
default:
panic("unsupported op " + c.Op.String())
}
}
}
func (c *StringIntersectionConstraint) IsKnown() bool {
return c.I.IsKnown()
}
func (c *StringIntersectionConstraint) MarkUnresolved() {
c.resolved = false
}
func (c *StringIntersectionConstraint) MarkResolved() {
c.resolved = true
}
func (c *StringIntersectionConstraint) IsResolved() bool {
return c.resolved
}

File diff suppressed because it is too large Load Diff