├── .gitignore ├── LICENSE ├── Makefile ├── README.markdown ├── _examples ├── store.generated.go └── store.go ├── codegen.go ├── interface.go ├── main.go ├── media └── logo.png ├── strings.go ├── strings_test.go └── wanted.go /.gitignore: -------------------------------------------------------------------------------- 1 | go-interface-fuzzer 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Pusher 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all default fmt build test clean 2 | 3 | all: default 4 | 5 | default: fmt build test 6 | 7 | fmt: 8 | go fmt ./... 9 | 10 | build: go-interface-fuzzer 11 | 12 | test: 13 | go test ./... 14 | 15 | clean: 16 | rm go-interface-fuzzer 17 | 18 | go-interface-fuzzer: *.go 19 | go build -o $@ 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | Go Interface Fuzzer is a fuzzy testing tool for Go interfaces used [@Pusher](https://github.com/pusher). The 7 | goal of the project is to make it easier for developers to have 8 | confidence in the correctness of their programs by combining 9 | randomised testing with reference semantics. 10 | 11 | Given an interface, a reference implementation, and some hints on how 12 | to generate function parameters and compare function return values, 13 | Go Interface Fuzzer will generate testing functions which can be 14 | used to check that the behaviour of an arbitrary other type 15 | implementing the interface behaves the same. 16 | 17 | See the [`_examples` directory](https://github.com/pusher/go-interface-fuzzer/tree/master/_examples) for a complete self-contained example. 18 | 19 | ## Table of Contents 20 | 21 | - [Project Status](#project-status) 22 | - [Getting Started](#getting-started) 23 | - [Installing](#installing) 24 | - [Usage](#usage) 25 | - [Incorporating into the build](#incorporating-into-the-build) 26 | - [Directives](#directives) 27 | - [`@fuzz interface` (required)](#fuzz-interface-required) 28 | - [`@known correct` (required)](#known-correct-required) 29 | - [`@invariant`](#invariant) 30 | - [`@comparison`](#comparison) 31 | - [`@generator state`](#generator-state) 32 | - [Defaults](#defaults) 33 | - [Other Uses](#other-uses) 34 | - [Regression testing](#regression-testing) 35 | - [Assertion-only testing](#assertion-only-testing) 36 | 37 | ## Project Status 38 | 39 | The tool is stable and the interface fixed. New functionality, 40 | directives, and command-line arguments may be added, but existing 41 | usages will not suddenly stop working. 42 | 43 | ## Getting Started 44 | 45 | ### Installing 46 | 47 | To start using Go Interface Fuzzer, install Go and run `go get`: 48 | 49 | ```sh 50 | $ go get github.com/pusher/go-interface-fuzzer 51 | ``` 52 | 53 | This will install the `go-interface-fuzzer` command-line tool into 54 | your `$GOBIN` path, which defaults to `$GOPATH/bin`. 55 | 56 | 57 | ### Usage 58 | 59 | The generated code in the `_examples` directory is produced by 60 | 61 | ```bash 62 | go-interface-fuzzer -c -o -f _examples/store.generated.go _examples/store.go 63 | ``` 64 | 65 | - The `-c` flag generates a **c**omplete source file, complete with 66 | package name and imports. 67 | - The `-o` flag writes the **o**utput to the filename given by the 68 | `-f` flag. 69 | - The `-f` flag specifies the **f**ilename to use when writing output 70 | and resolving imports. 71 | 72 | The generated code can be customised further, see the full help text 73 | (`go-interface-fuzzer --help`) for a complete flag listing. 74 | 75 | The tool generates three functions, named after the interface used. 76 | With the example file, the following functions are be produced: 77 | 78 | - `FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error` 79 | 80 | Create a new reference store and test store, apply a 81 | randomly-generated list of actions, and bail out on inconsistency. 82 | 83 | - `FuzzStore(makeTest (func(int) Store), rand *rand.Rand, maxops uint) error` 84 | 85 | Call `FuzzStoreWithReference` with the ModelStore as the reference 86 | one. 87 | 88 | - `FuzzTestStore(makeTest (func(int) Store), t *testing.T)` 89 | 90 | A test case parameterised by the store generating function, with a 91 | default maxops of 100. 92 | 93 | By default Go Interface Fuzzer generates an incomplete fragment: no 94 | package name, no imports, just the three testing functions per 95 | interface. 96 | 97 | 98 | #### Incorporating into the build 99 | 100 | Go makes adding a code generation stage to your build process quite 101 | simple, with the `go generate` tool. To incorporate into your build, 102 | add a comment to your source file: 103 | 104 | ```go 105 | //go:generate go-interface-fuzzer -c -o -f output_file.go input_file.go 106 | ``` 107 | 108 | Typically this would be added to the same file which defines the 109 | interface and provides the processing directives (see the next 110 | section), but that isn't required. 111 | 112 | To then actually generate the file, run `go generate`. It is not done 113 | for you as part of `go build`. 114 | 115 | For further information on code generation in Go see 116 | "[Generating code](https://blog.golang.org/generate)", on the Go blog. 117 | 118 | 119 | ### Directives 120 | 121 | An interface must be marked-up with some processing directives to 122 | direct the tool. These directives are given inside a single multi-line 123 | comment. 124 | 125 | The minimum is just indicating that a fuzz tester should be generated 126 | for the interface, and how to produce a new value of the reference 127 | implementation type. For example: 128 | 129 | ```go 130 | /* 131 | @fuzz interface: Store 132 | @known correct: makeReferenceStore int 133 | */ 134 | type Store interface { 135 | // ... 136 | } 137 | ``` 138 | 139 | The fuzzer definition does not need to be immediately next to the 140 | interface, it can be anywhere in the source file. 141 | 142 | See the `_examples` directory for a complete self-contained example 143 | using most of the directives. 144 | 145 | 146 | #### `@fuzz interface` (required) 147 | 148 | This directive begins the definition of a fuzzer, and gives the name 149 | of the interface to test. 150 | 151 | **Example:** `@fuzz interface: Store` 152 | 153 | **Argument syntax:** `InterfaceName` 154 | 155 | 156 | #### `@known correct` (required) 157 | 158 | This directive gives a function to produce a new value of the 159 | reference implementation. It specifies the parameters of the function, 160 | and whether the return type is a value or a pointer type. 161 | 162 | The generated fuzzing function will expect a function argument with 163 | the same parameters to create a new value of the type under test. 164 | 165 | **Example:** `@known correct: makeReferenceStore int` 166 | 167 | **Argument syntax:** `[&] FunctionName [ArgType1 ... ArgTypeN]` 168 | 169 | The presence of a `&` means that this returns a value rather than a 170 | pointer, and so a reference must be made to it. 171 | 172 | 173 | #### `@invariant` 174 | 175 | This directive specifies a property that must always hold. It is only 176 | checked for the test implementation, not the reference implementation. 177 | 178 | **Example:** `@invariant: %var.NumEntries() == len(%var.AsSlice())` 179 | 180 | **Argument syntax:** `Expression` 181 | 182 | The argument is a Go expression that evaluates to a boolean, with 183 | `%var` replaced with the variable name. 184 | 185 | 186 | #### `@comparison` 187 | 188 | This directive specifies a function to use to compare two values. If 189 | not specified the reflection package is used. 190 | 191 | **Example:** `@comparison: *MessageIterator:CompareWith` 192 | 193 | **Argument syntax:** `(Type:FunctionName | FunctionName Type)` 194 | 195 | In the method form, the target of the comparison is passed as the sole 196 | parameter; in the function form both are passed as parameters. 197 | 198 | 199 | #### `@generator` 200 | 201 | This directive specifies a function to generate a value of the 202 | required type. It is passed a PRNG of type `*rand.Rand`. If no 203 | generator for a type is specified, the tool will attempt to produce a 204 | default; and report an error otherwise. 205 | 206 | **Example:** `@generator: GenerateChannel model.Channel` 207 | 208 | **Argument syntax:** `[!] FunctionName Type` 209 | 210 | The presence of a `!` means that this is a stateful function: it is 211 | also passed a state parameter and is expected to return a new state as 212 | its second result. 213 | 214 | 215 | #### `@generator state` 216 | 217 | This directive supplies an initial state for stateful generators. It 218 | must be given if any generators are stateful. The initial state is any 219 | legal Go expression; it is just copied verbatim into the generated 220 | code. 221 | 222 | **Example:** `@generator state: InitialGeneratorState` 223 | 224 | **Argument syntax:** `Expression` 225 | 226 | 227 | ### Defaults 228 | 229 | The following default **comparison** operations are used if not 230 | overridden: 231 | 232 | | Type | Comparison | 233 | |-----------------|----------------------------------------------| 234 | | `error` | Equal if both values are `nil` or non-`nil`. | 235 | | Everything else | `reflect.DeepEqual` | 236 | 237 | The following default **generator** functions are used if not 238 | overridden: 239 | 240 | | Type | Generator | 241 | |-----------------|---------------------------------------------------------------------| 242 | | `bool` | `rand.Intn(2) == 0` | 243 | | `byte` | `byte(rand.Uint32())` | 244 | | `complex64` | `complex(float32(rand.NormFloat64()), float32(rand.NormFloat64()))` | 245 | | `complex128` | `complex(rand.NormFloat64(), rand.NormFloat64())` | 246 | | `float32` | `float32(rand.NormFloat64())` | 247 | | `float64` | `rand.NormFloat64()` | 248 | | `int` | `rand.Int()` | 249 | | `int8` | `int8(rand.Int())` | 250 | | `int16` | `int16(rand.Int())` | 251 | | `int32` | `rand.Int31()` | 252 | | `int64` | `rand.Int63()` | 253 | | `rune` | `rune(rand.Int31())` | 254 | | `uint` | `uint(rand.Uint32())` | 255 | | `uint8` | `uint8(rand.Uint32())` | 256 | | `uint16` | `uint16(rand.Uint32())` | 257 | | `uint32` | `rand.Uint32()` | 258 | | `uint64` | `(uint64(rand.Uint32()) << 32) | uint64(rand.Uint32())` | 259 | | Everything else | **No default** | 260 | 261 | 262 | ## Other Uses 263 | ### Regression testing 264 | 265 | Although the motivating use-case for Go Interface Fuzzer was an 266 | interface with two implementations that should have identical 267 | behaviour, there is nothing preventing the use of multiple *versions* 268 | of the *same* implementation. This facilitates regression testing, 269 | although at the cost of needing to keep the old implementation around. 270 | 271 | Here are the concrete steps you would need to follow to do this: 272 | 273 | 1. Make a copy of your current implementation, with a new name. 274 | 2. Write a new implementation. 275 | 3. Use the current implementation as the reference implementation for 276 | the generated fuzzer. 277 | 278 | This isn't quite the same as reference correctness, as any bugs in the 279 | old implementation which the new fixes will be reported as an error in 280 | the new implementation. 281 | 282 | 283 | ### Assertion-only testing 284 | 285 | By supplying the same implementation as both the reference and the 286 | test implementation, the fuzz tester will simply check invariants. 287 | Although, it'll be a little slow, because every operation is being 288 | performed twice. 289 | 290 | In the future, there will probably be an "invariant-only" mode of 291 | operation. 292 | -------------------------------------------------------------------------------- /_examples/store.generated.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | // Store 12 | 13 | func FuzzTestStore(makeTest func(int) Store, t *testing.T) { 14 | rand := rand.New(rand.NewSource(0)) 15 | 16 | err := FuzzStore(makeTest, rand, 100) 17 | 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | } 22 | 23 | func FuzzStore(makeTest func(int) Store, rand *rand.Rand, max uint) error { 24 | var ( 25 | argInt int 26 | ) 27 | 28 | argInt = rand.Int() 29 | 30 | expectedStore := makeReferenceStore(argInt) 31 | actualStore := makeTest(argInt) 32 | 33 | return FuzzStoreWith(&expectedStore, actualStore, rand, max) 34 | } 35 | 36 | func FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error { 37 | // Create initial state 38 | state := uint(0) 39 | 40 | for i := uint(0); i < maxops; i++ { 41 | // Pick a random number between 0 and the number of methods of the interface. Then do that method on 42 | // both, check for discrepancy, and bail out on error. Simple! 43 | 44 | actionToPerform := rand.Intn(6) 45 | 46 | switch actionToPerform { 47 | case 0: 48 | // Call the method on both implementations 49 | var ( 50 | argMessage Message 51 | ) 52 | 53 | argMessage, state = generateMessage(rand, state) 54 | 55 | expectedError := reference.Put(argMessage) 56 | actualError := test.Put(argMessage) 57 | 58 | // And check for discrepancies. 59 | if !((expectedError == nil) == (actualError == nil)) { 60 | return fmt.Errorf("inconsistent result in Put\nexpected: %v\nactual: %v", expectedError, actualError) 61 | } 62 | case 1: 63 | // Call the method on both implementations 64 | var ( 65 | argID ID 66 | argChannel Channel 67 | ) 68 | 69 | argID, state = generateID(rand, state) 70 | argChannel = generateChannel(rand) 71 | 72 | expectedID, expectedMessage := reference.EntriesSince(argID, argChannel) 73 | actualID, actualMessage := test.EntriesSince(argID, argChannel) 74 | 75 | // And check for discrepancies. 76 | if !reflect.DeepEqual(expectedID, actualID) { 77 | return fmt.Errorf("inconsistent result in EntriesSince\nexpected: %v\nactual: %v", expectedID, actualID) 78 | } 79 | if !reflect.DeepEqual(expectedMessage, actualMessage) { 80 | return fmt.Errorf("inconsistent result in EntriesSince\nexpected: %v\nactual: %v", expectedMessage, actualMessage) 81 | } 82 | case 2: 83 | // Call the method on both implementations 84 | expectedID := reference.MostRecentID() 85 | actualID := test.MostRecentID() 86 | 87 | // And check for discrepancies. 88 | if !reflect.DeepEqual(expectedID, actualID) { 89 | return fmt.Errorf("inconsistent result in MostRecentID\nexpected: %v\nactual: %v", expectedID, actualID) 90 | } 91 | case 3: 92 | // Call the method on both implementations 93 | expectedInt := reference.NumEntries() 94 | actualInt := test.NumEntries() 95 | 96 | // And check for discrepancies. 97 | if !reflect.DeepEqual(expectedInt, actualInt) { 98 | return fmt.Errorf("inconsistent result in NumEntries\nexpected: %v\nactual: %v", expectedInt, actualInt) 99 | } 100 | case 4: 101 | // Call the method on both implementations 102 | expectedMessage := reference.AsSlice() 103 | actualMessage := test.AsSlice() 104 | 105 | // And check for discrepancies. 106 | if !reflect.DeepEqual(expectedMessage, actualMessage) { 107 | return fmt.Errorf("inconsistent result in AsSlice\nexpected: %v\nactual: %v", expectedMessage, actualMessage) 108 | } 109 | case 5: 110 | // Call the method on both implementations 111 | expectedInt := reference.MessageLimit() 112 | actualInt := test.MessageLimit() 113 | 114 | // And check for discrepancies. 115 | if !reflect.DeepEqual(expectedInt, actualInt) { 116 | return fmt.Errorf("inconsistent result in MessageLimit\nexpected: %v\nactual: %v", expectedInt, actualInt) 117 | } 118 | } 119 | 120 | if !(reference.NumEntries() == len(reference.AsSlice())) { 121 | return errors.New("invariant violated: %var.NumEntries() == len(%var.AsSlice())") 122 | } 123 | 124 | if !(reference.NumEntries() <= reference.MessageLimit()) { 125 | return errors.New("invariant violated: %var.NumEntries() <= %var.MessageLimit()") 126 | } 127 | 128 | } 129 | 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /_examples/store.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | A very simple message store example: messages are sorted into 4 | "channels", and have an associated "ID". A message cannot directly be 5 | retrieved, but all messages since an ID can be retrieved: either as an 6 | iterator or as a slice. 7 | 8 | This assumes a type called ReferenceStore and a function 9 | NewReferenceStore are in scope. 10 | 11 | */ 12 | 13 | package example 14 | 15 | import ( 16 | "math/rand" 17 | "reflect" 18 | ) 19 | 20 | /* 21 | @fuzz interface: Store 22 | 23 | @known correct: & makeReferenceStore int 24 | 25 | @invariant: %var.NumEntries() == len(%var.AsSlice()) 26 | @invariant: %var.NumEntries() <= %var.MessageLimit() 27 | 28 | @generator state: uint(0) 29 | 30 | @generator: generateChannel Channel 31 | @generator: ! generateID ID 32 | @generator: ! generateMessage Message 33 | */ 34 | type Store interface { 35 | // Inserts an entry in the store. Returns an error if an entry with greater or 36 | // equal ID was already inserted. 37 | Put(msg Message) error 38 | 39 | // Returns a slice of all messages in the specified channels, from the 40 | // specified ID to the message with most recent ID. All messages will have IDs 41 | // such that `sinceID < ID <= mostRecentID`. 42 | EntriesSince(sinceID ID, channel Channel) (ID, []Message) 43 | 44 | // Returns the ID of the most recently inserted message. 45 | MostRecentID() ID 46 | 47 | // Returns the number of messages in the store. 48 | NumEntries() int 49 | 50 | // Returns all messages across all channels as a single slice, 51 | // sorted by ID. 52 | AsSlice() []Message 53 | 54 | // Returns the maximum number of messages in the store. 55 | MessageLimit() int 56 | } 57 | 58 | type Message struct { 59 | // Each message has a unique ID. 60 | ID ID 61 | 62 | // A message belongs to a specific channel. 63 | Channel Channel 64 | 65 | // And has a body 66 | Body string 67 | } 68 | 69 | type Channel string 70 | 71 | type ID uint64 72 | 73 | // Create a new clean ReferenceStore for testing purposes. 74 | func makeReferenceStore(capacity int) ReferenceStore { 75 | return NewReferenceStore(capacity, []Message{}) 76 | } 77 | 78 | // Generate a channel name. Use a short string. 79 | func generateChannel(rand *rand.Rand) Channel { 80 | return Channel(randomString(rand, 1+rand.Intn(4))) 81 | } 82 | 83 | // Generate an ID. Randomly generate some across the full range of 84 | // rand.Int. Sometimes use only those that have been seen before. This 85 | // is so that EntriesSince and EntriesSinceIter have a good chance of 86 | // actually returning something (whilst not disregarding the "error" 87 | // case where the ID is invalid). 88 | func generateID(rand *rand.Rand, maxSoFar uint) (ID, uint) { 89 | newid := uint(rand.Uint32()) % 64 90 | 91 | if rand.Intn(2) == 0 { 92 | newid = uint(rand.Uint32()) % maxSoFar 93 | } 94 | 95 | return ID(newid), maxSoFar 96 | } 97 | 98 | // Generate an ID message. Randomly generate some. Use a 99 | // monotonically-increasing ID for others. This is because Put 100 | // requires that, so if IDs were totally random, this wouldn't be 101 | // likely to produce particularly good results. 102 | func generateMessage(rand *rand.Rand, maxSoFar uint) (Message, uint) { 103 | newid := uint(rand.Uint32()) % 64 104 | 105 | msg := Message{ 106 | Channel: generateChannel(rand), 107 | Data: MessageData(randomString(rand, 1+rand.Intn(4))), 108 | } 109 | 110 | if rand.Intn(2) == 0 { 111 | newid = maxSoFar + 1 112 | } 113 | 114 | if newid > maxSoFar { 115 | maxSoFar = newid 116 | } 117 | 118 | return Message{ID: ID(newid), Message: msg}, maxSoFar 119 | } 120 | 121 | // Generate a random string 122 | func randomString(rand *rand.Rand, n int) string { 123 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 124 | b := make([]rune, n) 125 | for i := range b { 126 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 127 | } 128 | return string(b) 129 | } 130 | -------------------------------------------------------------------------------- /codegen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "go/ast" 8 | "strconv" 9 | "strings" 10 | "text/template" 11 | "unicode" 12 | 13 | goimports "golang.org/x/tools/imports" 14 | ) 15 | 16 | // CodeGenOptions specifies the behaviour for the top-level code 17 | // generator 18 | type CodeGenOptions struct { 19 | // The filename to use when automatically resolving 20 | // imports. If unset by the command-line arguments, defaults 21 | // to the filename of the source file. 22 | Filename string 23 | 24 | // The package to use in the generated output. If unset by the 25 | // command-line arguments, defaults to the package of the 26 | // source file. 27 | PackageName string 28 | 29 | // Generate a complete source file, with package name and imports. 30 | Complete bool 31 | 32 | // Avoid generating the FuzzTest...(..., *testing.T) function. 33 | NoTestCase bool 34 | 35 | // Avoid generating the Fuzz...(..., *rand.Rand, uint) 36 | // function. This implies NoTestCase. 37 | NoDefaultFuzz bool 38 | } 39 | 40 | // Fuzzer is a pair of an interface declaration and a description of 41 | // how to generate the fuzzer. 42 | type Fuzzer struct { 43 | Name string 44 | Methods []Function 45 | Wanted WantedFuzzer 46 | } 47 | 48 | var ( 49 | // Default generators for builtin types. If there is no entry 50 | // for the desired type, an error is signalled. 51 | defaultGenerators = map[string]string{ 52 | "bool": "rand.Intn(2) == 0", 53 | "byte": "byte(rand.Uint32())", 54 | "complex64": "complex(float32(rand.NormFloat64()), float32(rand.NormFloat64()))", 55 | "complex128": "complex(rand.NormFloat64(), rand.NormFloat64())", 56 | "float32": "float32(rand.NormFloat64())", 57 | "float64": "rand.NormFloat64()", 58 | "int": "rand.Int()", 59 | "int8": "int8(rand.Int())", 60 | "int16": "int16(rand.Int())", 61 | "int32": "rand.Int31()", 62 | "int64": "rand.Int63()", 63 | "rune": "rune(rand.Int31())", 64 | "uint": "uint(rand.Uint32())", 65 | "uint8": "uint8(rand.Uint32())", 66 | "uint16": "uint16(rand.Uint32())", 67 | "uint32": "rand.Uint32()", 68 | "uint64": "(uint64(rand.Uint32()) << 32) | uint64(rand.Uint32())", 69 | } 70 | 71 | // Default comparisons for builtin types. If there is no entry 72 | // for the desired type, 'fallbackComparison' is used. 73 | defaultComparisons = map[string]string{ 74 | "error": "((%s == nil) == (%s == nil))", 75 | } 76 | 77 | // Fallback comparison if there is nothing in 'defaultComparisons'. 78 | fallbackComparison = "reflect.DeepEqual(%s, %s)" 79 | ) 80 | 81 | // All of the templates take a Fuzzer as the argument. 82 | const ( 83 | // Template used by CodegenTestCase. 84 | testCaseTemplate = ` 85 | {{$name := .Name}} 86 | {{$args := argV .Wanted.Reference.Parameters}} 87 | 88 | func FuzzTest{{$name}}(makeTest func({{$args}}) {{$name}}, t *testing.T) { 89 | rand := rand.New(rand.NewSource(0)) 90 | 91 | err := Fuzz{{$name}}(makeTest, rand, 100) 92 | 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | }` 97 | 98 | // Template used by CodegenWithDefaultReference 99 | withDefaultReferenceTemplate = ` 100 | {{$name := .Name}} 101 | {{$args := argV .Wanted.Reference.Parameters}} 102 | {{$decls := makeFunCalls . .Wanted.Reference .Wanted.Reference.Name "makeTest"}} 103 | {{$and := eitherOr .Wanted.ReturnsValue "&" ""}} 104 | 105 | func Fuzz{{$name}}(makeTest func ({{$args}}) {{$name}}, rand *rand.Rand, max uint) error { 106 | {{indent $decls "\t"}} 107 | 108 | return Fuzz{{$name}}With({{$and}}expected{{$name}}, actual{{$name}}, rand, max) 109 | }` 110 | 111 | // Template used by CodegenWithReference 112 | withReferenceTemplate = ` 113 | {{$fuzzer := .}} 114 | {{$name := .Name}} 115 | {{$count := len .Methods}} 116 | {{$state := .Wanted.GeneratorState}} 117 | 118 | func Fuzz{{$name}}With(reference {{$name}}, test {{$name}}, rand *rand.Rand, maxops uint) error { 119 | {{if $state | eq ""}}{{else}} // Create initial state 120 | state := {{$state}} 121 | 122 | {{end}} for i := uint(0); i < maxops; i++ { 123 | // Pick a random number between 0 and the number of methods of the interface. Then do that method on 124 | // both, check for discrepancy, and bail out on error. Simple! 125 | 126 | actionToPerform := rand.Intn({{$count}}) 127 | 128 | switch actionToPerform { {{range $i, $function := .Methods}} 129 | case {{$i}}: 130 | // Call the method on both implementations 131 | {{indent (makeFunCalls $fuzzer $function (printf "reference.%s" $function.Name) (printf "test.%s" $function.Name)) "\t\t\t"}} 132 | 133 | // And check for discrepancies.{{range $j, $ty := $function.Returns}}{{$expected := expected $function $j}}{{$actual := actual $function $j}} 134 | if !{{printf (comparison $fuzzer $ty) $expected $actual}} { 135 | return fmt.Errorf("inconsistent result in {{$function.Name}}\nexpected: %v\nactual: %v", {{$expected}}, {{$actual}}) 136 | }{{end}}{{end}} 137 | } {{range $i, $invariant := .Wanted.Invariants}} 138 | 139 | if !({{sed $invariant "%var" "reference"}}) { 140 | return errors.New("invariant violated: {{$invariant}}") 141 | } 142 | {{end}} 143 | } 144 | 145 | return nil 146 | }` 147 | 148 | // Template used by MakeFunctionCalls. 149 | functionCallTemplate = ` 150 | {{$fuzzer := . }} 151 | {{$function := function ""}} 152 | {{$expecteds := expecteds $function}} 153 | {{$actuals := actuals $function}} 154 | {{$arguments := arguments $function}} 155 | {{$expectedFunc := expectedFunc ""}} 156 | {{$actualFunc := actualFunc ""}} 157 | 158 | {{if len $arguments | ne 0}} 159 | var ({{range $i, $ty := $function.Parameters}} 160 | {{argument $function $i}} {{toString $ty}}{{end}} 161 | ) 162 | {{range $i, $ty := $function.Parameters}} 163 | {{makeTyGen $fuzzer (argument $function $i) $ty}}{{end}}{{end}} 164 | 165 | {{if len $expecteds | eq 0}} 166 | {{$expectedFunc}}({{varV $arguments}}) 167 | {{$actualFunc}}({{varV $arguments}}) 168 | {{else}} 169 | {{varV $expecteds}} := {{$expectedFunc}}({{varV $arguments}}) 170 | {{varV $actuals}} := {{$actualFunc}}({{varV $arguments}}) 171 | {{end}}` 172 | ) 173 | 174 | /// ENTRY POINT 175 | 176 | // CodeGen generate code for the fuzzers. 177 | func CodeGen(options CodeGenOptions, imports []*ast.ImportSpec, fuzzers []Fuzzer) (string, []error) { 178 | var code string 179 | var errs []error 180 | 181 | if options.Complete { 182 | code = generatePreamble(options.PackageName, imports) 183 | } 184 | 185 | codeGenErr := func(fuzzer Fuzzer, err error) error { 186 | return fmt.Errorf("error occurred whilst generating code for '%s': %s", fuzzer.Name, err) 187 | } 188 | 189 | for _, fuzzer := range fuzzers { 190 | code = code + "// " + fuzzer.Name + "\n\n" 191 | 192 | // FuzzTest...(... *testing.T) 193 | if !(options.NoTestCase || options.NoDefaultFuzz) { 194 | generated, err := CodegenTestCase(fuzzer) 195 | if err != nil { 196 | errs = append(errs, codeGenErr(fuzzer, err)) 197 | continue 198 | } 199 | code = code + generated + "\n\n" 200 | } 201 | 202 | // Fuzz...(... *rand.Rand, uint) 203 | if !options.NoDefaultFuzz { 204 | generated, err := CodegenWithDefaultReference(fuzzer) 205 | if err != nil { 206 | errs = append(errs, codeGenErr(fuzzer, err)) 207 | continue 208 | } 209 | code = code + generated + "\n\n" 210 | } 211 | 212 | generated, err := CodegenWithReference(fuzzer) 213 | if err != nil { 214 | errs = append(errs, codeGenErr(fuzzer, err)) 215 | continue 216 | } 217 | code = code + generated + "\n\n" 218 | } 219 | 220 | code, err := fixImports(options, code) 221 | if err != nil { 222 | errs = append(errs, err) 223 | } 224 | 225 | return code, errs 226 | } 227 | 228 | // Generates the header for a complete source file: the package name 229 | // and the imports. These imports may be both overzealous (all imports 230 | // from the source file are copied across) and incomplete (imports the 231 | // generated functions pull in aren't added), so the FixImports 232 | // function must be called after the full code has been generated to 233 | // fix this up. 234 | func generatePreamble(packagename string, imports []*ast.ImportSpec) string { 235 | preamble := "package " + packagename + "\n\n" 236 | 237 | for _, iport := range imports { 238 | preamble = preamble + generateImport(iport) + "\n" 239 | } 240 | 241 | return preamble + "\n" 242 | } 243 | 244 | // Generates an import statement from an *ast.ImportSpec. 245 | func generateImport(iport *ast.ImportSpec) string { 246 | if iport.Name != nil { 247 | return "import " + iport.Name.Name + " " + iport.Path.Value 248 | } 249 | return "import " + iport.Path.Value 250 | } 251 | 252 | // Adds and removes imports with 'goimports'. 253 | func fixImports(options CodeGenOptions, code string) (string, error) { 254 | if options.Complete { 255 | cbytes, err := goimports.Process(options.Filename, []byte(code), nil) 256 | return string(cbytes), err 257 | } 258 | return code, nil 259 | } 260 | 261 | /// MAIN CODE GENERATORS 262 | 263 | // CodegenTestCase generates a function which can be used as a test 264 | // case when given an implementation to test. 265 | // 266 | // For an interface named `Store` with a generating function that 267 | // takes a single `int`, the generated function signature looks like 268 | // this: 269 | // 270 | // FuzzTestStore(makeTest (func(int) Store), t *testing.T) 271 | // 272 | // This test case will call `FuzzStore` (see 273 | // CodegenWithDefaultReference) with a max number of 100 operations. 274 | func CodegenTestCase(fuzzer Fuzzer) (string, error) { 275 | return runTemplate("testCase", testCaseTemplate, fuzzer) 276 | } 277 | 278 | // CodegenWithDefaultReference generates a function which will compare 279 | // a supplied implementation of the interface against the reference, 280 | // by performing a sequence of random operations. 281 | // 282 | // For an interface named `Store` with a generating function that 283 | // takes a single `int`, the generated function signature looks like 284 | // this: 285 | // 286 | // FuzzStore(makeTest (func(int) Store), rand *rand.Rand, maxops uint) error 287 | // 288 | // This function will call `FuzzStoreWith` (see CodegenWithReference) 289 | // with the default reference. 290 | func CodegenWithDefaultReference(fuzzer Fuzzer) (string, error) { 291 | return runTemplate("withDefaultReference", withDefaultReferenceTemplate, fuzzer) 292 | } 293 | 294 | // CodegenWithReference generates a function which will compare two 295 | // arbitrary implementations of the supplied interface, by performing 296 | // a sequence of random operations. 297 | // 298 | // For an interface named `Store`, the generated function signature 299 | // looks like this: 300 | // 301 | // FuzzStoreWith(reference Store, test Store, rand *rand.Rand, maxops uint) error 302 | // 303 | // In any found discrepancies, the return value from the reference 304 | // `Store` (the first parameter) will be displayed as the "expected" 305 | // output, and the other as the "actual". 306 | func CodegenWithReference(fuzzer Fuzzer) (string, error) { 307 | return runTemplate("withReference", withReferenceTemplate, fuzzer) 308 | } 309 | 310 | /// FUNCTION CALLS 311 | 312 | // Generate a call to two functions with the same signature, with 313 | // random argument values. 314 | // 315 | // Arguments are stored in variables arg0 ... argN. Return values in 316 | // variables reta0 ... retaN and retb0 ... retbN. 317 | func makeFunctionCalls(fuzzer Fuzzer, function Function, funcA, funcB string) (string, error) { 318 | funcs := template.FuncMap{ 319 | "function": func(s string) Function { return function }, 320 | "expectedFunc": func(s string) string { return funcA }, 321 | "actualFunc": func(s string) string { return funcB }, 322 | } 323 | 324 | return runTemplateWith("functionCall", functionCallTemplate, fuzzer, funcs) 325 | } 326 | 327 | /// VALUE INITIALISATION 328 | 329 | // Produce some code to populate a given variable with a random value 330 | // of the named type, assuming a PRNG called 'rand' is in scope. 331 | func makeTypeGenerator(fuzzer Fuzzer, varname string, ty Type) (string, error) { 332 | tyname := ty.ToString() 333 | 334 | // If there's a provided generator, use that. 335 | generator, ok := fuzzer.Wanted.Generator[tyname] 336 | if ok { 337 | if generator.IsStateful { 338 | if fuzzer.Wanted.GeneratorState == "" { 339 | return "", errors.New("stateful generator used when no initial state given") 340 | } 341 | return fmt.Sprintf("%s, state = %s(rand, state)", varname, generator.Name), nil 342 | } 343 | return fmt.Sprintf("%s = %s(rand)", varname, generator.Name), nil 344 | } 345 | 346 | // If it's a type we can handle, supply a default generator. 347 | var tygen string 348 | tygen, ok = defaultGenerators[tyname] 349 | if ok { 350 | return fmt.Sprintf("%s = %s", varname, tygen), nil 351 | } 352 | 353 | // Otherwise cry because generic programming in Go is hard :( 354 | return "", fmt.Errorf("I don't know how to generate a %s", tyname) 355 | } 356 | 357 | /// VALUE COMPARISON 358 | 359 | // Produce a format string to compare two values of the same type. 360 | // given the variable names. 361 | func makeValueComparison(fuzzer Fuzzer, ty Type) string { 362 | tyname := ty.ToString() 363 | comparison, ok := defaultComparisons[tyname] 364 | if !ok { 365 | comparison = fallbackComparison 366 | } 367 | 368 | // If there's a provided comparison, use that. 369 | tycomp, ok := fuzzer.Wanted.Comparison[tyname] 370 | if ok { 371 | comparison = "%s." + tycomp.Name + "(%s)" 372 | 373 | if tycomp.IsFunction { 374 | comparison = tycomp.Name + "(%s, %s)" 375 | } 376 | } 377 | 378 | return comparison 379 | } 380 | 381 | /// TEMPLATES 382 | 383 | // Run a template and return the output. 384 | func runTemplate(tplName, tpl string, fuzzer Fuzzer) (string, error) { 385 | return runTemplateWith(tplName, tpl, fuzzer, nil) 386 | } 387 | 388 | // Run a template and return the output, overriding the built-in 389 | // template functions with a custom map (which can also add new 390 | // functions). 391 | func runTemplateWith(tplName, tpl string, fuzzer Fuzzer, overrides template.FuncMap) (string, error) { 392 | funcMap := template.FuncMap{ 393 | // Render a list of types 394 | "argV": func(types []Type) string { 395 | var args []string 396 | 397 | for _, ty := range types { 398 | args = append(args, ty.ToString()) 399 | } 400 | 401 | return strings.Join(args, ", ") 402 | }, 403 | // Render a list of variables 404 | "varV": func(vars []string) string { 405 | return strings.Join(vars, ", ") 406 | }, 407 | // Select one of two values based on a flag 408 | "eitherOr": func(f bool, a, b string) string { 409 | if f { 410 | return a 411 | } 412 | return b 413 | }, 414 | // Indent every line of a string 415 | "indent": indentLines, 416 | // Argument names 417 | "arguments": func(function Function) []string { 418 | return funcArgNames(function) 419 | }, 420 | "argument": func(function Function, i int) (string, error) { 421 | return inSlice(funcArgNames(function), i, "argument") 422 | }, 423 | // Expected value names 424 | "expecteds": func(function Function) []string { return funcExpectedNames(function) }, 425 | "expected": func(function Function, i int) (string, error) { 426 | return inSlice(funcExpectedNames(function), i, "result") 427 | }, 428 | // Actual value names 429 | "actuals": func(function Function) []string { 430 | return funcActualNames(function) 431 | }, 432 | "actual": func(function Function, i int) (string, error) { 433 | return inSlice(funcActualNames(function), i, "result") 434 | }, 435 | // Render a type as a string 436 | "toString": func(ty Type) string { 437 | return ty.ToString() 438 | }, 439 | // Make a function call 440 | "makeFunCalls": makeFunctionCalls, 441 | // Make a value comparison 442 | "comparison": makeValueComparison, 443 | // Make a type generator 444 | "makeTyGen": makeTypeGenerator, 445 | // Replace one string with another 446 | "sed": func(s, old, new string) string { 447 | return strings.Replace(s, old, new, -1) 448 | }, 449 | } 450 | 451 | for k, v := range overrides { 452 | funcMap[k] = v 453 | } 454 | 455 | var buf bytes.Buffer 456 | t, err := template.New(tplName).Funcs(funcMap).Parse(tpl) 457 | if err != nil { 458 | return "", err 459 | } 460 | err = t.Execute(&buf, fuzzer) 461 | return strings.TrimSpace(string(buf.Bytes())), err 462 | } 463 | 464 | // Safe slice lookup 465 | func inSlice(ss []string, i int, name string) (string, error) { 466 | if i < 0 || i >= len(ss) { 467 | return "", errors.New(name + " index out of range") 468 | } 469 | 470 | return ss[i], nil 471 | } 472 | 473 | /// TYPE-DIRECTED VARIABLE NAMING 474 | 475 | // Produce unique variable names for function arguments. These do not 476 | // clash with names produced by funcExpectedNames or funcActualNames. 477 | func funcArgNames(function Function) []string { 478 | return typeListNames("arg", function.Parameters) 479 | } 480 | 481 | // Produce unique variable names for actual function returns. These do 482 | // not clash with names produced by funcArgNames or funcExpectedNames. 483 | func funcActualNames(function Function) []string { 484 | return typeListNames("actual", function.Returns) 485 | } 486 | 487 | // Produce unique variable names for expected function returns. These 488 | // do not clash with names produced by funcArgNames or 489 | // funcActualNames. 490 | func funcExpectedNames(function Function) []string { 491 | return typeListNames("expected", function.Returns) 492 | } 493 | 494 | // Produce names for variables given a list of types. 495 | func typeListNames(prefix string, tylist []Type) []string { 496 | var names []string 497 | 498 | for i, ty := range tylist { 499 | // Generate a name for this variable based on the type. 500 | name := typeNameToVarName(prefix, ty) 501 | for _, prior := range names { 502 | if name == prior { 503 | name = name + strconv.Itoa(i) 504 | break 505 | } 506 | } 507 | names = append(names, name) 508 | } 509 | 510 | return names 511 | } 512 | 513 | // Produce a (possibly not unique) variable name from a type name. 514 | func typeNameToVarName(pref string, ty Type) string { 515 | name := filter(ty.ToString(), unicode.IsLetter) 516 | 517 | // More pleasing capitalisation. 518 | for i, r := range name { 519 | name = string(unicode.ToUpper(r)) + name[i+1:] 520 | break 521 | } 522 | 523 | return pref + name 524 | } 525 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | // Parse interface declarations. 2 | // 3 | // For all of the InterfaceFrom* functions, if the parameter is not an 4 | // interface type, the default interface is returned and the error is 5 | // non-nil. Similarly for the FunctionFrom* functions. 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "go/ast" 12 | ) 13 | 14 | // A Function is a representation of a function name and type, which 15 | // may be a member of an interface or not. All Functions in an 16 | // Interface will be members. 17 | type Function struct { 18 | // The name of the function. 19 | Name string 20 | 21 | // The parameter types 22 | Parameters []Type 23 | 24 | // The output types 25 | Returns []Type 26 | } 27 | 28 | // Type is a representation of a Go type. The concrete types are 29 | // ArrayType, BasicType, ChanType, MapType, PointerType, and 30 | // QualifiedType. 31 | type Type interface { 32 | // Return an unambiguous string rendition of the type. 33 | ToString() string 34 | } 35 | 36 | // ArrayType is the type of arrays. 37 | type ArrayType struct { 38 | // The element type 39 | ElementType Type 40 | } 41 | 42 | // ToString converts an ArrayType into a string of the form 43 | // "[](type)". 44 | func (ty *ArrayType) ToString() string { 45 | if ty == nil { 46 | return "" 47 | } 48 | 49 | tystr := fmt.Sprintf("[](%s)", ty.ElementType.ToString()) 50 | return tystr 51 | } 52 | 53 | // BasicType is simple named types with no additional structure. 54 | type BasicType string 55 | 56 | // ToString just exposes the underlying type name. 57 | func (ty *BasicType) ToString() string { 58 | if ty == nil { 59 | return "" 60 | } 61 | 62 | return string(*ty) 63 | } 64 | 65 | // ChanType is the type of channels. 66 | type ChanType struct { 67 | // The element type. 68 | ElementType Type 69 | } 70 | 71 | // ToString converts a ChanType into a string of the form "chan 72 | // (type)". 73 | func (ty *ChanType) ToString() string { 74 | if ty == nil { 75 | return "" 76 | } 77 | 78 | tystr := fmt.Sprintf("chan (%s)", ty.ElementType.ToString()) 79 | return tystr 80 | } 81 | 82 | // MapType is the type of maps. 83 | type MapType struct { 84 | // The key type 85 | KeyType Type 86 | // The value type. 87 | ValueType Type 88 | } 89 | 90 | // ToString converts a MapType into a string of the form 91 | // "map[type](type)". 92 | func (ty *MapType) ToString() string { 93 | if ty == nil { 94 | return "" 95 | } 96 | 97 | tystr := fmt.Sprintf("map[%s](%s)", ty.KeyType.ToString(), ty.ValueType.ToString()) 98 | return tystr 99 | } 100 | 101 | // PointerType is the type of pointers. 102 | type PointerType struct { 103 | // The target type. 104 | TargetType Type 105 | } 106 | 107 | // QualifiedType is the type of types with a package name qualfiier. 108 | type QualifiedType struct { 109 | // The package name 110 | Package string 111 | // The type 112 | Type Type 113 | } 114 | 115 | // ToString converts a QualifiedType into a string of the form 116 | // "package.type". 117 | func (ty *QualifiedType) ToString() string { 118 | if ty == nil { 119 | return "" 120 | } 121 | 122 | tystr := fmt.Sprintf("%s.%s", ty.Package, ty.Type.ToString()) 123 | return tystr 124 | } 125 | 126 | // ToString converts a PointerType into a string of the form 127 | // "*(type)". 128 | func (ty *PointerType) ToString() string { 129 | if ty == nil { 130 | return "" 131 | } 132 | 133 | tystr := fmt.Sprintf("*(%s)", ty.TargetType.ToString()) 134 | return tystr 135 | } 136 | 137 | // InterfacesFromAST extracts all interface declarations from the AST 138 | // of a file, as a map from names to interface decls. 139 | func InterfacesFromAST(theAST ast.Node) map[string][]Function { 140 | if theAST == nil { 141 | return nil 142 | } 143 | 144 | interfaces := make(map[string][]Function) 145 | 146 | ast.Inspect(theAST, func(node ast.Node) bool { 147 | switch tyspec := node.(type) { 148 | case *ast.TypeSpec: 149 | name := tyspec.Name.Name 150 | switch ifacety := tyspec.Type.(type) { 151 | case *ast.InterfaceType: 152 | functions, err := FunctionsFromInterfaceType(*ifacety) 153 | if err == nil { 154 | interfaces[name] = functions 155 | } 156 | } 157 | 158 | // Whether we found one or not, there can be 159 | // no interfaces at a lower level than this. 160 | return false 161 | } 162 | 163 | // Maybe we just haven't recursed deeply 164 | // enough. Onwards! 165 | return true 166 | }) 167 | 168 | return interfaces 169 | } 170 | 171 | // FunctionsFromInterfaceType tries to extract function declarations 172 | // from an ast.InterfaceType. 173 | func FunctionsFromInterfaceType(ifacety ast.InterfaceType) ([]Function, error) { 174 | var functions []Function 175 | for _, field := range ifacety.Methods.List { 176 | if len(field.Names) == 0 { 177 | continue 178 | } 179 | 180 | // Can there be more than one name? 181 | name := field.Names[0].Name 182 | obj := field.Names[0].Obj 183 | 184 | if obj == nil || obj.Decl == nil { 185 | continue 186 | } 187 | 188 | switch fundecl := obj.Decl.(type) { 189 | case *ast.Field: 190 | ast.Inspect(fundecl, func(node ast.Node) bool { 191 | switch funty := node.(type) { 192 | case *ast.FuncType: 193 | function := Function{Name: name} 194 | if funty.Params != nil { 195 | function.Parameters = TypeListFromFieldList(*funty.Params) 196 | } 197 | if funty.Results != nil { 198 | function.Returns = TypeListFromFieldList(*funty.Results) 199 | } 200 | functions = append(functions, function) 201 | return false 202 | } 203 | 204 | return true 205 | }) 206 | } 207 | } 208 | 209 | return functions, nil 210 | } 211 | 212 | // TypeListFromFieldList gets the list of type names from an 213 | // ast.FieldList. Names are not returned. 214 | func TypeListFromFieldList(fields ast.FieldList) []Type { 215 | types := make([]Type, len(fields.List)) 216 | 217 | for i, field := range fields.List { 218 | types[i] = TypeFromTypeExpr(field.Type) 219 | } 220 | 221 | return types 222 | } 223 | 224 | // TypeFromTypeExpr gets a type from an ast.Expr which is known to 225 | // represent a type. 226 | func TypeFromTypeExpr(ty ast.Expr) Type { 227 | switch x := ty.(type) { 228 | case *ast.Ident: 229 | // Type name 230 | ty := BasicType(x.Name) 231 | return &ty 232 | case *ast.ArrayType: 233 | ty := ArrayType{ElementType: TypeFromTypeExpr(x.Elt)} 234 | return &ty 235 | case *ast.ChanType: 236 | ty := ChanType{ElementType: TypeFromTypeExpr(x.Value)} 237 | return &ty 238 | case *ast.MapType: 239 | ty := MapType{KeyType: TypeFromTypeExpr(x.Key), ValueType: TypeFromTypeExpr(x.Value)} 240 | return &ty 241 | case *ast.StarExpr: 242 | ty := PointerType{TargetType: TypeFromTypeExpr(x.X)} 243 | return &ty 244 | case *ast.SelectorExpr: 245 | // x.X is an expression which resolves to the package 246 | // name and x.Sel is the "selector", which is the 247 | // actual type name. 248 | pkg := TypeFromTypeExpr(x.X).ToString() 249 | innerTy := BasicType(x.Sel.Name) 250 | ty := QualifiedType{Package: pkg, Type: &innerTy} 251 | return &ty 252 | } 253 | 254 | return nil 255 | } 256 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/parser" 6 | "go/token" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | // Turn a collection of errors into a single error message with a list 15 | // of errors. 16 | func errorList(message string, errs []error) string { 17 | var errstrs []string 18 | for _, err := range errs { 19 | errstrs = append(errstrs, err.Error()) 20 | } 21 | return (message + ":\n\t- " + strings.Join(errstrs, "\n\t- ")) 22 | } 23 | 24 | // Reconcile the wanted fuzzers with the interfaces. Complain if there 25 | // are any wanted fuzzers for which the interface decl isn't in the 26 | // file. 27 | func reconcileFuzzers(interfaces map[string][]Function, wanteds []WantedFuzzer) ([]Fuzzer, []error) { 28 | var errs []error 29 | 30 | // Fuzzers are stored as a map from interface name to fuzzer. 31 | // This allows rapid checking for duplicates. 32 | fuzzers := make(map[string]Fuzzer) 33 | 34 | for _, wanted := range wanteds { 35 | _, present := fuzzers[wanted.InterfaceName] 36 | if present { 37 | errs = append(errs, fmt.Errorf("already have a fuzzer for '%s'", wanted.InterfaceName)) 38 | continue 39 | } 40 | 41 | methods, ok := interfaces[wanted.InterfaceName] 42 | 43 | if !ok { 44 | errs = append(errs, fmt.Errorf("couldn't find interface '%s' in this file", wanted.InterfaceName)) 45 | } 46 | 47 | fuzzer := Fuzzer{Name: wanted.InterfaceName, Methods: methods, Wanted: wanted} 48 | fuzzers[wanted.InterfaceName] = fuzzer 49 | } 50 | 51 | // Get a slice out of the 'fuzzers' map 52 | realfuzzers := make([]Fuzzer, len(fuzzers)) 53 | i := 0 54 | for _, fuzzer := range fuzzers { 55 | realfuzzers[i] = fuzzer 56 | i++ 57 | } 58 | return realfuzzers, errs 59 | } 60 | 61 | func main() { 62 | var opts CodeGenOptions 63 | var ifaceonly string 64 | var writeout bool 65 | 66 | app := cli.NewApp() 67 | app.Name = "go-interface-fuzzer" 68 | app.Usage = "Generate fuzz tests for Go interfaces." 69 | app.Flags = []cli.Flag{ 70 | cli.BoolFlag{ 71 | Name: "complete, c", 72 | Usage: "Generate a complete source file, with package name and imports", 73 | Destination: &opts.Complete, 74 | }, 75 | cli.StringFlag{ 76 | Name: "filename, f", 77 | Usage: "Use `FILE` as the file name when automatically resolving imports (defaults to the filename of the source file)", 78 | Destination: &opts.Filename, 79 | }, 80 | cli.StringFlag{ 81 | Name: "package, p", 82 | Usage: "Use `NAME` as the package name (defaults to the package of the source file)", 83 | Destination: &opts.PackageName, 84 | }, 85 | cli.BoolFlag{ 86 | Name: "no-test-case, T", 87 | Usage: "Do not generate the TestFuzz... function", 88 | Destination: &opts.NoTestCase, 89 | }, 90 | cli.BoolFlag{ 91 | Name: "no-default, D", 92 | Usage: "Do not generate the Fuzz... function, implies no-test-case", 93 | Destination: &opts.NoDefaultFuzz, 94 | }, 95 | cli.StringFlag{ 96 | Name: "interface", 97 | Usage: "Ignore special comments and just generate a fuzz tester for the named interface, implies no-default", 98 | Destination: &ifaceonly, 99 | }, 100 | cli.BoolFlag{ 101 | Name: "output, o", 102 | Usage: "Write the output to the filename given by the -f flag, which must be specified", 103 | Destination: &writeout, 104 | }, 105 | } 106 | app.Action = func(c *cli.Context) error { 107 | if len(c.Args()) < 1 { 108 | return cli.NewExitError("Must specify a file to generate a fuzzer from.", 1) 109 | } 110 | 111 | filename := c.Args().Get(0) 112 | fset := token.NewFileSet() 113 | parsedFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) 114 | 115 | if err != nil { 116 | return cli.NewExitError(fmt.Sprintf("Could not parse file: '%s'", err.Error()), 1) 117 | } 118 | 119 | // Extract all the interfaces 120 | interfaces := InterfacesFromAST(parsedFile) 121 | 122 | // Extract the wanted fuzzers 123 | var wanteds []WantedFuzzer 124 | var werrs []error 125 | if ifaceonly == "" { 126 | wanteds, werrs = WantedFuzzersFromAST(parsedFile) 127 | } else { 128 | // Default fuzzer for this interface. 129 | wanteds = append(wanteds, WantedFuzzer{InterfaceName: ifaceonly}) 130 | } 131 | if len(werrs) > 0 { 132 | return cli.NewExitError(errorList("Found errors while extracting interface definitions", werrs), 1) 133 | } 134 | 135 | // Reconcile the wanteds with the interfaces. 136 | fuzzers, ferrs := reconcileFuzzers(interfaces, wanteds) 137 | if len(ferrs) > 0 { 138 | return cli.NewExitError(errorList("Found errors while determining wanted fuzz testers", ferrs), 1) 139 | } 140 | 141 | // Codegen 142 | if opts.Filename == "" { 143 | if writeout { 144 | return cli.NewExitError("When using -o a filename MUST be given to -f", 1) 145 | } 146 | opts.Filename = filename 147 | } 148 | if opts.PackageName == "" { 149 | opts.PackageName = parsedFile.Name.Name 150 | } 151 | code, cerrs := CodeGen(opts, parsedFile.Imports, fuzzers) 152 | if len(cerrs) > 0 { 153 | return cli.NewExitError(errorList("Found some errors while generating code", cerrs), 1) 154 | } 155 | 156 | if writeout { 157 | err := ioutil.WriteFile(opts.Filename, []byte(code), 0644) 158 | if err != nil { 159 | return cli.NewExitError(err.Error(), 1) 160 | } 161 | } else { 162 | fmt.Println(code) 163 | } 164 | 165 | return nil 166 | } 167 | 168 | app.Run(os.Args) 169 | } 170 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/go-interface-fuzzer/cebf347f37c499000edc0ccf4093adab119cbc8a/media/logo.png -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | // String operations used elsewhere in the parser. 2 | 3 | package main 4 | 5 | import ( 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | // Drop a prefix and suffix. Complain if the prefix is present but not 11 | // the suffix and vice versa. 12 | func matchDelims(s, pref, suff string) (string, bool) { 13 | s1, prefCount := removePrefix(s, pref) 14 | s2, suffCount := removeSuffix(s1, suff) 15 | 16 | return s2, prefCount == suffCount 17 | } 18 | 19 | // Remove a prefix from a string as many times as possible. 20 | func removePrefix(s, pref string) (string, uint) { 21 | var count uint 22 | for strings.HasPrefix(s, pref) { 23 | s = strings.TrimPrefix(s, pref) 24 | count++ 25 | } 26 | return s, count 27 | } 28 | 29 | // Remove a suffix from a string as many times as possible. 30 | func removeSuffix(s, suff string) (string, uint) { 31 | var count uint 32 | for strings.HasSuffix(s, suff) { 33 | s = strings.TrimSuffix(s, suff) 34 | count++ 35 | } 36 | return s, count 37 | } 38 | 39 | // Split a string into lines. 40 | func splitLines(s string) []string { 41 | return strings.Split(s, "\n") 42 | } 43 | 44 | // Check if a string has a prefix and, if so, return the bit following 45 | // the prefix with whitespace betwen the prefix and the rest trimmed. 46 | func matchPrefix(s, prefix string) (string, bool) { 47 | if strings.HasPrefix(s, prefix) { 48 | after := strings.TrimPrefix(s, prefix) 49 | return strings.TrimLeftFunc(after, unicode.IsSpace), true 50 | } 51 | return s, false 52 | } 53 | 54 | // Return a prefix and a suffix where the prefix contains only allowed 55 | // characters. 56 | func takeWhileIn(s, allowed string) (string, string) { 57 | for i, chr := range s { 58 | if !strings.ContainsRune(allowed, chr) { 59 | return s[:i], s[i:] 60 | } 61 | } 62 | 63 | return s, "" 64 | } 65 | 66 | // Indent every non-blank line by the given number of tabs. 67 | func indentLines(s string, indent string) string { 68 | lines := strings.Split(s, "\n") 69 | indented := indent + strings.Join(lines, "\n"+indent) 70 | return strings.Replace(indented, indent+"\n", "\n", -1) 71 | } 72 | 73 | // Filter runes in a string. Unlike takeWhileIn this processes the 74 | // entire string, not just a prefix. 75 | func filter(s string, allowed func(rune) bool) string { 76 | f := func(r rune) rune { 77 | if allowed(r) { 78 | return r 79 | } 80 | return -1 81 | } 82 | 83 | return strings.Map(f, s) 84 | } 85 | -------------------------------------------------------------------------------- /strings_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "testing/quick" 8 | "unicode" 9 | ) 10 | 11 | // Check that matchDelims can remove delimiters and get back the 12 | // original string. 13 | func TestMatchDelims(t *testing.T) { 14 | f := func(str string, numL uint, numR uint) bool { 15 | numL = numL % 10 16 | numR = numR % 10 17 | 18 | pref := makeFixedString("(", numL) 19 | suff := makeFixedString(")", numR) 20 | 21 | teststr := fmt.Sprintf("%s|%s|%s", pref, str, suff) 22 | 23 | innerstr, balanced := matchDelims(teststr, "(", ")") 24 | 25 | if innerstr != "|"+str+"|" { 26 | expectedActual("Failed at matching delimiters.", "|"+str+"|", innerstr, t) 27 | } 28 | 29 | if (balanced && numL != numR) || (!balanced && numL == numR) { 30 | t.Fatal("Reported delimiters balanced when they are not.") 31 | } 32 | 33 | return true 34 | } 35 | 36 | quickcheck(f, t) 37 | } 38 | 39 | // Check that the result of splitLines can be converted back to the 40 | // original string. 41 | func TestSplitLinesInvertible(t *testing.T) { 42 | f := func(str string) bool { 43 | splitted := splitLines(str) 44 | merged := strings.Join(splitted, "\n") 45 | if str != merged { 46 | expectedActual("Splitting a string by lines is not invertible.", str, merged, t) 47 | } 48 | 49 | return true 50 | } 51 | 52 | quickcheck(f, t) 53 | } 54 | 55 | // Check that the result of splitLines doesn't contain newlines. 56 | func TestSplitLinesNoNewlines(t *testing.T) { 57 | f := func(str string) bool { 58 | splitted := splitLines(str) 59 | for _, line := range splitted { 60 | if strings.Contains(line, "\n") { 61 | chunks := strings.Split(line, "\n") 62 | expectedActual("Newline found in output of string split by lines.", chunks[0], line, t) 63 | } 64 | } 65 | 66 | return true 67 | } 68 | 69 | quickcheck(f, t) 70 | } 71 | 72 | // Check matchPrefix correctly removes the prefix. 73 | func TestMatchPrefix(t *testing.T) { 74 | f := func(pref, suff string) bool { 75 | str := pref + suff 76 | 77 | suff2, ok := matchPrefix(str, pref) 78 | 79 | if !ok || suff2 != suff { 80 | expectedActual("Failed to match prefix.", suff, suff2, t) 81 | } 82 | 83 | return true 84 | } 85 | 86 | quickcheck(f, t) 87 | } 88 | 89 | // Check that takeWhileIn returns a prefix of the original string. 90 | func TestTakeWhileInGivesPrefix(t *testing.T) { 91 | f := func(str, allowed string) bool { 92 | pref, suff := takeWhileIn(str, allowed) 93 | 94 | suff2, ok := matchPrefix(str, pref) 95 | 96 | if !ok || suff != suff2 { 97 | expectedActual("Failed to match prefix given by takeWhileIn.", suff2, suff, t) 98 | } 99 | 100 | return true 101 | } 102 | 103 | quickcheck(f, t) 104 | } 105 | 106 | // Check that takeWhileIn returns a prefix which only contains allowed characters. 107 | func TestTakeWhileInGivesGoodPrefix(t *testing.T) { 108 | f := func(str, allowed string) bool { 109 | pref, _ := takeWhileIn(str, allowed) 110 | 111 | for _, chr := range pref { 112 | if !strings.ContainsRune(allowed, chr) { 113 | t.Fatal("Found invalid character in prefix.") 114 | } 115 | } 116 | 117 | return true 118 | } 119 | 120 | quickcheck(f, t) 121 | } 122 | 123 | // Check that filter doesn't increase the length. 124 | func TestFilterLen(t *testing.T) { 125 | f := func(str string) bool { 126 | return len(str) >= len(filter(str, unicode.IsLetter)) 127 | } 128 | 129 | quickcheck(f, t) 130 | } 131 | 132 | // Check that the result of filter only contains characters matching 133 | // the predicate. 134 | func TestFilterPredicate(t *testing.T) { 135 | f := func(str string) bool { 136 | predicate := unicode.IsLetter 137 | filtered := filter(str, predicate) 138 | 139 | for _, r := range filtered { 140 | if !predicate(r) { 141 | t.Fatal("Found invalid character in filter output.") 142 | } 143 | } 144 | 145 | return true 146 | } 147 | 148 | quickcheck(f, t) 149 | } 150 | 151 | // Check that filtering is idempotent. 152 | func TestFilterIdempotent(t *testing.T) { 153 | f := func(str string) bool { 154 | predicate := unicode.IsLetter 155 | filtered1 := filter(str, predicate) 156 | filtered2 := filter(filtered1, predicate) 157 | 158 | if filtered1 != filtered2 { 159 | expectedActual("Filtering is not idempotent.", filtered1, filtered2, t) 160 | } 161 | 162 | return true 163 | } 164 | 165 | quickcheck(f, t) 166 | } 167 | 168 | // Helper for "expected"/"actual" output. 169 | func expectedActual(msg string, expected interface{}, actual interface{}, t *testing.T) { 170 | t.Fatal(fmt.Sprintf("%s\nGot: %v\nExpected: %v", msg, actual, expected)) 171 | } 172 | 173 | // Helper for quickcheck 174 | func quickcheck(f interface{}, t *testing.T) { 175 | var c quick.Config 176 | c.MaxCount = 10 177 | if err := quick.Check(f, &c); err != nil { 178 | t.Fatal(err) 179 | } 180 | } 181 | 182 | // Helper for making strings of a single character. 183 | func makeFixedString(of string, len uint) (s string) { 184 | var i uint 185 | for i = 0; i < len; i++ { 186 | s = s + of 187 | } 188 | return 189 | } 190 | -------------------------------------------------------------------------------- /wanted.go: -------------------------------------------------------------------------------- 1 | // Parse fuzzer special comments. 2 | // 3 | // For all the WantedFuzzerFrom* functions, if the parameter does not 4 | // contain a fuzzer special comment, the default wanted fuzzer is 5 | // returned and the error is non-nil. 6 | // 7 | // See the README for a comprehensive explanation of the special 8 | // comments. 9 | 10 | package main 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "go/ast" 16 | "strings" 17 | "unicode" 18 | ) 19 | 20 | // WantedFuzzer is a description of a fuzzer we want to generate. 21 | type WantedFuzzer struct { 22 | // The name of the interface. 23 | InterfaceName string 24 | 25 | // The function to produce a reference implementation. 26 | Reference Function 27 | 28 | // If true, the reference function returns a value rather than a 29 | // pointer. 30 | ReturnsValue bool 31 | 32 | // Invariant expressions. 33 | Invariants []string 34 | 35 | // Comparison functions to use. The keys of this map are 36 | // ToString'd Types. 37 | Comparison map[string]EitherFunctionOrMethod 38 | 39 | // Generator functions The keys of this map are ToString'd Types. 40 | Generator map[string]Generator 41 | 42 | // Initial state for custom generator functions. 43 | GeneratorState string 44 | } 45 | 46 | // Generator is the name of a function to generate a value of a given 47 | // type. 48 | type Generator struct { 49 | // True if this is stateful. 50 | IsStateful bool 51 | 52 | // The function itself. 53 | Name string 54 | } 55 | 56 | // EitherFunctionOrMethod is either a function or a method. Param and 57 | // receiver types are all the same. 58 | type EitherFunctionOrMethod struct { 59 | // True if this is function, rather than a method. 60 | IsFunction bool 61 | 62 | // The function itself. 63 | Name string 64 | 65 | // The type of the method receiver / function parameters. 66 | Type Type 67 | 68 | // List of return types. Only meaningful in "@before compare". 69 | Returns []Type 70 | } 71 | 72 | // WantedFuzzersFromAST extracts all wanted fuzzers from comments in 73 | // the AST of a file. 74 | func WantedFuzzersFromAST(theAST *ast.File) (wanteds []WantedFuzzer, errs []error) { 75 | if theAST == nil { 76 | return nil, nil 77 | } 78 | 79 | if theAST.Doc != nil { 80 | wanted, err := WantedFuzzersFromCommentGroup(theAST.Doc) 81 | if err == nil { 82 | wanteds = append(wanteds, wanted...) 83 | } else { 84 | errs = append(errs, err) 85 | } 86 | } 87 | 88 | for _, group := range theAST.Comments { 89 | wanted, err := WantedFuzzersFromCommentGroup(group) 90 | if err == nil { 91 | wanteds = append(wanteds, wanted...) 92 | } else { 93 | errs = append(errs, err) 94 | } 95 | } 96 | 97 | return wanteds, errs 98 | } 99 | 100 | // WantedFuzzersFromCommentGroup extracts all wanted fuzzer 101 | // descriptions from a comment group. The "@fuzz interface:" line 102 | // starts a new fuzzer definition; special comments in a group before 103 | // this are ignored. 104 | func WantedFuzzersFromCommentGroup(group *ast.CommentGroup) ([]WantedFuzzer, error) { 105 | if group == nil { 106 | return nil, nil 107 | } 108 | 109 | var commentLines []string 110 | for _, comment := range group.List { 111 | lines := splitLines(comment.Text) 112 | commentLines = append(commentLines, lines...) 113 | } 114 | 115 | return WantedFuzzersFromCommentLines(commentLines) 116 | } 117 | 118 | // WantedFuzzersFromCommentLines extracts all wanted fuzzer 119 | // descriptions from a collection of comment lines. The "@fuzz 120 | // interface:" line starts a new fuzzer definition; special comments 121 | // in a group before this are ignored. 122 | func WantedFuzzersFromCommentLines(commentLines []string) ([]WantedFuzzer, error) { 123 | if commentLines == nil { 124 | return nil, nil 125 | } 126 | 127 | var fuzzers []WantedFuzzer 128 | var fuzzer WantedFuzzer 129 | 130 | // 'fuzzing' indicates whether we've found the start of a 131 | // special comment or not. If not, just look for "@fuzz 132 | // interface" and ignore everything else. 133 | fuzzing := false 134 | 135 | for _, line := range commentLines { 136 | line = strings.TrimSpace(line) 137 | 138 | var err error 139 | if fuzzing { 140 | err = parseLine(line, &fuzzer) 141 | } else { 142 | // "@fuzz interface:" 143 | suff, ok := matchPrefix(line, "@fuzz interface:") 144 | if !ok { 145 | continue 146 | } 147 | 148 | if fuzzing { 149 | // Found a new fuzzer! Add the old one to the list. 150 | if fuzzer.Reference.Name == "" { 151 | return fuzzers, fmt.Errorf("fuzzer declaration for %s missing '@known correct' line", fuzzer.InterfaceName) 152 | } 153 | fuzzers = append(fuzzers, fuzzer) 154 | 155 | } 156 | 157 | var name string 158 | name, err = parseFuzzInterface(suff) 159 | fuzzer = WantedFuzzer{ 160 | InterfaceName: name, 161 | Comparison: make(map[string]EitherFunctionOrMethod), 162 | Generator: make(map[string]Generator), 163 | } 164 | fuzzing = true 165 | } 166 | 167 | if err != nil { 168 | return fuzzers, err 169 | } 170 | 171 | } 172 | 173 | if fuzzing { 174 | // Add the final fuzzer to the list. 175 | if fuzzer.Reference.Name == "" { 176 | return fuzzers, fmt.Errorf("fuzzer declaration for %s missing '@known correct' line", fuzzer.InterfaceName) 177 | } 178 | return append(fuzzers, fuzzer), nil 179 | } 180 | 181 | return fuzzers, nil 182 | } 183 | 184 | /* Parse a line in a comment. If this is a special comment, handle it 185 | and mutate the wanted fuzzer; if not, skip over. 186 | 187 | SYNTAX: @known correct: 188 | | @invariant: 189 | | @comparison: 190 | | @generator: 191 | | @generator state: 192 | */ 193 | func parseLine(line string, fuzzer *WantedFuzzer) error { 194 | // "@known correct:" 195 | suff, ok := matchPrefix(line, "@known correct:") 196 | if ok { 197 | fundecl, returnsValue, err := parseKnownCorrect(suff) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | retty := BasicType(fuzzer.InterfaceName) 203 | fundecl.Returns = []Type{&retty} 204 | fuzzer.Reference = fundecl 205 | fuzzer.ReturnsValue = returnsValue 206 | } 207 | 208 | // "@invariant:" 209 | suff, ok = matchPrefix(line, "@invariant:") 210 | if ok { 211 | inv, err := parseInvariant(suff) 212 | if err != nil { 213 | return err 214 | } 215 | 216 | fuzzer.Invariants = append(fuzzer.Invariants, inv) 217 | } 218 | 219 | // "@comparison:" 220 | suff, ok = matchPrefix(line, "@comparison:") 221 | if ok { 222 | tyname, fundecl, err := parseComparison(suff) 223 | if err != nil { 224 | return err 225 | } 226 | 227 | fuzzer.Comparison[tyname.ToString()] = fundecl 228 | } 229 | 230 | // "@generator:" 231 | suff, ok = matchPrefix(line, "@generator:") 232 | if ok { 233 | tyname, genfunc, stateful, err := parseGenerator(suff) 234 | if err != nil { 235 | return err 236 | } 237 | 238 | fuzzer.Generator[tyname.ToString()] = Generator{IsStateful: stateful, Name: genfunc} 239 | } 240 | 241 | // "@generator state:" 242 | suff, ok = matchPrefix(line, "@generator state:") 243 | if ok { 244 | state, err := parseGeneratorState(suff) 245 | if err != nil { 246 | return err 247 | } 248 | 249 | fuzzer.GeneratorState = state 250 | } 251 | 252 | return nil 253 | } 254 | 255 | // Parse a "@fuzz interface:" 256 | // 257 | // SYNTAX: Name 258 | func parseFuzzInterface(line string) (string, error) { 259 | var ( 260 | name string 261 | err error 262 | rest string 263 | ) 264 | 265 | name, rest = parseName(line) 266 | 267 | if name == "" { 268 | err = fmt.Errorf("expected a name in '%s'", line) 269 | } else if rest != "" { 270 | err = fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest) 271 | } 272 | 273 | return name, err 274 | } 275 | 276 | // Parse a "@known correct:" 277 | // 278 | // SYNTAX: [&] FunctionName [ArgType1 ... ArgTypeN] 279 | func parseKnownCorrect(line string) (Function, bool, error) { 280 | var function Function 281 | 282 | if len(line) == 0 { 283 | return function, false, errors.New("@known correct has empty argument") 284 | } 285 | 286 | // [&] 287 | rest, returnsValue := matchPrefix(line, "&") 288 | 289 | // FunctionName 290 | if len(line) == 0 { 291 | return function, false, errors.New("@known correct must have a function name") 292 | } 293 | 294 | function.Name, rest = parseFunctionName(rest) 295 | 296 | // [ArgType1 ... ArgTypeN] 297 | var args []Type 298 | for rest != "" { 299 | var argty Type 300 | var err error 301 | argty, rest, err = parseType(rest) 302 | 303 | if err != nil { 304 | return function, false, err 305 | } 306 | 307 | args = append(args, argty) 308 | } 309 | function.Parameters = args 310 | 311 | return function, returnsValue, nil 312 | } 313 | 314 | // Parse a "@comparison:" 315 | // 316 | // SYNTAX: (Type:FunctionName | FunctionName Type) 317 | func parseComparison(line string) (Type, EitherFunctionOrMethod, error) { 318 | funcOrMeth, rest, err := parseFunctionOrMethod(line) 319 | 320 | if err != nil { 321 | return nil, funcOrMeth, err 322 | } 323 | if rest != "" { 324 | return nil, funcOrMeth, fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest) 325 | } 326 | 327 | return funcOrMeth.Type, funcOrMeth, err 328 | } 329 | 330 | // Parse a "@generator:" 331 | // 332 | // SYNTAX: [!] FunctionName Type 333 | func parseGenerator(line string) (Type, string, bool, error) { 334 | // [!] 335 | rest, stateful := matchPrefix(line, "!") 336 | 337 | // FunctionName 338 | var name string 339 | name, rest = parseFunctionName(rest) 340 | 341 | if name == "" { 342 | return nil, name, stateful, fmt.Errorf("expected a name in '%s'", line) 343 | } 344 | 345 | var err error 346 | var ty Type 347 | ty, rest, err = parseType(rest) 348 | 349 | if rest != "" { 350 | err = fmt.Errorf("unexpected left over input in '%s' (got '%s')", line, rest) 351 | } 352 | 353 | return ty, name, stateful, err 354 | } 355 | 356 | // Parse a "@generator state:" 357 | // 358 | // This does absolutely NO checking whatsoever beyond presence 359 | // checking! 360 | // 361 | // SYNTAX: Expression 362 | func parseGeneratorState(line string) (string, error) { 363 | if line == "" { 364 | return "", fmt.Errorf("expected an initial state") 365 | } 366 | 367 | return line, nil 368 | } 369 | 370 | // Parse an "@invariant:" 371 | // 372 | // This does absolutely NO checking whatsoever beyond presence 373 | // checking! 374 | // 375 | // SYNTAX: Expression 376 | func parseInvariant(line string) (string, error) { 377 | if line == "" { 378 | return "", fmt.Errorf("expected an expression") 379 | } 380 | 381 | return line, nil 382 | } 383 | 384 | // Parse a function or a method, returning the remainder of the 385 | // string, which has leading spaces stripped. 386 | // 387 | // SYNTAX: (Type:FunctionName | FunctionName Type) 388 | func parseFunctionOrMethod(line string) (EitherFunctionOrMethod, string, error) { 389 | var ( 390 | funcOrMeth EitherFunctionOrMethod 391 | rest string 392 | err error 393 | ) 394 | 395 | // This is a bit tricky, as there is overlap between names and 396 | // types. Try parsing as both a name and a type: if the type 397 | // succeeds, assume it's a method and go with that; if not and the 398 | // name succeeds assume it's a function; and if neither succeed 399 | // give an error. 400 | 401 | tyType, tyRest, tyErr := parseType(line) 402 | nName, nRest := parseFunctionName(line) 403 | 404 | if tyErr == nil && tyRest[0] == ':' { 405 | // It's a method. 406 | funcOrMeth.Type = tyType 407 | funcOrMeth.Name, rest = parseFunctionName(tyRest[1:]) 408 | } else if nName != "" { 409 | // It's a function 410 | funcOrMeth.Name = nName 411 | funcOrMeth.Type, rest, err = parseType(nRest) 412 | funcOrMeth.IsFunction = true 413 | } else { 414 | err = fmt.Errorf("'%s' does not appear to be a method or function", line) 415 | } 416 | 417 | return funcOrMeth, rest, err 418 | } 419 | 420 | // Parse a function name, returning the remainder of the string, which 421 | // has leading spaces stripped. 422 | // 423 | // SYNTAX: [ModuleName.].FunctionName 424 | func parseFunctionName(line string) (string, string) { 425 | var ( 426 | name string 427 | rest string 428 | ) 429 | 430 | // Parse a name and see if the next character is a '.'. 431 | pref, suff := parseName(line) 432 | suff = strings.TrimLeftFunc(suff, unicode.IsSpace) 433 | 434 | if len(suff) > 0 && suff[0] == '.' { 435 | modname := pref 436 | funcname, suff2 := parseName(suff[1:]) 437 | name = modname + "." + funcname 438 | rest = strings.TrimLeftFunc(suff2, unicode.IsSpace) 439 | } else { 440 | name = pref 441 | rest = suff 442 | } 443 | 444 | return name, rest 445 | 446 | } 447 | 448 | // Parse a type. This is very stupid and doesn't make much effort to 449 | // be absolutely correct. 450 | // 451 | // SYNTAX: []Type | chan Type | map[Type]Type | *Type | (Type) | Name.Type | Name 452 | func parseType(s string) (Type, string, error) { 453 | // Array type 454 | suff, ok := matchPrefix(s, "[]") 455 | if ok { 456 | tycon := func(t Type) Type { 457 | ty := ArrayType{ElementType: t} 458 | return &ty 459 | } 460 | return parseUnaryType(tycon, suff, s) 461 | } 462 | 463 | // Chan type 464 | suff, ok = matchPrefix(s, "chan") 465 | if ok { 466 | tycon := func(t Type) Type { 467 | ty := ChanType{ElementType: t} 468 | return &ty 469 | } 470 | return parseUnaryType(tycon, suff, s) 471 | } 472 | 473 | // Map type 474 | suff, ok = matchPrefix(s, "map[") 475 | if ok { 476 | keyTy, keyRest, keyErr := parseType(suff) 477 | suff, ok = matchPrefix(keyRest, "]") 478 | if ok && keyErr == nil { 479 | tycon := func(t Type) Type { 480 | ty := MapType{KeyType: keyTy, ValueType: t} 481 | return &ty 482 | } 483 | return parseUnaryType(tycon, keyRest[1:], s) 484 | } 485 | return nil, s, fmt.Errorf("Mismatched brackets in '%s'", s) 486 | } 487 | 488 | // Pointer type 489 | suff, ok = matchPrefix(s, "*") 490 | if ok { 491 | tycon := func(t Type) Type { 492 | ty := PointerType{TargetType: t} 493 | return &ty 494 | } 495 | return parseUnaryType(tycon, suff, s) 496 | } 497 | 498 | // Type in (posibly 0) parentheses 499 | noParens, parenOk := matchDelims(s, "(", ")") 500 | if parenOk { 501 | // Basic type OR qualified type 502 | if noParens == s { 503 | pref, suff := parseName(s) 504 | suff = strings.TrimLeftFunc(suff, unicode.IsSpace) 505 | 506 | if len(suff) > 0 && suff[0] == '.' { 507 | pkg := pref 508 | tyname, suff2 := parseName(suff[1:]) 509 | ty := BasicType(tyname) 510 | rest := strings.TrimLeftFunc(suff2, unicode.IsSpace) 511 | qty := QualifiedType{Package: pkg, Type: &ty} 512 | return &qty, rest, nil 513 | } 514 | basicTy := BasicType(pref) 515 | rest := suff 516 | return &basicTy, rest, nil 517 | } 518 | 519 | return parseType(noParens) 520 | } 521 | 522 | return nil, s, fmt.Errorf("mismatched parentheses in '%s'", s) 523 | } 524 | 525 | // Helper function for parsing a unary type operator: [], chan, or *. 526 | // 527 | // SYNTAX: Type 528 | func parseUnaryType(tycon func(Type) Type, s, orig string) (Type, string, error) { 529 | var ( 530 | innerTy Type 531 | rest string 532 | err error 533 | ) 534 | 535 | noSpaces := strings.TrimLeft(s, " ") 536 | noParens, parenOk := matchDelims(noSpaces, "(", ")") 537 | 538 | if parenOk { 539 | innerTy, rest, err = parseType(noParens) 540 | } else { 541 | err = fmt.Errorf("mismatched parentheses in '%s'", orig) 542 | } 543 | 544 | return tycon(innerTy), rest, err 545 | } 546 | 547 | // Parse a name. 548 | // 549 | // SYNTAX: [a-zA-Z0-9_-] 550 | func parseName(s string) (string, string) { 551 | name, suff := takeWhileIn(s, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") 552 | rest := strings.TrimLeftFunc(suff, unicode.IsSpace) 553 | return name, rest 554 | } 555 | --------------------------------------------------------------------------------