├── .github └── workflows │ ├── test.yml │ └── validate.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── _generated ├── allownil.go ├── allownil_test.go ├── clearomitted.go ├── clearomitted_test.go ├── compactfloats.go ├── compactfloats_test.go ├── convert.go ├── convert_test.go ├── custom_tag.go ├── custom_tag_test.go ├── def.go ├── def_test.go ├── embedded_struct.go ├── embedded_struct_test.go ├── errorwrap.go ├── errorwrap_test.go ├── gen_test.go ├── issue102.go ├── issue191.go ├── issue191_test.go ├── issue94.go ├── issue94_test.go ├── newtime.go ├── newtime_test.go ├── omitempty.go ├── omitempty_test.go ├── omitzero.go ├── omitzero_ext.go ├── omitzero_test.go ├── pointer.go ├── replace.go ├── replace_ext.go ├── replace_test.go ├── setof.go ├── setof_test.go └── vet_copylocks.go ├── gen ├── decode.go ├── elem.go ├── encode.go ├── marshal.go ├── size.go ├── spec.go ├── testgen.go └── unmarshal.go ├── go.mod ├── go.sum ├── issue185_test.go ├── main.go ├── msgp ├── advise_linux.go ├── advise_other.go ├── circular.go ├── defs.go ├── defs_test.go ├── edit.go ├── edit_test.go ├── elsize.go ├── elsize_default.go ├── elsize_test.go ├── elsize_tinygo.go ├── errors.go ├── errors_default.go ├── errors_test.go ├── errors_tinygo.go ├── extension.go ├── extension_test.go ├── file.go ├── file_port.go ├── file_test.go ├── floatbench_test.go ├── integers.go ├── integers_test.go ├── json.go ├── json_bytes.go ├── json_bytes_test.go ├── json_test.go ├── number.go ├── number_test.go ├── purego.go ├── raw_test.go ├── read.go ├── read_bytes.go ├── read_bytes_test.go ├── read_test.go ├── setof │ ├── _gen │ │ └── main.go │ ├── generated.go │ └── setof.go ├── size.go ├── unsafe.go ├── write.go ├── write_bytes.go ├── write_bytes_test.go └── write_test.go ├── parse ├── directives.go ├── getast.go └── inline.go ├── printer └── print.go └── tinygotest ├── .gitignore ├── testdata ├── empty │ └── main.go ├── roundtrip │ └── main.go ├── simple_bytes_append │ └── main.go ├── simple_bytes_read │ └── main.go ├── simple_marshal │ └── main.go ├── simple_roundtrip │ └── main.go └── simple_unmarshal │ └── main.go └── tinygo_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - 'master' 12 | - 'main' 13 | tags: 14 | - 'v*' 15 | pull_request: 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | go-version: [1.21.x, 1.22.x, 1.23.x] 25 | os: [ubuntu-latest] 26 | runs-on: ${{ matrix.os }} 27 | timeout-minutes: 10 28 | steps: 29 | - uses: actions/setup-go@v5 30 | with: 31 | go-version: ${{ matrix.go-version }} 32 | - uses: actions/checkout@v4 33 | - name: test 34 | run: make ci 35 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - 'master' 12 | - 'main' 13 | tags: 14 | - 'v*' 15 | pull_request: 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | linters: 22 | strategy: 23 | matrix: 24 | go-version: [1.23.x] 25 | os: [ubuntu-latest] 26 | runs-on: ${{ matrix.os }} 27 | timeout-minutes: 10 28 | steps: 29 | - uses: actions/setup-go@v5 30 | with: 31 | go-version: ${{ matrix.go-version }} 32 | - uses: actions/checkout@v4 33 | - name: prepare generated code 34 | run: make prepare 35 | - name: lint 36 | uses: golangci/golangci-lint-action@v6 37 | with: 38 | version: v1.60.3 39 | args: --print-resources-usage --timeout=10m --verbose 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _generated/generated.go 2 | _generated/generated_test.go 3 | _generated/*_gen.go 4 | _generated/*_gen_test.go 5 | _generated/embeddedStruct/*_gen.go 6 | _generated/embeddedStruct/*_gen_test.go 7 | msgp/defgen_test.go 8 | msgp/cover.out 9 | *~ 10 | *.coverprofile 11 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable: 3 | - errcheck 4 | 5 | linters-settings: 6 | staticcheck: 7 | checks: 8 | - all 9 | - '-SA1019' # We use the ast package. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Philip Hofer 2 | Portions Copyright (c) 2009 The Go Authors (license at http://golang.org) where indicated 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # NOTE: This Makefile is only necessary if you 3 | # plan on developing the msgp tool and library. 4 | # Installation can still be performed with a 5 | # normal `go install`. 6 | 7 | # generated integration test files 8 | GGEN = ./_generated/generated.go ./_generated/generated_test.go 9 | # generated unit test files 10 | MGEN = ./msgp/defgen_test.go 11 | 12 | SHELL := /bin/bash 13 | 14 | BIN = $(GOBIN)/msgp 15 | 16 | .PHONY: clean wipe install get-deps bench all ci prepare 17 | 18 | $(BIN): */*.go 19 | @go install ./... 20 | 21 | install: $(BIN) 22 | 23 | $(GGEN): ./_generated/def.go 24 | go generate ./_generated 25 | 26 | $(MGEN): ./msgp/defs_test.go 27 | go generate ./msgp 28 | 29 | test: all 30 | go test ./... ./_generated 31 | 32 | bench: all 33 | go test -bench ./... 34 | 35 | clean: 36 | $(RM) $(GGEN) $(MGEN) 37 | 38 | wipe: clean 39 | $(RM) $(BIN) 40 | 41 | get-deps: 42 | go get -d -t ./... 43 | 44 | all: install $(GGEN) $(MGEN) 45 | 46 | # Prepare generated code to be used for linting and testing in CI 47 | prepare: 48 | go install . 49 | go generate ./msgp 50 | go generate ./_generated 51 | 52 | # CI enters here 53 | ci: prepare 54 | arch 55 | if [ `arch` == 'x86_64' ]; then \ 56 | sudo apt-get -y -q update; \ 57 | sudo apt-get -y -q install build-essential; \ 58 | wget -q https://github.com/tinygo-org/tinygo/releases/download/v0.33.0/tinygo_0.33.0_amd64.deb; \ 59 | sudo dpkg -i tinygo_0.33.0_amd64.deb; \ 60 | export PATH=$$PATH:/usr/local/tinygo/bin; \ 61 | fi 62 | go test -v ./... ./_generated 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MessagePack Code Generator 2 | ======= 3 | 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/tinylib/msgp.svg)](https://pkg.go.dev/github.com/tinylib/msgp) 5 | [![test](https://github.com/tinylib/msgp/actions/workflows/test.yml/badge.svg)](https://github.com/tinylib/msgp/actions/workflows/test.yml) 6 | [![validate](https://github.com/tinylib/msgp/actions/workflows/validate.yml/badge.svg)](https://github.com/tinylib/msgp/actions/workflows/validate.yml) 7 | 8 | This is a code generation tool and serialization library for [MessagePack](http://msgpack.org). You can read more about MessagePack [in the wiki](http://github.com/tinylib/msgp/wiki), or at [msgpack.org](http://msgpack.org). 9 | 10 | ### Why? 11 | 12 | - Use Go as your schema language 13 | - Performance 14 | - [JSON interop](https://pkg.go.dev/github.com/tinylib/msgp/msgp#CopyToJSON) 15 | - [User-defined extensions](http://github.com/tinylib/msgp/wiki/Using-Extensions) 16 | - Type safety 17 | - Encoding flexibility 18 | 19 | ### Quickstart 20 | 21 | First install the `msgp` generator command. Using Go this is done with `go install github.com/tinylib/msgp@latest` 22 | 23 | In a source file, include the following directive: 24 | 25 | ```go 26 | //go:generate msgp 27 | ``` 28 | 29 | The `msgp` command will generate serialization methods for all exported type declarations in the file. 30 | 31 | You can [read more about the code generation options here](http://github.com/tinylib/msgp/wiki/Using-the-Code-Generator). 32 | 33 | ### Use 34 | 35 | Field names can be set in much the same way as the `encoding/json` package. For example: 36 | 37 | ```go 38 | type Person struct { 39 | Name string `msg:"name"` 40 | Address string `msg:"address"` 41 | Age int `msg:"age"` 42 | Hidden string `msg:"-"` // this field is ignored 43 | unexported bool // this field is also ignored 44 | } 45 | ``` 46 | 47 | By default, the code generator will satisfy `msgp.Sizer`, `msgp.Encodable`, `msgp.Decodable`, 48 | `msgp.Marshaler`, and `msgp.Unmarshaler`. Carefully-designed applications can use these methods to do 49 | marshalling/unmarshalling with zero heap allocations. 50 | 51 | While `msgp.Marshaler` and `msgp.Unmarshaler` are quite similar to the standard library's 52 | `json.Marshaler` and `json.Unmarshaler`, `msgp.Encodable` and `msgp.Decodable` are useful for 53 | stream serialization. (`*msgp.Writer` and `*msgp.Reader` are essentially protocol-aware versions 54 | of `*bufio.Writer` and `*bufio.Reader`, respectively.) 55 | 56 | An important thing to note is that msgp operates on *individual files*. 57 | This means if your structs include types defined in other files, these must be processed as well. 58 | 59 | ### Features 60 | 61 | - Extremely fast generated code 62 | - Test and benchmark generation 63 | - JSON interoperability (see `msgp.CopyToJSON() and msgp.UnmarshalAsJSON()`) 64 | - Support for complex type declarations 65 | - Native support for Go's `time.Time`, `complex64`, and `complex128` types 66 | - Generation of both `[]byte`-oriented and `io.Reader/io.Writer`-oriented methods 67 | - Support for arbitrary type system extensions 68 | - [Preprocessor directives](http://github.com/tinylib/msgp/wiki/Preprocessor-Directives) 69 | - File-based dependency model means fast codegen regardless of source tree size. 70 | 71 | Consider the following: 72 | ```go 73 | const Eight = 8 74 | type MyInt int 75 | type Data []byte 76 | 77 | type Struct struct { 78 | Which map[string]*MyInt `msg:"which"` 79 | Other Data `msg:"other"` 80 | Nums [Eight]float64 `msg:"nums"` 81 | } 82 | ``` 83 | As long as the declarations of `MyInt` and `Data` are in the same file as `Struct`, the parser will determine that the type information for `MyInt` and `Data` can be passed into the definition of `Struct` before its methods are generated. 84 | 85 | #### Extensions 86 | 87 | MessagePack supports defining your own types through "extensions," which are just a tuple of 88 | the data "type" (`int8`) and the raw binary. You [can see a worked example in the wiki.](http://github.com/tinylib/msgp/wiki/Using-Extensions) 89 | 90 | ### Status 91 | 92 | Mostly stable, in that no breaking changes have been made to the `/msgp` library in more than a year. Newer versions 93 | of the code may generate different code than older versions for performance reasons. I (@philhofer) am aware of a 94 | number of stability-critical commercial applications that use this code with good results. But, caveat emptor. 95 | 96 | You can read more about how `msgp` maps MessagePack types onto Go types [in the wiki](http://github.com/tinylib/msgp/wiki). 97 | 98 | Here some of the known limitations/restrictions: 99 | 100 | - Identifiers from outside the processed source file are assumed (optimistically) to satisfy the generator's interfaces. If this isn't the case, your code will fail to compile. 101 | - Like most serializers, `chan` and `func` fields are ignored, as well as non-exported fields. 102 | - Encoding of `interface{}` is limited to built-ins or types that have explicit encoding methods. 103 | - _Maps must have `string` keys._ This is intentional (as it preserves JSON interop.) Although non-string map keys are not forbidden by the MessagePack standard, many serializers impose this restriction. (It also means *any* well-formed `struct` can be de-serialized into a `map[string]interface{}`.) The only exception to this rule is that the deserializers will allow you to read map keys encoded as `bin` types, due to the fact that some legacy encodings permitted this. (However, those values will still be cast to Go `string`s, and they will be converted to `str` types when re-encoded. It is the responsibility of the user to ensure that map keys are UTF-8 safe in this case.) The same rules hold true for JSON translation. 104 | 105 | If the output compiles, then there's a pretty good chance things are fine. (Plus, we generate tests for you.) *Please, please, please* file an issue if you think the generator is writing broken code. 106 | 107 | ### Performance 108 | 109 | If you like benchmarks, see [here](http://bravenewgeek.com/so-you-wanna-go-fast/) and [here](https://github.com/alecthomas/go_serialization_benchmarks). 110 | 111 | As one might expect, the generated methods that deal with `[]byte` are faster for small objects, but the `io.Reader/Writer` methods are generally more memory-efficient (and, at some point, faster) for large (> 2KB) objects. 112 | -------------------------------------------------------------------------------- /_generated/allownil_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/tinylib/msgp/msgp" 9 | ) 10 | 11 | func TestAllownil(t *testing.T) { 12 | tt := &NamedStructAN{ 13 | A: []string{}, 14 | B: nil, 15 | } 16 | var buf bytes.Buffer 17 | 18 | err := msgp.Encode(&buf, tt) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | in := buf.Bytes() 23 | 24 | for _, tnew := range []*NamedStructAN{{}, {A: []string{}}, {B: []string{}}} { 25 | err = msgp.Decode(bytes.NewBuffer(in), tnew) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | if !reflect.DeepEqual(tt, tnew) { 31 | t.Logf("in: %#v", tt) 32 | t.Logf("out: %#v", tnew) 33 | t.Fatal("objects not equal") 34 | } 35 | } 36 | 37 | in, err = tt.MarshalMsg(nil) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | for _, tanother := range []*NamedStructAN{{}, {A: []string{}}, {B: []string{}}} { 42 | var left []byte 43 | left, err = tanother.UnmarshalMsg(in) 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | if len(left) > 0 { 48 | t.Errorf("%d bytes left", len(left)) 49 | } 50 | 51 | if !reflect.DeepEqual(tt, tanother) { 52 | t.Logf("in: %#v", tt) 53 | t.Logf("out: %#v", tanother) 54 | t.Fatal("objects not equal") 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /_generated/clearomitted.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | //go:generate msgp 9 | 10 | //msgp:clearomitted 11 | 12 | // check some specific cases for omitzero 13 | 14 | type ClearOmitted0 struct { 15 | AStruct ClearOmittedA `msg:"astruct,omitempty"` // leave this one omitempty 16 | BStruct ClearOmittedA `msg:"bstruct,omitzero"` // and compare to this 17 | AStructPtr *ClearOmittedA `msg:"astructptr,omitempty"` // a pointer case omitempty 18 | BStructPtr *ClearOmittedA `msg:"bstructptr,omitzero"` // a pointer case omitzero 19 | AExt OmitZeroExt `msg:"aext,omitzero"` // external type case 20 | 21 | // more 22 | APtrNamedStr *NamedStringCO `msg:"aptrnamedstr,omitzero"` 23 | ANamedStruct NamedStructCO `msg:"anamedstruct,omitzero"` 24 | APtrNamedStruct *NamedStructCO `msg:"aptrnamedstruct,omitzero"` 25 | EmbeddableStructCO `msg:",flatten,omitzero"` // embed flat 26 | EmbeddableStructCO2 `msg:"embeddablestruct2,omitzero"` // embed non-flat 27 | ATime time.Time `msg:"atime,omitzero"` 28 | ASlice []int `msg:"aslice,omitempty"` 29 | AMap map[string]int `msg:"amap,omitempty"` 30 | ABin []byte `msg:"abin,omitempty"` 31 | AInt int `msg:"aint,omitempty"` 32 | AString string `msg:"atring,omitempty"` 33 | Adur time.Duration `msg:"adur,omitempty"` 34 | AJSON json.Number `msg:"ajson,omitempty"` 35 | AnAny any `msg:"anany,omitempty"` 36 | 37 | ClearOmittedTuple ClearOmittedTuple `msg:"ozt"` // the inside of a tuple should ignore both omitempty and omitzero 38 | } 39 | 40 | type ClearOmittedA struct { 41 | A string `msg:"a,omitempty"` 42 | B NamedStringCO `msg:"b,omitzero"` 43 | C NamedStringCO `msg:"c,omitzero"` 44 | } 45 | 46 | func (o *ClearOmittedA) IsZero() bool { 47 | if o == nil { 48 | return true 49 | } 50 | return *o == (ClearOmittedA{}) 51 | } 52 | 53 | type NamedStructCO struct { 54 | A string `msg:"a,omitempty"` 55 | B string `msg:"b,omitempty"` 56 | } 57 | 58 | func (ns *NamedStructCO) IsZero() bool { 59 | if ns == nil { 60 | return true 61 | } 62 | return *ns == (NamedStructCO{}) 63 | } 64 | 65 | type NamedStringCO string 66 | 67 | func (ns *NamedStringCO) IsZero() bool { 68 | if ns == nil { 69 | return true 70 | } 71 | return *ns == "" 72 | } 73 | 74 | type EmbeddableStructCO struct { 75 | SomeEmbed string `msg:"someembed2,omitempty"` 76 | } 77 | 78 | func (es EmbeddableStructCO) IsZero() bool { return es == (EmbeddableStructCO{}) } 79 | 80 | type EmbeddableStructCO2 struct { 81 | SomeEmbed2 string `msg:"someembed2,omitempty"` 82 | } 83 | 84 | func (es EmbeddableStructCO2) IsZero() bool { return es == (EmbeddableStructCO2{}) } 85 | 86 | //msgp:tuple ClearOmittedTuple 87 | 88 | // ClearOmittedTuple is flagged for tuple output, it should ignore all omitempty and omitzero functionality 89 | // since it's fundamentally incompatible. 90 | type ClearOmittedTuple struct { 91 | FieldA string `msg:"fielda,omitempty"` 92 | FieldB NamedStringCO `msg:"fieldb,omitzero"` 93 | FieldC NamedStringCO `msg:"fieldc,omitzero"` 94 | } 95 | 96 | type ClearOmitted1 struct { 97 | T1 ClearOmittedTuple `msg:"t1"` 98 | } 99 | -------------------------------------------------------------------------------- /_generated/clearomitted_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/tinylib/msgp/msgp" 11 | ) 12 | 13 | func TestClearOmitted(t *testing.T) { 14 | cleared := ClearOmitted0{} 15 | encoded, err := cleared.MarshalMsg(nil) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | vPtr := NamedStringCO("value") 20 | filled := ClearOmitted0{ 21 | AStruct: ClearOmittedA{A: "something"}, 22 | BStruct: ClearOmittedA{A: "somthing"}, 23 | AStructPtr: &ClearOmittedA{A: "something"}, 24 | AExt: OmitZeroExt{25}, 25 | APtrNamedStr: &vPtr, 26 | ANamedStruct: NamedStructCO{A: "value"}, 27 | APtrNamedStruct: &NamedStructCO{A: "sdf"}, 28 | EmbeddableStructCO: EmbeddableStructCO{"value"}, 29 | EmbeddableStructCO2: EmbeddableStructCO2{"value"}, 30 | ATime: time.Now(), 31 | ASlice: []int{1, 2, 3}, 32 | AMap: map[string]int{"1": 1}, 33 | ABin: []byte{1, 2, 3}, 34 | ClearOmittedTuple: ClearOmittedTuple{FieldA: "value"}, 35 | AInt: 42, 36 | AString: "value", 37 | Adur: time.Second, 38 | AJSON: json.Number(`43.0000000000002`), 39 | } 40 | dst := filled 41 | _, err = dst.UnmarshalMsg(encoded) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if !reflect.DeepEqual(dst, cleared) { 46 | t.Errorf("\n got=%#v\nwant=%#v", dst, cleared) 47 | } 48 | // Reset 49 | dst = filled 50 | err = dst.DecodeMsg(msgp.NewReader(bytes.NewReader(encoded))) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | if !reflect.DeepEqual(dst, cleared) { 55 | t.Errorf("\n got=%#v\nwant=%#v", dst, cleared) 56 | } 57 | 58 | // Check that fields aren't accidentally zeroing fields. 59 | wantJson, err := json.Marshal(filled) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | encoded, err = filled.MarshalMsg(nil) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | dst = ClearOmitted0{} 68 | _, err = dst.UnmarshalMsg(encoded) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | got, err := json.Marshal(dst) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | if !bytes.Equal(got, wantJson) { 77 | t.Errorf("\n got=%#v\nwant=%#v", string(got), string(wantJson)) 78 | } 79 | // Reset 80 | dst = ClearOmitted0{} 81 | err = dst.DecodeMsg(msgp.NewReader(bytes.NewReader(encoded))) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | got, err = json.Marshal(dst) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | if !bytes.Equal(got, wantJson) { 90 | t.Errorf("\n got=%#v\nwant=%#v", string(got), string(wantJson)) 91 | } 92 | t.Log("OK - got", string(got)) 93 | } 94 | -------------------------------------------------------------------------------- /_generated/compactfloats.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | 5 | //msgp:compactfloats 6 | 7 | //msgp:ignore F64 8 | type F64 float64 9 | 10 | //msgp:replace F64 with:float64 11 | 12 | type Floats struct { 13 | A float64 14 | B float32 15 | Slice []float64 16 | Map map[string]float64 17 | F F64 18 | OE float64 `msg:",omitempty"` 19 | } 20 | -------------------------------------------------------------------------------- /_generated/compactfloats_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/tinylib/msgp/msgp" 9 | ) 10 | 11 | func TestCompactFloats(t *testing.T) { 12 | // Constant that can be represented in f32 without loss 13 | const f32ok = -1e2 14 | allF32 := Floats{ 15 | A: f32ok, 16 | B: f32ok, 17 | Slice: []float64{f32ok, f32ok}, 18 | Map: map[string]float64{"a": f32ok}, 19 | F: f32ok, 20 | OE: f32ok, 21 | } 22 | asF32 := float32(f32ok) 23 | wantF32 := map[string]any{"A": asF32, "B": asF32, "F": asF32, "Map": map[string]any{"a": asF32}, "OE": asF32, "Slice": []any{asF32, asF32}} 24 | 25 | enc, err := allF32.MarshalMsg(nil) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | i, _, _ := msgp.ReadIntfBytes(enc) 30 | got := i.(map[string]any) 31 | if !reflect.DeepEqual(got, wantF32) { 32 | t.Errorf("want: %v, got: %v (diff may be types)", wantF32, got) 33 | } 34 | 35 | var buf bytes.Buffer 36 | en := msgp.NewWriter(&buf) 37 | allF32.EncodeMsg(en) 38 | en.Flush() 39 | enc = buf.Bytes() 40 | i, _, _ = msgp.ReadIntfBytes(enc) 41 | got = i.(map[string]any) 42 | if !reflect.DeepEqual(got, wantF32) { 43 | t.Errorf("want: %v, got: %v (diff may be types)", wantF32, got) 44 | } 45 | 46 | const f64ok = -10e64 47 | allF64 := Floats{ 48 | A: f64ok, 49 | B: f32ok, 50 | Slice: []float64{f64ok, f64ok}, 51 | Map: map[string]float64{"a": f64ok}, 52 | F: f64ok, 53 | OE: f64ok, 54 | } 55 | asF64 := float64(f64ok) 56 | wantF64 := map[string]any{"A": asF64, "B": asF32, "F": asF64, "Map": map[string]any{"a": asF64}, "OE": asF64, "Slice": []any{asF64, asF64}} 57 | 58 | enc, err = allF64.MarshalMsg(nil) 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | i, _, _ = msgp.ReadIntfBytes(enc) 63 | got = i.(map[string]any) 64 | if !reflect.DeepEqual(got, wantF64) { 65 | t.Errorf("want: %v, got: %v (diff may be types)", wantF64, got) 66 | } 67 | 68 | buf.Reset() 69 | en = msgp.NewWriter(&buf) 70 | allF64.EncodeMsg(en) 71 | en.Flush() 72 | enc = buf.Bytes() 73 | i, _, _ = msgp.ReadIntfBytes(enc) 74 | got = i.(map[string]any) 75 | if !reflect.DeepEqual(got, wantF64) { 76 | t.Errorf("want: %v, got: %v (diff may be types)", wantF64, got) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /_generated/convert.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "errors" 4 | 5 | //go:generate msgp 6 | 7 | //msgp:shim ConvertStringVal as:string using:fromConvertStringVal/toConvertStringVal mode:convert 8 | //msgp:ignore ConvertStringVal 9 | 10 | func fromConvertStringVal(v ConvertStringVal) (string, error) { 11 | return string(v), nil 12 | } 13 | 14 | func toConvertStringVal(s string) (ConvertStringVal, error) { 15 | return ConvertStringVal(s), nil 16 | } 17 | 18 | type ConvertStringVal string 19 | 20 | type ConvertString struct { 21 | String ConvertStringVal 22 | } 23 | 24 | type ConvertStringSlice struct { 25 | Strings []ConvertStringVal 26 | } 27 | 28 | type ConvertStringMapValue struct { 29 | Strings map[string]ConvertStringVal 30 | } 31 | 32 | //msgp:shim ConvertIntfVal as:interface{} using:fromConvertIntfVal/toConvertIntfVal mode:convert 33 | //msgp:ignore ConvertIntfVal 34 | 35 | func fromConvertIntfVal(v ConvertIntfVal) (interface{}, error) { 36 | return v.Test, nil 37 | } 38 | 39 | func toConvertIntfVal(s interface{}) (ConvertIntfVal, error) { 40 | return ConvertIntfVal{Test: s.(string)}, nil 41 | } 42 | 43 | type ConvertIntfVal struct { 44 | Test string 45 | } 46 | 47 | type ConvertIntf struct { 48 | Intf ConvertIntfVal 49 | } 50 | 51 | //msgp:shim ConvertErrVal as:string using:fromConvertErrVal/toConvertErrVal mode:convert 52 | //msgp:ignore ConvertErrVal 53 | 54 | var ( 55 | errConvertFrom = errors.New("error: convert from") 56 | errConvertTo = errors.New("error: convert to") 57 | ) 58 | 59 | const ( 60 | fromFailStr = "fromfail" 61 | toFailStr = "tofail" 62 | ) 63 | 64 | func fromConvertErrVal(v ConvertErrVal) (string, error) { 65 | s := string(v) 66 | if s == fromFailStr { 67 | return "", errConvertFrom 68 | } 69 | return s, nil 70 | } 71 | 72 | func toConvertErrVal(s string) (ConvertErrVal, error) { 73 | if s == toFailStr { 74 | return ConvertErrVal(""), errConvertTo 75 | } 76 | return ConvertErrVal(s), nil 77 | } 78 | 79 | type ConvertErrVal string 80 | 81 | type ConvertErr struct { 82 | Err ConvertErrVal 83 | } 84 | -------------------------------------------------------------------------------- /_generated/convert_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/tinylib/msgp/msgp" 8 | ) 9 | 10 | func TestConvertFromEncodeError(t *testing.T) { 11 | e := ConvertErr{ConvertErrVal(fromFailStr)} 12 | var buf bytes.Buffer 13 | w := msgp.NewWriter(&buf) 14 | err := e.EncodeMsg(w) 15 | if msgp.Cause(err) != errConvertFrom { 16 | t.Fatalf("expected conversion error, found '%v'", err.Error()) 17 | } 18 | } 19 | 20 | func TestConvertToEncodeError(t *testing.T) { 21 | var in, out ConvertErr 22 | in = ConvertErr{ConvertErrVal(toFailStr)} 23 | var buf bytes.Buffer 24 | w := msgp.NewWriter(&buf) 25 | err := in.EncodeMsg(w) 26 | if err != nil { 27 | t.FailNow() 28 | } 29 | w.Flush() 30 | 31 | r := msgp.NewReader(&buf) 32 | err = (&out).DecodeMsg(r) 33 | 34 | if msgp.Cause(err) != errConvertTo { 35 | t.Fatalf("expected conversion error, found %v", err.Error()) 36 | } 37 | } 38 | 39 | func TestConvertFromMarshalError(t *testing.T) { 40 | e := ConvertErr{ConvertErrVal(fromFailStr)} 41 | var b []byte 42 | _, err := e.MarshalMsg(b) 43 | if msgp.Cause(err) != errConvertFrom { 44 | t.Fatalf("expected conversion error, found %v", err.Error()) 45 | } 46 | } 47 | 48 | func TestConvertToMarshalError(t *testing.T) { 49 | var in, out ConvertErr 50 | in = ConvertErr{ConvertErrVal(toFailStr)} 51 | b, err := in.MarshalMsg(nil) 52 | if err != nil { 53 | t.FailNow() 54 | } 55 | 56 | _, err = (&out).UnmarshalMsg(b) 57 | if msgp.Cause(err) != errConvertTo { 58 | t.Fatalf("expected conversion error, found %v", err.Error()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /_generated/custom_tag.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | //msgp:tag mytag 5 | 6 | type CustomTag struct { 7 | Foo string `mytag:"foo_custom_name"` 8 | Bar int `mytag:"bar1234"` 9 | } 10 | -------------------------------------------------------------------------------- /_generated/custom_tag_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | 9 | "bytes" 10 | 11 | "github.com/tinylib/msgp/msgp" 12 | ) 13 | 14 | func TestCustomTag(t *testing.T) { 15 | t.Run("File Scope", func(t *testing.T) { 16 | ts := CustomTag{ 17 | Foo: "foostring13579", 18 | Bar: 999_999} 19 | encDecCustomTag(t, ts, "mytag") 20 | }) 21 | } 22 | 23 | func encDecCustomTag(t *testing.T, testStruct msgp.Encodable, tag string) { 24 | var b bytes.Buffer 25 | msgp.Encode(&b, testStruct) 26 | 27 | // Check tag names using JSON as an intermediary layer 28 | // TODO: is there a way to avoid the JSON layer? We'd need to directly decode raw msgpack -> map[string]any 29 | refJSON, err := json.Marshal(testStruct) 30 | if err != nil { 31 | t.Error(fmt.Sprintf("error encoding struct as JSON: %v", err)) 32 | } 33 | ref := make(map[string]any) 34 | // Encoding and decoding the original struct via JSON is necessary 35 | // for field comparisons to work, since JSON -> map[string]any 36 | // relies on type inferences such as all numbers being float64s 37 | json.Unmarshal(refJSON, &ref) 38 | 39 | var encJSON bytes.Buffer 40 | msgp.UnmarshalAsJSON(&encJSON, b.Bytes()) 41 | encoded := make(map[string]any) 42 | json.Unmarshal(encJSON.Bytes(), &encoded) 43 | 44 | tsType := reflect.TypeOf(testStruct) 45 | for i := 0; i < tsType.NumField(); i++ { 46 | // Check encoded field name 47 | field := tsType.Field(i) 48 | encodedValue, ok := encoded[field.Tag.Get(tag)] 49 | if !ok { 50 | t.Error("missing encoded value for field", field.Name) 51 | continue 52 | } 53 | // Check encoded field value (against original value post-JSON enc + dec) 54 | jsonName, ok := field.Tag.Lookup("json") 55 | if !ok { 56 | jsonName = field.Name 57 | } 58 | refValue := ref[jsonName] 59 | if !reflect.DeepEqual(refValue, encodedValue) { 60 | t.Error(fmt.Sprintf("incorrect encoded value for field %s. reference: %v, encoded: %v", 61 | field.Name, refValue, encodedValue)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /_generated/def_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/tinylib/msgp/msgp" 10 | ) 11 | 12 | func TestRuneEncodeDecode(t *testing.T) { 13 | tt := &TestType{} 14 | r := 'r' 15 | rp := &r 16 | tt.Rune = r 17 | tt.RunePtr = &r 18 | tt.RunePtrPtr = &rp 19 | tt.RuneSlice = []rune{'a', 'b', '😳'} 20 | 21 | var buf bytes.Buffer 22 | wrt := msgp.NewWriter(&buf) 23 | if err := tt.EncodeMsg(wrt); err != nil { 24 | t.Errorf("%v", err) 25 | } 26 | wrt.Flush() 27 | 28 | var out TestType 29 | rdr := msgp.NewReader(&buf) 30 | if err := (&out).DecodeMsg(rdr); err != nil { 31 | t.Errorf("%v", err) 32 | } 33 | if r != out.Rune { 34 | t.Errorf("rune mismatch: expected %c found %c", r, out.Rune) 35 | } 36 | if r != *out.RunePtr { 37 | t.Errorf("rune ptr mismatch: expected %c found %c", r, *out.RunePtr) 38 | } 39 | if r != **out.RunePtrPtr { 40 | t.Errorf("rune ptr ptr mismatch: expected %c found %c", r, **out.RunePtrPtr) 41 | } 42 | if !reflect.DeepEqual(tt.RuneSlice, out.RuneSlice) { 43 | t.Errorf("rune slice mismatch") 44 | } 45 | } 46 | 47 | func TestRuneMarshalUnmarshal(t *testing.T) { 48 | tt := &TestType{} 49 | r := 'r' 50 | rp := &r 51 | tt.Rune = r 52 | tt.RunePtr = &r 53 | tt.RunePtrPtr = &rp 54 | tt.RuneSlice = []rune{'a', 'b', '😳'} 55 | 56 | bts, err := tt.MarshalMsg(nil) 57 | if err != nil { 58 | t.Errorf("%v", err) 59 | } 60 | 61 | var out TestType 62 | if _, err := (&out).UnmarshalMsg(bts); err != nil { 63 | t.Errorf("%v", err) 64 | } 65 | if r != out.Rune { 66 | t.Errorf("rune mismatch: expected %c found %c", r, out.Rune) 67 | } 68 | if r != *out.RunePtr { 69 | t.Errorf("rune ptr mismatch: expected %c found %c", r, *out.RunePtr) 70 | } 71 | if r != **out.RunePtrPtr { 72 | t.Errorf("rune ptr ptr mismatch: expected %c found %c", r, **out.RunePtrPtr) 73 | } 74 | if !reflect.DeepEqual(tt.RuneSlice, out.RuneSlice) { 75 | t.Errorf("rune slice mismatch") 76 | } 77 | } 78 | 79 | func TestJSONNumber(t *testing.T) { 80 | test := NumberJSONSample{ 81 | Single: "-42", 82 | Array: []json.Number{"0", "-0", "1", "-1", "0.1", "-0.1", "1234", "-1234", "12.34", "-12.34", "12E0", "12E1", "12e34", "12E-0", "12e+1", "12e-34", "-12E0", "-12E1", "-12e34", "-12E-0", "-12e+1", "-12e-34", "1.2E0", "1.2E1", "1.2e34", "1.2E-0", "1.2e+1", "1.2e-34", "-1.2E0", "-1.2E1", "-1.2e34", "-1.2E-0", "-1.2e+1", "-1.2e-34", "0E0", "0E1", "0e34", "0E-0", "0e+1", "0e-34", "-0E0", "-0E1", "-0e34", "-0E-0", "-0e+1", "-0e-34"}, 83 | Map: map[string]json.Number{ 84 | "a": json.Number("50.2"), 85 | }, 86 | } 87 | 88 | // This is not guaranteed to be symmetric 89 | encoded, err := test.MarshalMsg(nil) 90 | if err != nil { 91 | t.Errorf("%v", err) 92 | } 93 | var v NumberJSONSample 94 | _, err = v.UnmarshalMsg(encoded) 95 | if err != nil { 96 | t.Errorf("%v", err) 97 | } 98 | // Test two values 99 | if v.Single != "-42" { 100 | t.Errorf("want %v, got %v", "-42", v.Single) 101 | } 102 | if v.Map["a"] != "50.2" { 103 | t.Errorf("want %v, got %v", "50.2", v.Map["a"]) 104 | } 105 | 106 | var jsBuf bytes.Buffer 107 | remain, err := msgp.UnmarshalAsJSON(&jsBuf, encoded) 108 | if err != nil { 109 | t.Errorf("%v", err) 110 | } 111 | if len(remain) != 0 { 112 | t.Errorf("remain should be empty") 113 | } 114 | wantjs := `{"Single":-42,"Array":[0,0,1,-1,0.1,-0.1,1234,-1234,12.34,-12.34,12,120,120000000000000000000000000000000000,12,120,0.0000000000000000000000000000000012,-12,-120,-120000000000000000000000000000000000,-12,-120,-0.0000000000000000000000000000000012,1.2,12,12000000000000000000000000000000000,1.2,12,0.00000000000000000000000000000000012,-1.2,-12,-12000000000000000000000000000000000,-1.2,-12,-0.00000000000000000000000000000000012,0,0,0,0,0,0,-0,-0,-0,-0,-0,-0],"Map":{"a":50.2}}` 115 | if jsBuf.String() != wantjs { 116 | t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs) 117 | } 118 | // Test encoding 119 | var buf bytes.Buffer 120 | en := msgp.NewWriter(&buf) 121 | err = test.EncodeMsg(en) 122 | if err != nil { 123 | t.Errorf("%v", err) 124 | } 125 | en.Flush() 126 | encoded = buf.Bytes() 127 | 128 | dc := msgp.NewReader(&buf) 129 | err = v.DecodeMsg(dc) 130 | if err != nil { 131 | t.Errorf("%v", err) 132 | } 133 | // Test two values 134 | if v.Single != "-42" { 135 | t.Errorf("want %s, got %s", "-42", v.Single) 136 | } 137 | if v.Map["a"] != "50.2" { 138 | t.Errorf("want %s, got %s", "50.2", v.Map["a"]) 139 | } 140 | 141 | jsBuf.Reset() 142 | remain, err = msgp.UnmarshalAsJSON(&jsBuf, encoded) 143 | if err != nil { 144 | t.Errorf("%v", err) 145 | } 146 | if len(remain) != 0 { 147 | t.Errorf("remain should be empty") 148 | } 149 | if jsBuf.String() != wantjs { 150 | t.Errorf("jsBuf.String() = \n%s, want \n%s", jsBuf.String(), wantjs) 151 | } 152 | 153 | // Try interface encoder 154 | jd := json.NewDecoder(&jsBuf) 155 | jd.UseNumber() 156 | var jsIntf map[string]any 157 | err = jd.Decode(&jsIntf) 158 | if err != nil { 159 | t.Errorf("%v", err) 160 | } 161 | // Ensure we encode correctly 162 | _ = (jsIntf["Single"]).(json.Number) 163 | 164 | fromInt, err := msgp.AppendIntf(nil, jsIntf) 165 | if err != nil { 166 | t.Errorf("%v", err) 167 | } 168 | 169 | // Take the value from the JSON interface encoder and unmarshal back into our struct. 170 | v = NumberJSONSample{} 171 | _, err = v.UnmarshalMsg(fromInt) 172 | if err != nil { 173 | t.Errorf("%v", err) 174 | } 175 | if v.Single != "-42" { 176 | t.Errorf("want %s, got %s", "-42", v.Single) 177 | } 178 | if v.Map["a"] != "50.2" { 179 | t.Errorf("want %s, got %s", "50.2", v.Map["a"]) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /_generated/embedded_struct.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | 5 | type GetUserRequestWithEmbeddedStruct struct { 6 | Common `msg:",flatten"` 7 | UserID uint32 `msg:"user_id"` 8 | } 9 | 10 | type GetUserRequest struct { 11 | RequestID uint32 `msg:"request_id"` 12 | Token string `msg:"token"` 13 | UserID uint32 `msg:"user_id"` 14 | } 15 | 16 | type Common struct { 17 | RequestID uint32 `msg:"request_id"` 18 | Token string `msg:"token"` 19 | } 20 | -------------------------------------------------------------------------------- /_generated/embedded_struct_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConvertDataFromAEmbeddedStructToANonEmbeddedStruct(t *testing.T) { 8 | getUserRequestWithEmbeddedStruct := GetUserRequestWithEmbeddedStruct{ 9 | Common: Common{ 10 | RequestID: 10, 11 | Token: "token", 12 | }, 13 | UserID: 1000, 14 | } 15 | 16 | bytes, err := getUserRequestWithEmbeddedStruct.MarshalMsg(nil) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | getUserRequest := GetUserRequest{} 22 | _, err = getUserRequest.UnmarshalMsg(bytes) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | if getUserRequest.RequestID != getUserRequestWithEmbeddedStruct.RequestID { 27 | t.Fatal("not same request id") 28 | } 29 | if getUserRequest.UserID != getUserRequestWithEmbeddedStruct.UserID { 30 | t.Fatal("not same user id") 31 | } 32 | if getUserRequest.Token != getUserRequestWithEmbeddedStruct.Token { 33 | t.Fatal("not same token") 34 | } 35 | 36 | return 37 | } 38 | 39 | func TestConvertDataFromANonEmbeddedStructToAEmbeddedStruct(t *testing.T) { 40 | getUserRequest := GetUserRequest{ 41 | RequestID: 10, 42 | Token: "token", 43 | UserID: 1000, 44 | } 45 | 46 | bytes, err := getUserRequest.MarshalMsg(nil) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | getUserRequestWithEmbeddedStruct := GetUserRequestWithEmbeddedStruct{} 52 | _, err = getUserRequestWithEmbeddedStruct.UnmarshalMsg(bytes) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if getUserRequest.RequestID != getUserRequestWithEmbeddedStruct.RequestID { 57 | t.Fatal("not same request id") 58 | } 59 | if getUserRequest.UserID != getUserRequestWithEmbeddedStruct.UserID { 60 | t.Fatal("not same user id") 61 | } 62 | if getUserRequest.Token != getUserRequestWithEmbeddedStruct.Token { 63 | t.Fatal("not same token") 64 | } 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /_generated/errorwrap.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | 5 | // The leaves of interest in this crazy structs are strings. The test case 6 | // looks for strings in the serialised msgpack and makes them unreadable. 7 | 8 | type ErrorCtxMapChild struct { 9 | Val string 10 | } 11 | 12 | type ErrorCtxMapChildNotInline struct { 13 | Val1, Val2, Val3, Val4, Val5 string 14 | } 15 | 16 | type ErrorCtxAsMap struct { 17 | Val string 18 | Child *ErrorCtxMapChild 19 | Children []*ErrorCtxMapChild 20 | ComplexChild *ErrorCtxMapChildNotInline 21 | Map map[string]string 22 | 23 | Nest struct { 24 | Val string 25 | Child *ErrorCtxMapChild 26 | Children []*ErrorCtxMapChild 27 | Map map[string]string 28 | 29 | Nest struct { 30 | Val string 31 | Child *ErrorCtxMapChild 32 | Children []*ErrorCtxMapChild 33 | Map map[string]string 34 | } 35 | } 36 | } 37 | 38 | //msgp:tuple ErrorCtxTupleChild 39 | 40 | type ErrorCtxTupleChild struct { 41 | Val string 42 | } 43 | 44 | //msgp:tuple ErrorCtxTupleChildNotInline 45 | 46 | type ErrorCtxTupleChildNotInline struct { 47 | Val1, Val2, Val3, Val4, Val5 string 48 | } 49 | 50 | //msgp:tuple ErrorCtxAsTuple 51 | 52 | type ErrorCtxAsTuple struct { 53 | Val string 54 | Child *ErrorCtxTupleChild 55 | Children []*ErrorCtxTupleChild 56 | ComplexChild *ErrorCtxTupleChildNotInline 57 | Map map[string]string 58 | 59 | Nest struct { 60 | Val string 61 | Child *ErrorCtxTupleChild 62 | Children []*ErrorCtxTupleChild 63 | Map map[string]string 64 | 65 | Nest struct { 66 | Val string 67 | Child *ErrorCtxTupleChild 68 | Children []*ErrorCtxTupleChild 69 | Map map[string]string 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /_generated/gen_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tinylib/msgp/msgp" 10 | ) 11 | 12 | // benchmark encoding a small, "fast" type. 13 | // the point here is to see how much garbage 14 | // is generated intrinsically by the encoding/ 15 | // decoding process as opposed to the nature 16 | // of the struct. 17 | func BenchmarkFastEncode(b *testing.B) { 18 | v := &TestFast{ 19 | Lat: 40.12398, 20 | Long: -41.9082, 21 | Alt: 201.08290, 22 | Data: []byte("whaaaaargharbl"), 23 | } 24 | var buf bytes.Buffer 25 | msgp.Encode(&buf, v) 26 | en := msgp.NewWriter(msgp.Nowhere) 27 | b.SetBytes(int64(buf.Len())) 28 | b.ReportAllocs() 29 | b.ResetTimer() 30 | for i := 0; i < b.N; i++ { 31 | v.EncodeMsg(en) 32 | } 33 | en.Flush() 34 | } 35 | 36 | // benchmark decoding a small, "fast" type. 37 | // the point here is to see how much garbage 38 | // is generated intrinsically by the encoding/ 39 | // decoding process as opposed to the nature 40 | // of the struct. 41 | func BenchmarkFastDecode(b *testing.B) { 42 | v := &TestFast{ 43 | Lat: 40.12398, 44 | Long: -41.9082, 45 | Alt: 201.08290, 46 | Data: []byte("whaaaaargharbl"), 47 | } 48 | 49 | var buf bytes.Buffer 50 | msgp.Encode(&buf, v) 51 | dc := msgp.NewReader(msgp.NewEndlessReader(buf.Bytes(), b)) 52 | b.SetBytes(int64(buf.Len())) 53 | b.ReportAllocs() 54 | b.ResetTimer() 55 | for i := 0; i < b.N; i++ { 56 | v.DecodeMsg(dc) 57 | } 58 | } 59 | 60 | func (a *TestType) Equal(b *TestType) bool { 61 | // compare times, appended, then zero out those 62 | // fields, perform a DeepEqual, and restore them 63 | ta, tb := a.Time, b.Time 64 | if !ta.Equal(tb) { 65 | return false 66 | } 67 | aa, ab := a.Appended, b.Appended 68 | if !bytes.Equal(aa, ab) { 69 | return false 70 | } 71 | if len(a.MapStringEmpty) == 0 && len(b.MapStringEmpty) == 0 { 72 | a.MapStringEmpty = nil 73 | b.MapStringEmpty = nil 74 | } 75 | if len(a.MapStringEmpty2) == 0 && len(b.MapStringEmpty2) == 0 { 76 | a.MapStringEmpty2 = nil 77 | b.MapStringEmpty2 = nil 78 | } 79 | 80 | a.Time, b.Time = time.Time{}, time.Time{} 81 | aa, ab = nil, nil 82 | ok := reflect.DeepEqual(a, b) 83 | a.Time, b.Time = ta, tb 84 | a.Appended, b.Appended = aa, ab 85 | return ok 86 | } 87 | 88 | // This covers the following cases: 89 | // - Recursive types 90 | // - Non-builtin identifiers (and recursive types) 91 | // - time.Time 92 | // - map[string]string 93 | // - anonymous structs 94 | func Test1EncodeDecode(t *testing.T) { 95 | f := 32.00 96 | tt := &TestType{ 97 | F: &f, 98 | Els: map[string]string{ 99 | "thing_one": "one", 100 | "thing_two": "two", 101 | }, 102 | Obj: struct { 103 | ValueA string `msg:"value_a"` 104 | ValueB []byte `msg:"value_b"` 105 | }{ 106 | ValueA: "here's the first inner value", 107 | ValueB: []byte("here's the second inner value"), 108 | }, 109 | Child: nil, 110 | Time: time.Now(), 111 | Appended: msgp.Raw([]byte{}), // 'nil' 112 | MapStringEmpty: map[string]struct{}{"Key": {}, "Key2": {}}, 113 | MapStringEmpty2: map[string]EmptyStruct{"Key3": {}, "Key4": {}}, 114 | } 115 | 116 | var buf bytes.Buffer 117 | 118 | err := msgp.Encode(&buf, tt) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | 123 | tnew := new(TestType) 124 | 125 | err = msgp.Decode(&buf, tnew) 126 | if err != nil { 127 | t.Error(err) 128 | } 129 | 130 | if !tt.Equal(tnew) { 131 | t.Logf("in: %#v", tt) 132 | t.Logf("out: %#v", tnew) 133 | t.Fatal("objects not equal") 134 | } 135 | if tnew.Time.Location() != time.UTC { 136 | t.Errorf("time location not UTC: %v", tnew.Time.Location()) 137 | } 138 | 139 | tanother := new(TestType) 140 | 141 | buf.Reset() 142 | msgp.Encode(&buf, tt) 143 | 144 | var left []byte 145 | left, err = tanother.UnmarshalMsg(buf.Bytes()) 146 | if err != nil { 147 | t.Error(err) 148 | } 149 | if len(left) > 0 { 150 | t.Errorf("%d bytes left", len(left)) 151 | } 152 | 153 | if !tt.Equal(tanother) { 154 | t.Logf("in: %v", tt) 155 | t.Logf("out: %v", tanother) 156 | t.Fatal("objects not equal") 157 | } 158 | if tanother.Time.Location() != time.UTC { 159 | t.Errorf("time location not UTC: %v", tanother.Time.Location()) 160 | } 161 | 162 | } 163 | 164 | func TestIssue168(t *testing.T) { 165 | buf := bytes.Buffer{} 166 | test := TestObj{} 167 | 168 | msgp.Encode(&buf, &TestObj{ID1: "1", ID2: "2"}) 169 | msgp.Decode(&buf, &test) 170 | 171 | if test.ID1 != "1" || test.ID2 != "2" { 172 | t.Fatalf("got back %+v", test) 173 | } 174 | } 175 | 176 | func TestIssue362(t *testing.T) { 177 | in := StructByteSlice{ 178 | ABytes: make([]byte, 0), 179 | AString: make([]string, 0), 180 | ABool: make([]bool, 0), 181 | AInt: make([]int, 0), 182 | AInt8: make([]int8, 0), 183 | AInt16: make([]int16, 0), 184 | AInt32: make([]int32, 0), 185 | AInt64: make([]int64, 0), 186 | AUint: make([]uint, 0), 187 | AUint8: make([]uint8, 0), 188 | AUint16: make([]uint16, 0), 189 | AUint32: make([]uint32, 0), 190 | AUint64: make([]uint64, 0), 191 | AFloat32: make([]float32, 0), 192 | AFloat64: make([]float64, 0), 193 | AComplex64: make([]complex64, 0), 194 | AComplex128: make([]complex128, 0), 195 | AStruct: make([]Fixed, 0), 196 | } 197 | 198 | b, err := in.MarshalMsg(nil) 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | 203 | var dst StructByteSlice 204 | _, err = dst.UnmarshalMsg(b) 205 | if err != nil { 206 | t.Fatal(err) 207 | } 208 | if !reflect.DeepEqual(in, dst) { 209 | t.Fatalf("mismatch %#v != %#v", in, dst) 210 | } 211 | dst2 := StructByteSlice{} 212 | dec := msgp.NewReader(bytes.NewReader(b)) 213 | err = dst2.DecodeMsg(dec) 214 | if err != nil { 215 | t.Fatal(err) 216 | } 217 | if !reflect.DeepEqual(in, dst2) { 218 | t.Fatalf("mismatch %#v != %#v", in, dst2) 219 | } 220 | 221 | // Encode with nil 222 | zero := StructByteSlice{} 223 | b, err = zero.MarshalMsg(nil) 224 | if err != nil { 225 | t.Fatal(err) 226 | } 227 | // Decode into dst that now has values... 228 | _, err = dst.UnmarshalMsg(b) 229 | if err != nil { 230 | t.Fatal(err) 231 | } 232 | // All should be nil now. 233 | if !reflect.DeepEqual(zero, dst) { 234 | t.Fatalf("mismatch %#v != %#v", zero, dst) 235 | } 236 | dec = msgp.NewReader(bytes.NewReader(b)) 237 | err = dst2.DecodeMsg(dec) 238 | if err != nil { 239 | t.Fatal(err) 240 | } 241 | if !reflect.DeepEqual(zero, dst2) { 242 | t.Fatalf("mismatch %#v != %#v", zero, dst2) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /_generated/issue102.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | 5 | type Issue102 struct{} 6 | 7 | type Issue102deep struct { 8 | A int 9 | X struct{} 10 | Y struct{} 11 | Z int 12 | } 13 | 14 | //msgp:tuple Issue102Tuple 15 | 16 | type Issue102Tuple struct{} 17 | 18 | //msgp:tuple Issue102TupleDeep 19 | 20 | type Issue102TupleDeep struct { 21 | A int 22 | X struct{} 23 | Y struct{} 24 | Z int 25 | } 26 | 27 | type Issue102Uses struct { 28 | Nested Issue102 29 | NestedPtr *Issue102 30 | } 31 | 32 | //msgp:tuple Issue102TupleUsesTuple 33 | 34 | type Issue102TupleUsesTuple struct { 35 | Nested Issue102Tuple 36 | NestedPtr *Issue102Tuple 37 | } 38 | 39 | //msgp:tuple Issue102TupleUsesMap 40 | 41 | type Issue102TupleUsesMap struct { 42 | Nested Issue102 43 | NestedPtr *Issue102 44 | } 45 | 46 | type Issue102MapUsesTuple struct { 47 | Nested Issue102Tuple 48 | NestedPtr *Issue102Tuple 49 | } 50 | -------------------------------------------------------------------------------- /_generated/issue191.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | //go:generate msgp 4 | 5 | type Issue191 struct { 6 | Foo string 7 | Bar string 8 | } 9 | -------------------------------------------------------------------------------- /_generated/issue191_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Issue #191: panic in unsafe.UnsafeString() 8 | 9 | func TestIssue191(t *testing.T) { 10 | b := []byte{0x81, 0xa0, 0xa0} 11 | var i Issue191 12 | _, err := (&i).UnmarshalMsg(b) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /_generated/issue94.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | //go:generate msgp 8 | 9 | //msgp:shim time.Time as:string using:timetostr/strtotime 10 | type T struct { 11 | T time.Time 12 | } 13 | 14 | func timetostr(t time.Time) string { 15 | return t.Format(time.RFC3339) 16 | } 17 | 18 | func strtotime(s string) time.Time { 19 | t, _ := time.Parse(time.RFC3339, s) 20 | return t 21 | } 22 | -------------------------------------------------------------------------------- /_generated/issue94_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | // Issue 94: shims were not propogated recursively, 10 | // which caused shims that weren't at the top level 11 | // to be silently ignored. 12 | // 13 | // The following line will generate an error after 14 | // the code is generated if the generated code doesn't 15 | // have the right identifier in it. 16 | func TestIssue94(t *testing.T) { 17 | b, err := os.ReadFile("issue94_gen.go") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | const want = "timetostr" 22 | if !bytes.Contains(b, []byte(want)) { 23 | t.Errorf("generated code did not contain %q", want) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /_generated/newtime.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "time" 4 | 5 | //go:generate msgp -v 6 | 7 | //msgp:newtime 8 | //msgp:timezone local 9 | 10 | type NewTime struct { 11 | T time.Time 12 | Array []time.Time 13 | Map map[string]time.Time 14 | } 15 | 16 | func (t1 NewTime) Equal(t2 NewTime) bool { 17 | if !t1.T.Equal(t2.T) { 18 | return false 19 | } 20 | if len(t1.Array) != len(t2.Array) { 21 | return false 22 | } 23 | for i := range t1.Array { 24 | if !t1.Array[i].Equal(t2.Array[i]) { 25 | return false 26 | } 27 | } 28 | if len(t1.Map) != len(t2.Map) { 29 | return false 30 | } 31 | for k, v := range t1.Map { 32 | if !t2.Map[k].Equal(v) { 33 | return false 34 | } 35 | } 36 | return true 37 | } 38 | -------------------------------------------------------------------------------- /_generated/newtime_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/tinylib/msgp/msgp" 10 | ) 11 | 12 | func TestNewTime(t *testing.T) { 13 | value := NewTime{ 14 | T: time.Now().UTC(), 15 | Array: []time.Time{time.Now().UTC(), time.Now().UTC()}, 16 | Map: map[string]time.Time{ 17 | "a": time.Now().UTC(), 18 | }, 19 | } 20 | encoded, err := value.MarshalMsg(nil) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | checkExtMinusOne(t, encoded) 25 | var got NewTime 26 | _, err = got.UnmarshalMsg(encoded) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | if !value.Equal(got) { 31 | t.Errorf("UnmarshalMsg got %v want %v", value, got) 32 | } 33 | if got.T.Location() != time.Local { 34 | t.Errorf("DecodeMsg got %v want %v", got.T.Location(), time.Local) 35 | } 36 | 37 | var buf bytes.Buffer 38 | w := msgp.NewWriter(&buf) 39 | err = value.EncodeMsg(w) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | w.Flush() 44 | checkExtMinusOne(t, buf.Bytes()) 45 | 46 | got = NewTime{} 47 | r := msgp.NewReader(&buf) 48 | err = got.DecodeMsg(r) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if !value.Equal(got) { 53 | t.Errorf("DecodeMsg got %v want %v", value, got) 54 | } 55 | if got.T.Location() != time.Local { 56 | t.Errorf("DecodeMsg got %v want %v", got.T.Location(), time.Local) 57 | } 58 | } 59 | 60 | func checkExtMinusOne(t *testing.T, b []byte) { 61 | r := msgp.NewReader(bytes.NewBuffer(b)) 62 | _, err := r.ReadMapHeader() 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | key, err := r.ReadMapKey(nil) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | for !bytes.Equal(key, []byte("T")) { 71 | key, err = r.ReadMapKey(nil) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | } 76 | n, _, err := r.ReadExtensionRaw() 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | if n != -1 { 81 | t.Fatalf("got %v want -1", n) 82 | } 83 | t.Log("Was -1 extension") 84 | } 85 | 86 | func TestNewTimeRandom(t *testing.T) { 87 | rng := rand.New(rand.NewSource(0)) 88 | runs := int(1e6) 89 | if testing.Short() { 90 | runs = 1e4 91 | } 92 | for i := 0; i < runs; i++ { 93 | nanos := rng.Int63n(999999999 + 1) 94 | secs := rng.Uint64() 95 | // Tweak the distribution, so we get more than average number of 96 | // length 4 and 8 timestamps. 97 | if rng.Intn(5) == 0 { 98 | secs %= uint64(time.Now().Unix()) 99 | if rng.Intn(2) == 0 { 100 | nanos = 0 101 | } 102 | } 103 | 104 | value := NewTime{ 105 | T: time.Unix(int64(secs), nanos), 106 | } 107 | encoded, err := value.MarshalMsg(nil) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | var got NewTime 112 | _, err = got.UnmarshalMsg(encoded) 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | if !value.Equal(got) { 117 | t.Fatalf("UnmarshalMsg got %v want %v", value, got) 118 | } 119 | var buf bytes.Buffer 120 | w := msgp.NewWriter(&buf) 121 | err = value.EncodeMsg(w) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | w.Flush() 126 | got = NewTime{} 127 | r := msgp.NewReader(&buf) 128 | err = got.DecodeMsg(r) 129 | if err != nil { 130 | t.Fatal(err) 131 | } 132 | if !value.Equal(got) { 133 | t.Fatalf("DecodeMsg got %v want %v", value, got) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /_generated/omitempty_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/tinylib/msgp/msgp" 10 | ) 11 | 12 | func mustEncodeToJSON(o msgp.Encodable) string { 13 | var buf bytes.Buffer 14 | var err error 15 | 16 | en := msgp.NewWriter(&buf) 17 | err = o.EncodeMsg(en) 18 | if err != nil { 19 | panic(err) 20 | } 21 | en.Flush() 22 | 23 | var outbuf bytes.Buffer 24 | _, err = msgp.CopyToJSON(&outbuf, &buf) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | return outbuf.String() 30 | } 31 | 32 | func TestOmitEmpty0(t *testing.T) { 33 | var s string 34 | 35 | var oe0a OmitEmpty0 36 | 37 | s = mustEncodeToJSON(&oe0a) 38 | if s != `{"aunnamedstruct":{},"aarrayint":[0,0,0,0,0]}` { 39 | t.Errorf("wrong result: %s", s) 40 | } 41 | 42 | var oe0b OmitEmpty0 43 | oe0b.AString = "teststr" 44 | s = mustEncodeToJSON(&oe0b) 45 | if s != `{"astring":"teststr","aunnamedstruct":{},"aarrayint":[0,0,0,0,0]}` { 46 | t.Errorf("wrong result: %s", s) 47 | } 48 | 49 | // more than 15 fields filled in 50 | var oe0c OmitEmpty0 51 | oe0c.ABool = true 52 | oe0c.AInt = 1 53 | oe0c.AInt8 = 1 54 | oe0c.AInt16 = 1 55 | oe0c.AInt32 = 1 56 | oe0c.AInt64 = 1 57 | oe0c.AUint = 1 58 | oe0c.AUint8 = 1 59 | oe0c.AUint16 = 1 60 | oe0c.AUint32 = 1 61 | oe0c.AUint64 = 1 62 | oe0c.AFloat32 = 1 63 | oe0c.AFloat64 = 1 64 | oe0c.AComplex64 = complex(1, 1) 65 | oe0c.AComplex128 = complex(1, 1) 66 | oe0c.AString = "test" 67 | oe0c.ANamedBool = true 68 | oe0c.ANamedInt = 1 69 | oe0c.ANamedFloat64 = 1 70 | 71 | var buf bytes.Buffer 72 | en := msgp.NewWriter(&buf) 73 | err := oe0c.EncodeMsg(en) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | en.Flush() 78 | de := msgp.NewReader(&buf) 79 | var oe0d OmitEmpty0 80 | err = oe0d.DecodeMsg(de) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | // spot check some fields 86 | if oe0c.AFloat32 != oe0d.AFloat32 { 87 | t.Fail() 88 | } 89 | if oe0c.ANamedBool != oe0d.ANamedBool { 90 | t.Fail() 91 | } 92 | if oe0c.AInt64 != oe0d.AInt64 { 93 | t.Fail() 94 | } 95 | } 96 | 97 | func TestOmitEmptyNoNames(t *testing.T) { 98 | var s string 99 | 100 | var oe0a OmitEmptyNoName 101 | 102 | s = mustEncodeToJSON(&oe0a) 103 | if s != `{"AUnnamedStruct":{},"AArrayInt":[0,0,0,0,0]}` { 104 | t.Errorf("wrong result: %s", s) 105 | } 106 | 107 | var oe0b OmitEmptyNoName 108 | oe0b.AString = "teststr" 109 | s = mustEncodeToJSON(&oe0b) 110 | if s != `{"AString":"teststr","AUnnamedStruct":{},"AArrayInt":[0,0,0,0,0]}` { 111 | t.Errorf("wrong result: %s", s) 112 | } 113 | 114 | // more than 15 fields filled in 115 | var oe0c OmitEmptyNoName 116 | oe0c.ABool = true 117 | oe0c.AInt = 1 118 | oe0c.AInt8 = 1 119 | oe0c.AInt16 = 1 120 | oe0c.AInt32 = 1 121 | oe0c.AInt64 = 1 122 | oe0c.AUint = 1 123 | oe0c.AUint8 = 1 124 | oe0c.AUint16 = 1 125 | oe0c.AUint32 = 1 126 | oe0c.AUint64 = 1 127 | oe0c.AFloat32 = 1 128 | oe0c.AFloat64 = 1 129 | oe0c.AComplex64 = complex(1, 1) 130 | oe0c.AComplex128 = complex(1, 1) 131 | oe0c.AString = "test" 132 | oe0c.ANamedBool = true 133 | oe0c.ANamedInt = 1 134 | oe0c.ANamedFloat64 = 1 135 | 136 | var buf bytes.Buffer 137 | en := msgp.NewWriter(&buf) 138 | err := oe0c.EncodeMsg(en) 139 | if err != nil { 140 | t.Fatal(err) 141 | } 142 | en.Flush() 143 | de := msgp.NewReader(&buf) 144 | var oe0d OmitEmptyNoName 145 | err = oe0d.DecodeMsg(de) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | // spot check some fields 151 | if oe0c.AFloat32 != oe0d.AFloat32 { 152 | t.Fail() 153 | } 154 | if oe0c.ANamedBool != oe0d.ANamedBool { 155 | t.Fail() 156 | } 157 | if oe0c.AInt64 != oe0d.AInt64 { 158 | t.Fail() 159 | } 160 | } 161 | 162 | // TestOmitEmptyHalfFull tests mixed omitempty and not 163 | func TestOmitEmptyHalfFull(t *testing.T) { 164 | var s string 165 | 166 | var oeA OmitEmptyHalfFull 167 | 168 | s = mustEncodeToJSON(&oeA) 169 | if s != `{"field01":"","field03":""}` { 170 | t.Errorf("wrong result: %s", s) 171 | } 172 | 173 | var oeB OmitEmptyHalfFull 174 | oeB.Field02 = "val2" 175 | s = mustEncodeToJSON(&oeB) 176 | if s != `{"field01":"","field02":"val2","field03":""}` { 177 | t.Errorf("wrong result: %s", s) 178 | } 179 | 180 | var oeC OmitEmptyHalfFull 181 | oeC.Field03 = "val3" 182 | s = mustEncodeToJSON(&oeC) 183 | if s != `{"field01":"","field03":"val3"}` { 184 | t.Errorf("wrong result: %s", s) 185 | } 186 | } 187 | 188 | // TestOmitEmptyLotsOFields tests the case of > 64 fields (triggers the bitmask needing to be an array instead of a single value) 189 | func TestOmitEmptyLotsOFields(t *testing.T) { 190 | var s string 191 | 192 | var oeLotsA OmitEmptyLotsOFields 193 | 194 | s = mustEncodeToJSON(&oeLotsA) 195 | if s != `{}` { 196 | t.Errorf("wrong result: %s", s) 197 | } 198 | 199 | var oeLotsB OmitEmptyLotsOFields 200 | oeLotsB.Field04 = "val4" 201 | s = mustEncodeToJSON(&oeLotsB) 202 | if s != `{"field04":"val4"}` { 203 | t.Errorf("wrong result: %s", s) 204 | } 205 | 206 | var oeLotsC OmitEmptyLotsOFields 207 | oeLotsC.Field64 = "val64" 208 | s = mustEncodeToJSON(&oeLotsC) 209 | if s != `{"field64":"val64"}` { 210 | t.Errorf("wrong result: %s", s) 211 | } 212 | } 213 | 214 | func BenchmarkOmitEmpty10AllEmpty(b *testing.B) { 215 | en := msgp.NewWriter(io.Discard) 216 | var s OmitEmpty10 217 | 218 | b.ResetTimer() 219 | 220 | for i := 0; i < b.N; i++ { 221 | err := s.EncodeMsg(en) 222 | if err != nil { 223 | b.Fatal(err) 224 | } 225 | } 226 | } 227 | 228 | func BenchmarkOmitEmpty10AllFull(b *testing.B) { 229 | en := msgp.NewWriter(io.Discard) 230 | var s OmitEmpty10 231 | s.Field00 = "this is the value of field00" 232 | s.Field01 = "this is the value of field01" 233 | s.Field02 = "this is the value of field02" 234 | s.Field03 = "this is the value of field03" 235 | s.Field04 = "this is the value of field04" 236 | s.Field05 = "this is the value of field05" 237 | s.Field06 = "this is the value of field06" 238 | s.Field07 = "this is the value of field07" 239 | s.Field08 = "this is the value of field08" 240 | s.Field09 = "this is the value of field09" 241 | 242 | b.ResetTimer() 243 | 244 | for i := 0; i < b.N; i++ { 245 | err := s.EncodeMsg(en) 246 | if err != nil { 247 | b.Fatal(err) 248 | } 249 | } 250 | } 251 | 252 | func BenchmarkNotOmitEmpty10AllEmpty(b *testing.B) { 253 | en := msgp.NewWriter(io.Discard) 254 | var s NotOmitEmpty10 255 | 256 | b.ResetTimer() 257 | 258 | for i := 0; i < b.N; i++ { 259 | err := s.EncodeMsg(en) 260 | if err != nil { 261 | b.Fatal(err) 262 | } 263 | } 264 | } 265 | 266 | func BenchmarkNotOmitEmpty10AllFull(b *testing.B) { 267 | en := msgp.NewWriter(io.Discard) 268 | var s NotOmitEmpty10 269 | s.Field00 = "this is the value of field00" 270 | s.Field01 = "this is the value of field01" 271 | s.Field02 = "this is the value of field02" 272 | s.Field03 = "this is the value of field03" 273 | s.Field04 = "this is the value of field04" 274 | s.Field05 = "this is the value of field05" 275 | s.Field06 = "this is the value of field06" 276 | s.Field07 = "this is the value of field07" 277 | s.Field08 = "this is the value of field08" 278 | s.Field09 = "this is the value of field09" 279 | 280 | b.ResetTimer() 281 | 282 | for i := 0; i < b.N; i++ { 283 | err := s.EncodeMsg(en) 284 | if err != nil { 285 | b.Fatal(err) 286 | } 287 | } 288 | } 289 | 290 | func TestTypeAlias(t *testing.T) { 291 | value := TypeSamples{TypeSample{}, TypeSample{K: 1, V: 2}} 292 | encoded, err := value.MarshalMsg(nil) 293 | if err != nil { 294 | t.Fatal(err) 295 | } 296 | var got TypeSamples 297 | _, err = got.UnmarshalMsg(encoded) 298 | if err != nil { 299 | t.Fatal(err) 300 | } 301 | if !reflect.DeepEqual(value, got) { 302 | t.Errorf("UnmarshalMsg got %v want %v", value, got) 303 | } 304 | var buf bytes.Buffer 305 | w := msgp.NewWriter(&buf) 306 | err = value.EncodeMsg(w) 307 | if err != nil { 308 | t.Fatal(err) 309 | } 310 | w.Flush() 311 | got = TypeSamples{} 312 | r := msgp.NewReader(&buf) 313 | err = got.DecodeMsg(r) 314 | if err != nil { 315 | t.Fatal(err) 316 | } 317 | if !reflect.DeepEqual(value, got) { 318 | t.Errorf("UnmarshalMsg got %v want %v", value, got) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /_generated/omitzero.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "time" 4 | 5 | //go:generate msgp 6 | 7 | // check some specific cases for omitzero 8 | 9 | type OmitZero0 struct { 10 | AStruct OmitZeroA `msg:"astruct,omitempty"` // leave this one omitempty 11 | BStruct OmitZeroA `msg:"bstruct,omitzero"` // and compare to this 12 | AStructPtr *OmitZeroA `msg:"astructptr,omitempty"` // a pointer case omitempty 13 | BStructPtr *OmitZeroA `msg:"bstructptr,omitzero"` // a pointer case omitzero 14 | AExt OmitZeroExt `msg:"aext,omitzero"` // external type case 15 | AExtPtr *OmitZeroExtPtr `msg:"aextptr,omitzero"` // external type pointer case 16 | 17 | // more 18 | APtrNamedStr *NamedStringOZ `msg:"aptrnamedstr,omitzero"` 19 | ANamedStruct NamedStructOZ `msg:"anamedstruct,omitzero"` 20 | APtrNamedStruct *NamedStructOZ `msg:"aptrnamedstruct,omitzero"` 21 | EmbeddableStruct `msg:",flatten,omitzero"` // embed flat 22 | EmbeddableStructOZ `msg:"embeddablestruct2,omitzero"` // embed non-flat 23 | ATime time.Time `msg:"atime,omitzero"` 24 | 25 | OmitZeroTuple OmitZeroTuple `msg:"ozt"` // the inside of a tuple should ignore both omitempty and omitzero 26 | } 27 | 28 | type OmitZeroA struct { 29 | A string `msg:"a,omitempty"` 30 | B NamedStringOZ `msg:"b,omitzero"` 31 | C NamedStringOZ `msg:"c,omitzero"` 32 | } 33 | 34 | func (o *OmitZeroA) IsZero() bool { 35 | if o == nil { 36 | return true 37 | } 38 | return *o == (OmitZeroA{}) 39 | } 40 | 41 | type NamedStructOZ struct { 42 | A string `msg:"a,omitempty"` 43 | B string `msg:"b,omitempty"` 44 | } 45 | 46 | func (ns *NamedStructOZ) IsZero() bool { 47 | if ns == nil { 48 | return true 49 | } 50 | return *ns == (NamedStructOZ{}) 51 | } 52 | 53 | type NamedStringOZ string 54 | 55 | func (ns *NamedStringOZ) IsZero() bool { 56 | if ns == nil { 57 | return true 58 | } 59 | return *ns == "" 60 | } 61 | 62 | type EmbeddableStructOZ struct { 63 | SomeEmbed string `msg:"someembed2,omitempty"` 64 | } 65 | 66 | func (es EmbeddableStructOZ) IsZero() bool { return es == (EmbeddableStructOZ{}) } 67 | 68 | type EmbeddableStructOZ2 struct { 69 | SomeEmbed2 string `msg:"someembed2,omitempty"` 70 | } 71 | 72 | func (es EmbeddableStructOZ2) IsZero() bool { return es == (EmbeddableStructOZ2{}) } 73 | 74 | //msgp:tuple OmitZeroTuple 75 | 76 | // OmitZeroTuple is flagged for tuple output, it should ignore all omitempty and omitzero functionality 77 | // since it's fundamentally incompatible. 78 | type OmitZeroTuple struct { 79 | FieldA string `msg:"fielda,omitempty"` 80 | FieldB NamedStringOZ `msg:"fieldb,omitzero"` 81 | FieldC NamedStringOZ `msg:"fieldc,omitzero"` 82 | } 83 | 84 | type OmitZero1 struct { 85 | T1 OmitZeroTuple `msg:"t1"` 86 | } 87 | -------------------------------------------------------------------------------- /_generated/omitzero_ext.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "github.com/tinylib/msgp/msgp" 5 | ) 6 | 7 | // this has "external" types that will show up 8 | // as generic IDENT during code generation 9 | 10 | type OmitZeroExt struct { 11 | a int // custom type 12 | } 13 | 14 | // IsZero will return true if a is not positive 15 | func (o OmitZeroExt) IsZero() bool { return o.a <= 0 } 16 | 17 | // EncodeMsg implements msgp.Encodable 18 | func (o OmitZeroExt) EncodeMsg(en *msgp.Writer) (err error) { 19 | if o.a > 0 { 20 | return en.WriteInt(o.a) 21 | } 22 | return en.WriteNil() 23 | } 24 | 25 | // DecodeMsg implements msgp.Decodable 26 | func (o *OmitZeroExt) DecodeMsg(dc *msgp.Reader) (err error) { 27 | if dc.IsNil() { 28 | err = dc.ReadNil() 29 | if err != nil { 30 | return 31 | } 32 | o.a = 0 33 | return 34 | } 35 | o.a, err = dc.ReadInt() 36 | return err 37 | } 38 | 39 | // MarshalMsg implements msgp.Marshaler 40 | func (o OmitZeroExt) MarshalMsg(b []byte) (ret []byte, err error) { 41 | ret = msgp.Require(b, o.Msgsize()) 42 | if o.a > 0 { 43 | return msgp.AppendInt(ret, o.a), nil 44 | } 45 | return msgp.AppendNil(ret), nil 46 | } 47 | 48 | // UnmarshalMsg implements msgp.Unmarshaler 49 | func (o *OmitZeroExt) UnmarshalMsg(bts []byte) (ret []byte, err error) { 50 | if msgp.IsNil(bts) { 51 | bts, err = msgp.ReadNilBytes(bts) 52 | return bts, err 53 | } 54 | o.a, bts, err = msgp.ReadIntBytes(bts) 55 | return bts, err 56 | } 57 | 58 | // Msgsize implements msgp.Msgsizer 59 | func (o OmitZeroExt) Msgsize() (s int) { 60 | return msgp.IntSize 61 | } 62 | 63 | type OmitZeroExtPtr struct { 64 | a int // custom type 65 | } 66 | 67 | // IsZero will return true if a is nil or not positive 68 | func (o *OmitZeroExtPtr) IsZero() bool { return o == nil || o.a <= 0 } 69 | 70 | // EncodeMsg implements msgp.Encodable 71 | func (o *OmitZeroExtPtr) EncodeMsg(en *msgp.Writer) (err error) { 72 | if o.a > 0 { 73 | return en.WriteInt(o.a) 74 | } 75 | return en.WriteNil() 76 | } 77 | 78 | // DecodeMsg implements msgp.Decodable 79 | func (o *OmitZeroExtPtr) DecodeMsg(dc *msgp.Reader) (err error) { 80 | if dc.IsNil() { 81 | err = dc.ReadNil() 82 | if err != nil { 83 | return 84 | } 85 | o.a = 0 86 | return 87 | } 88 | o.a, err = dc.ReadInt() 89 | return err 90 | } 91 | 92 | // MarshalMsg implements msgp.Marshaler 93 | func (o *OmitZeroExtPtr) MarshalMsg(b []byte) (ret []byte, err error) { 94 | ret = msgp.Require(b, o.Msgsize()) 95 | if o.a > 0 { 96 | return msgp.AppendInt(ret, o.a), nil 97 | } 98 | return msgp.AppendNil(ret), nil 99 | } 100 | 101 | // UnmarshalMsg implements msgp.Unmarshaler 102 | func (o *OmitZeroExtPtr) UnmarshalMsg(bts []byte) (ret []byte, err error) { 103 | if msgp.IsNil(bts) { 104 | bts, err = msgp.ReadNilBytes(bts) 105 | return bts, err 106 | } 107 | o.a, bts, err = msgp.ReadIntBytes(bts) 108 | return bts, err 109 | } 110 | 111 | // Msgsize implements msgp.Msgsizer 112 | func (o *OmitZeroExtPtr) Msgsize() (s int) { 113 | return msgp.IntSize 114 | } 115 | -------------------------------------------------------------------------------- /_generated/omitzero_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestOmitZero(t *testing.T) { 9 | 10 | t.Run("OmitZeroExt_not_empty", func(t *testing.T) { 11 | 12 | z := OmitZero0{AExt: OmitZeroExt{a: 1}} 13 | b, err := z.MarshalMsg(nil) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | if !bytes.Contains(b, []byte("aext")) { 18 | t.Errorf("expected to find aext in bytes %X", b) 19 | } 20 | z = OmitZero0{} 21 | _, err = z.UnmarshalMsg(b) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if z.AExt.a != 1 { 26 | t.Errorf("z.AExt.a expected 1 but got %d", z.AExt.a) 27 | } 28 | 29 | }) 30 | 31 | t.Run("OmitZeroExt_negative", func(t *testing.T) { 32 | 33 | z := OmitZero0{AExt: OmitZeroExt{a: -1}} // negative value should act as empty, via IsEmpty() call 34 | b, err := z.MarshalMsg(nil) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if bytes.Contains(b, []byte("aext")) { 39 | t.Errorf("expected to not find aext in bytes %X", b) 40 | } 41 | z = OmitZero0{} 42 | _, err = z.UnmarshalMsg(b) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | if z.AExt.a != 0 { 47 | t.Errorf("z.AExt.a expected 0 but got %d", z.AExt.a) 48 | } 49 | 50 | }) 51 | 52 | t.Run("OmitZeroTuple", func(t *testing.T) { 53 | 54 | // make sure tuple encoding isn't affected by omitempty or omitzero 55 | 56 | z := OmitZero0{OmitZeroTuple: OmitZeroTuple{FieldA: "", FieldB: "", FieldC: "fcval"}} 57 | b, err := z.MarshalMsg(nil) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | // verify the exact binary encoding, that the values follow each other without field names 62 | if !bytes.Contains(b, []byte{0xA0, 0xA0, 0xA5, 'f', 'c', 'v', 'a', 'l'}) { 63 | t.Errorf("failed to find expected bytes in %X", b) 64 | } 65 | z = OmitZero0{} 66 | _, err = z.UnmarshalMsg(b) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | if z.OmitZeroTuple.FieldA != "" || 71 | z.OmitZeroTuple.FieldB != "" || 72 | z.OmitZeroTuple.FieldC != "fcval" { 73 | t.Errorf("z.OmitZeroTuple unexpected value: %#v", z.OmitZeroTuple) 74 | } 75 | 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /_generated/pointer.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/tinylib/msgp/msgp" 8 | ) 9 | 10 | //go:generate msgp $GOFILE$ 11 | 12 | // Generate only pointer receivers: 13 | 14 | //msgp:pointer 15 | 16 | var mustNoInterf = []interface{}{ 17 | Pointer0{}, 18 | NamedBoolPointer(true), 19 | NamedIntPointer(0), 20 | NamedFloat64Pointer(0), 21 | NamedStringPointer(""), 22 | NamedMapStructPointer(nil), 23 | NamedMapStructPointer2(nil), 24 | NamedMapStringPointer(nil), 25 | NamedMapStringPointer2(nil), 26 | EmbeddableStructPointer{}, 27 | EmbeddableStruct2Pointer{}, 28 | PointerHalfFull{}, 29 | PointerNoName{}, 30 | } 31 | 32 | var mustHaveInterf = []interface{}{ 33 | &Pointer0{}, 34 | mustPtr(NamedBoolPointer(true)), 35 | mustPtr(NamedIntPointer(0)), 36 | mustPtr(NamedFloat64Pointer(0)), 37 | mustPtr(NamedStringPointer("")), 38 | mustPtr(NamedMapStructPointer(nil)), 39 | mustPtr(NamedMapStructPointer2(nil)), 40 | mustPtr(NamedMapStringPointer(nil)), 41 | mustPtr(NamedMapStringPointer2(nil)), 42 | &EmbeddableStructPointer{}, 43 | &EmbeddableStruct2Pointer{}, 44 | &PointerHalfFull{}, 45 | &PointerNoName{}, 46 | } 47 | 48 | func mustPtr[T any](v T) *T { 49 | return &v 50 | } 51 | 52 | func init() { 53 | for _, v := range mustNoInterf { 54 | if _, ok := v.(msgp.Marshaler); ok { 55 | panic(fmt.Sprintf("type %T supports interface", v)) 56 | } 57 | if _, ok := v.(msgp.Encodable); ok { 58 | panic(fmt.Sprintf("type %T supports interface", v)) 59 | } 60 | } 61 | for _, v := range mustHaveInterf { 62 | if _, ok := v.(msgp.Marshaler); !ok { 63 | panic(fmt.Sprintf("type %T does not support interface", v)) 64 | } 65 | if _, ok := v.(msgp.Encodable); !ok { 66 | panic(fmt.Sprintf("type %T does not support interface", v)) 67 | } 68 | } 69 | } 70 | 71 | type Pointer0 struct { 72 | ABool bool `msg:"abool"` 73 | AInt int `msg:"aint"` 74 | AInt8 int8 `msg:"aint8"` 75 | AInt16 int16 `msg:"aint16"` 76 | AInt32 int32 `msg:"aint32"` 77 | AInt64 int64 `msg:"aint64"` 78 | AUint uint `msg:"auint"` 79 | AUint8 uint8 `msg:"auint8"` 80 | AUint16 uint16 `msg:"auint16"` 81 | AUint32 uint32 `msg:"auint32"` 82 | AUint64 uint64 `msg:"auint64"` 83 | AFloat32 float32 `msg:"afloat32"` 84 | AFloat64 float64 `msg:"afloat64"` 85 | AComplex64 complex64 `msg:"acomplex64"` 86 | AComplex128 complex128 `msg:"acomplex128"` 87 | 88 | ANamedBool bool `msg:"anamedbool"` 89 | ANamedInt int `msg:"anamedint"` 90 | ANamedFloat64 float64 `msg:"anamedfloat64"` 91 | 92 | AMapStrStr map[string]string `msg:"amapstrstr"` 93 | 94 | APtrNamedStr *NamedString `msg:"aptrnamedstr"` 95 | 96 | AString string `msg:"astring"` 97 | ANamedString string `msg:"anamedstring"` 98 | AByteSlice []byte `msg:"abyteslice"` 99 | 100 | ASliceString []string `msg:"aslicestring"` 101 | ASliceNamedString []NamedString `msg:"aslicenamedstring"` 102 | 103 | ANamedStruct NamedStruct `msg:"anamedstruct"` 104 | APtrNamedStruct *NamedStruct `msg:"aptrnamedstruct"` 105 | 106 | AUnnamedStruct struct { 107 | A string `msg:"a"` 108 | } `msg:"aunnamedstruct"` // omitempty not supported on unnamed struct 109 | 110 | EmbeddableStruct `msg:",flatten"` // embed flat 111 | 112 | EmbeddableStruct2 `msg:"embeddablestruct2"` // embed non-flat 113 | 114 | AArrayInt [5]int `msg:"aarrayint"` // not supported 115 | 116 | ATime time.Time `msg:"atime"` 117 | } 118 | 119 | type ( 120 | NamedBoolPointer bool 121 | NamedIntPointer int 122 | NamedFloat64Pointer float64 123 | NamedStringPointer string 124 | NamedMapStructPointer map[string]Pointer0 125 | NamedMapStructPointer2 map[string]*Pointer0 126 | NamedMapStringPointer map[string]NamedStringPointer 127 | NamedMapStringPointer2 map[string]*NamedStringPointer 128 | ) 129 | 130 | type EmbeddableStructPointer struct { 131 | SomeEmbed string `msg:"someembed"` 132 | } 133 | 134 | type EmbeddableStruct2Pointer struct { 135 | SomeEmbed2 string `msg:"someembed2"` 136 | } 137 | 138 | type NamedStructPointer struct { 139 | A string `msg:"a"` 140 | B string `msg:"b"` 141 | } 142 | 143 | type PointerHalfFull struct { 144 | Field00 string `msg:"field00"` 145 | Field01 string `msg:"field01"` 146 | Field02 string `msg:"field02"` 147 | Field03 string `msg:"field03"` 148 | } 149 | 150 | type PointerNoName struct { 151 | ABool bool `msg:""` 152 | AInt int `msg:""` 153 | AInt8 int8 `msg:""` 154 | AInt16 int16 `msg:""` 155 | AInt32 int32 `msg:""` 156 | AInt64 int64 `msg:""` 157 | AUint uint `msg:""` 158 | AUint8 uint8 `msg:""` 159 | AUint16 uint16 `msg:""` 160 | AUint32 uint32 `msg:""` 161 | AUint64 uint64 `msg:""` 162 | AFloat32 float32 `msg:""` 163 | AFloat64 float64 `msg:""` 164 | AComplex64 complex64 `msg:""` 165 | AComplex128 complex128 `msg:""` 166 | 167 | ANamedBool bool `msg:""` 168 | ANamedInt int `msg:""` 169 | ANamedFloat64 float64 `msg:""` 170 | 171 | AMapStrF map[string]NamedFloat64Pointer `msg:""` 172 | AMapStrStruct map[string]PointerHalfFull `msg:""` 173 | AMapStrStruct2 map[string]*PointerHalfFull `msg:""` 174 | 175 | APtrNamedStr *NamedStringPointer `msg:""` 176 | 177 | AString string `msg:""` 178 | AByteSlice []byte `msg:""` 179 | 180 | ASliceString []string `msg:""` 181 | ASliceNamedString []NamedStringPointer `msg:""` 182 | 183 | ANamedStruct NamedStructPointer `msg:""` 184 | APtrNamedStruct *NamedStructPointer `msg:""` 185 | 186 | AUnnamedStruct struct { 187 | A string `msg:""` 188 | } `msg:""` // omitempty not supported on unnamed struct 189 | 190 | EmbeddableStructPointer `msg:",flatten"` // embed flat 191 | 192 | EmbeddableStruct2Pointer `msg:""` // embed non-flat 193 | 194 | AArrayInt [5]int `msg:""` // not supported 195 | 196 | ATime time.Time `msg:""` 197 | ADur time.Duration `msg:""` 198 | } 199 | -------------------------------------------------------------------------------- /_generated/replace.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "encoding/json" 4 | 5 | //go:generate msgp 6 | //msgp:replace Any with:any 7 | //msgp:replace MapString with:CompatibleMapString 8 | //msgp:replace MapAny with:map[string]any 9 | //msgp:replace SliceString with:[]string 10 | //msgp:replace SliceInt with:CompatibleSliceInt 11 | //msgp:replace Array8 with:CompatibleArray8 12 | //msgp:replace Array16 with:[16]byte 13 | //msgp:replace String with:string 14 | //msgp:replace Int with:CompatibleInt 15 | //msgp:replace Uint with:uint 16 | //msgp:replace Float32 with:CompatibleFloat32 17 | //msgp:replace Float64 with:CompatibleFloat64 18 | //msgp:replace Time with:time.Time 19 | //msgp:replace Duration with:time.Duration 20 | //msgp:replace StructA with:CompatibleStructA 21 | //msgp:replace StructB with:CompatibleStructB 22 | //msgp:replace StructC with:CompatibleStructC 23 | //msgp:replace StructD with:CompatibleStructD 24 | //msgp:replace StructI with:CompatibleStructI 25 | //msgp:replace StructS with:CompatibleStructS 26 | 27 | type ( 28 | CompatibleMapString map[string]string 29 | CompatibleArray8 [8]byte 30 | CompatibleInt int 31 | CompatibleFloat32 float32 32 | CompatibleFloat64 float64 33 | CompatibleSliceInt []Int 34 | 35 | // Doesn't work 36 | // CompatibleTime time.Time 37 | 38 | CompatibleStructA struct { 39 | StructB StructB 40 | Int Int 41 | } 42 | 43 | CompatibleStructB struct { 44 | StructC StructC 45 | Any Any 46 | Array8 Array8 47 | } 48 | 49 | CompatibleStructC struct { 50 | StructD StructD 51 | Float64 Float32 52 | Float32 Float64 53 | } 54 | 55 | CompatibleStructD struct { 56 | Time Time 57 | Duration Duration 58 | MapString MapString 59 | } 60 | 61 | CompatibleStructI struct { 62 | Int *Int 63 | Uint *Uint 64 | } 65 | 66 | CompatibleStructS struct { 67 | Slice SliceInt 68 | } 69 | 70 | Dummy struct { 71 | StructA StructA 72 | StructI StructI 73 | StructS StructS 74 | Array16 Array16 75 | Uint Uint 76 | String String 77 | } 78 | ) 79 | 80 | //msgp:replace json.Number with:string 81 | 82 | type NumberJSONSampleReplace struct { 83 | Single json.Number 84 | Array []json.Number 85 | Map map[string]json.Number 86 | } 87 | -------------------------------------------------------------------------------- /_generated/replace_ext.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "time" 4 | 5 | // external types to test replace directive 6 | 7 | type ( 8 | MapString map[string]string 9 | MapAny map[string]any 10 | SliceString []String 11 | SliceInt []Int 12 | Array8 [8]byte 13 | Array16 [16]byte 14 | Int int 15 | Uint uint 16 | String string 17 | Float32 float32 18 | Float64 float64 19 | Time time.Time 20 | Duration time.Duration 21 | Any any 22 | 23 | StructA struct { 24 | StructB StructB 25 | Int Int 26 | } 27 | 28 | StructB struct { 29 | StructC StructC 30 | Any Any 31 | Array8 Array8 32 | } 33 | 34 | StructC struct { 35 | StructD StructD 36 | Float64 Float32 37 | Float32 Float64 38 | } 39 | 40 | StructD struct { 41 | Time Time 42 | Duration Duration 43 | MapString MapString 44 | } 45 | 46 | StructI struct { 47 | Int *Int 48 | Uint *Uint 49 | } 50 | 51 | StructS struct { 52 | Slice SliceInt 53 | } 54 | ) 55 | -------------------------------------------------------------------------------- /_generated/setof.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "github.com/tinylib/msgp/msgp/setof" 4 | 5 | //go:generate msgp 6 | type SetOf struct { 7 | String setof.String 8 | StringSorted setof.StringSorted 9 | Int setof.Int 10 | IntSorted setof.IntSorted 11 | Int64 setof.Int64 12 | Int64Sorted setof.Int64Sorted 13 | Int32 setof.Int32 14 | Int32Sorted setof.Int32Sorted 15 | Int16 setof.Int16 16 | Int16Sorted setof.Int16Sorted 17 | Int8 setof.Int8 18 | Int8Sorted setof.Int8Sorted 19 | Byte setof.Byte 20 | ByteSorted setof.ByteSorted 21 | Uint setof.Uint 22 | UintSorted setof.UintSorted 23 | Uint64 setof.Uint64 24 | Uint64Sorted setof.Uint64Sorted 25 | Uint32 setof.Uint32 26 | Uint32Sorted setof.Uint32Sorted 27 | Uint16 setof.Uint16 28 | Uint16Sorted setof.Uint16Sorted 29 | Uint8 setof.Uint8 30 | Uint8Sorted setof.Uint8Sorted 31 | Float32 setof.Float32 32 | Float32Sorted setof.Float32Sorted 33 | Float64 setof.Float64 34 | Float64Sorted setof.Float64Sorted 35 | } 36 | 37 | //msgp:tuple SetOfTuple 38 | type SetOfTuple struct { 39 | String setof.String 40 | StringSorted setof.StringSorted 41 | Int setof.Int 42 | IntSorted setof.IntSorted 43 | Int64 setof.Int64 44 | Int64Sorted setof.Int64Sorted 45 | Int32 setof.Int32 46 | Int32Sorted setof.Int32Sorted 47 | Int16 setof.Int16 48 | Int16Sorted setof.Int16Sorted 49 | Int8 setof.Int8 50 | Int8Sorted setof.Int8Sorted 51 | Byte setof.Byte 52 | ByteSorted setof.ByteSorted 53 | Uint setof.Uint 54 | UintSorted setof.UintSorted 55 | Uint64 setof.Uint64 56 | Uint64Sorted setof.Uint64Sorted 57 | Uint32 setof.Uint32 58 | Uint32Sorted setof.Uint32Sorted 59 | Uint16 setof.Uint16 60 | Uint16Sorted setof.Uint16Sorted 61 | Uint8 setof.Uint8 62 | Uint8Sorted setof.Uint8Sorted 63 | Float32 setof.Float32 64 | Float32Sorted setof.Float32Sorted 65 | Float64 setof.Float64 66 | Float64Sorted setof.Float64Sorted 67 | } 68 | 69 | var setofFilled = SetOf{ 70 | String: map[string]struct{}{"a": {}, "b": {}, "c": {}}, 71 | StringSorted: map[string]struct{}{"d": {}, "e": {}, "f": {}}, 72 | Int: map[int]struct{}{1: {}, 2: {}, 3: {}}, 73 | IntSorted: map[int]struct{}{4: {}, 5: {}, 6: {}}, 74 | Int64: map[int64]struct{}{1: {}, 2: {}, 3: {}}, 75 | Int64Sorted: map[int64]struct{}{4: {}, 5: {}, 6: {}}, 76 | Int32: map[int32]struct{}{1: {}, 2: {}, 3: {}}, 77 | Int32Sorted: map[int32]struct{}{4: {}, 5: {}, 6: {}}, 78 | Int16: map[int16]struct{}{1: {}, 2: {}, 3: {}}, 79 | Int16Sorted: map[int16]struct{}{4: {}, 5: {}, 6: {}}, 80 | Int8: map[int8]struct{}{1: {}, 2: {}, 3: {}}, 81 | Int8Sorted: map[int8]struct{}{4: {}, 5: {}, 6: {}}, 82 | Byte: map[byte]struct{}{1: {}, 2: {}, 3: {}}, 83 | ByteSorted: map[byte]struct{}{4: {}, 5: {}, 6: {}}, 84 | Uint: map[uint]struct{}{1: {}, 2: {}, 3: {}}, 85 | UintSorted: map[uint]struct{}{4: {}, 5: {}, 6: {}}, 86 | Uint64: map[uint64]struct{}{1: {}, 2: {}, 3: {}}, 87 | Uint64Sorted: map[uint64]struct{}{4: {}, 5: {}, 6: {}}, 88 | Uint32: map[uint32]struct{}{1: {}, 2: {}, 3: {}}, 89 | Uint32Sorted: map[uint32]struct{}{4: {}, 5: {}, 6: {}}, 90 | Uint16: map[uint16]struct{}{1: {}, 2: {}, 3: {}}, 91 | Uint16Sorted: map[uint16]struct{}{4: {}, 5: {}, 6: {}}, 92 | Uint8: map[uint8]struct{}{1: {}, 2: {}, 3: {}}, 93 | Uint8Sorted: map[uint8]struct{}{4: {}, 5: {}, 6: {}}, 94 | Float32: map[float32]struct{}{1: {}, 2: {}, 3: {}}, 95 | Float32Sorted: map[float32]struct{}{4: {}, 5: {}, 6: {}}, 96 | Float64: map[float64]struct{}{1: {}, 2: {}, 3: {}}, 97 | Float64Sorted: map[float64]struct{}{4: {}, 5: {}, 6: {}}, 98 | } 99 | 100 | var setofZeroLen = SetOf{ 101 | String: map[string]struct{}{}, 102 | StringSorted: map[string]struct{}{}, 103 | Int: map[int]struct{}{}, 104 | IntSorted: map[int]struct{}{}, 105 | Int64: map[int64]struct{}{}, 106 | Int64Sorted: map[int64]struct{}{}, 107 | Int32: map[int32]struct{}{}, 108 | Int32Sorted: map[int32]struct{}{}, 109 | Int16: map[int16]struct{}{}, 110 | Int16Sorted: map[int16]struct{}{}, 111 | Int8: map[int8]struct{}{}, 112 | Int8Sorted: map[int8]struct{}{}, 113 | Byte: map[byte]struct{}{}, 114 | ByteSorted: map[byte]struct{}{}, 115 | Uint: map[uint]struct{}{}, 116 | UintSorted: map[uint]struct{}{}, 117 | Uint64: map[uint64]struct{}{}, 118 | Uint64Sorted: map[uint64]struct{}{}, 119 | Uint32: map[uint32]struct{}{}, 120 | Uint32Sorted: map[uint32]struct{}{}, 121 | Uint16: map[uint16]struct{}{}, 122 | Uint16Sorted: map[uint16]struct{}{}, 123 | Uint8: map[uint8]struct{}{}, 124 | Uint8Sorted: map[uint8]struct{}{}, 125 | Float32: map[float32]struct{}{}, 126 | Float32Sorted: map[float32]struct{}{}, 127 | Float64: map[float64]struct{}{}, 128 | Float64Sorted: map[float64]struct{}{}, 129 | } 130 | 131 | var setofSortedFilled = SetOf{ 132 | StringSorted: map[string]struct{}{"d": {}, "e": {}, "f": {}}, 133 | IntSorted: map[int]struct{}{4: {}, 5: {}, 6: {}}, 134 | Int64Sorted: map[int64]struct{}{4: {}, 5: {}, 6: {}}, 135 | Int32Sorted: map[int32]struct{}{4: {}, 5: {}, 6: {}}, 136 | Int16Sorted: map[int16]struct{}{4: {}, 5: {}, 6: {}}, 137 | Int8Sorted: map[int8]struct{}{4: {}, 5: {}, 6: {}}, 138 | ByteSorted: map[byte]struct{}{4: {}, 5: {}, 6: {}}, 139 | UintSorted: map[uint]struct{}{4: {}, 5: {}, 6: {}}, 140 | Uint64Sorted: map[uint64]struct{}{4: {}, 5: {}, 6: {}}, 141 | Uint32Sorted: map[uint32]struct{}{4: {}, 5: {}, 6: {}}, 142 | Uint16Sorted: map[uint16]struct{}{4: {}, 5: {}, 6: {}}, 143 | Uint8Sorted: map[uint8]struct{}{4: {}, 5: {}, 6: {}}, 144 | Float32Sorted: map[float32]struct{}{4: {}, 5: {}, 6: {}}, 145 | Float64Sorted: map[float64]struct{}{4: {}, 5: {}, 6: {}}, 146 | } 147 | -------------------------------------------------------------------------------- /_generated/setof_test.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/tinylib/msgp/msgp" 9 | ) 10 | 11 | func TestSetOf_MarshalMsg(t *testing.T) { 12 | b, err := setofFilled.MarshalMsg(nil) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | var got SetOf 17 | if _, err := got.UnmarshalMsg(b); err != nil { 18 | t.Fatal(err) 19 | } 20 | if !reflect.DeepEqual(got, setofFilled) { 21 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, setofFilled) 22 | } 23 | // nil values... 24 | var empty SetOf 25 | b, err = empty.MarshalMsg(nil) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if _, err := got.UnmarshalMsg(b); err != nil { 30 | t.Fatal(err) 31 | } 32 | if !reflect.DeepEqual(got, empty) { 33 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, empty) 34 | } 35 | // empty values... 36 | b, err = setofZeroLen.MarshalMsg(nil) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if _, err := got.UnmarshalMsg(b); err != nil { 41 | t.Fatal(err) 42 | } 43 | if !reflect.DeepEqual(got, setofZeroLen) { 44 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, setofZeroLen) 45 | } 46 | } 47 | 48 | func TestSetOf_EncodeMsg(t *testing.T) { 49 | var buf bytes.Buffer 50 | enc := msgp.NewWriter(&buf) 51 | dec := msgp.NewReader(&buf) 52 | err := setofFilled.EncodeMsg(enc) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | enc.Flush() 57 | var got SetOf 58 | if err := got.DecodeMsg(dec); err != nil { 59 | t.Fatal(err) 60 | } 61 | if !reflect.DeepEqual(got, setofFilled) { 62 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, setofFilled) 63 | } 64 | // nil values... 65 | var empty SetOf 66 | err = empty.EncodeMsg(enc) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | enc.Flush() 71 | if err := got.DecodeMsg(dec); err != nil { 72 | t.Fatal(err) 73 | } 74 | if !reflect.DeepEqual(got, empty) { 75 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, empty) 76 | } 77 | // empty values... 78 | err = setofZeroLen.EncodeMsg(enc) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | enc.Flush() 83 | if err := got.DecodeMsg(dec); err != nil { 84 | t.Fatal(err) 85 | } 86 | if !reflect.DeepEqual(got, setofZeroLen) { 87 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, setofZeroLen) 88 | } 89 | } 90 | 91 | func TestSetOfTuple_MarshalMsg(t *testing.T) { 92 | want := SetOfTuple(setofFilled) 93 | b, err := want.MarshalMsg(nil) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | var got SetOfTuple 98 | if _, err := got.UnmarshalMsg(b); err != nil { 99 | t.Fatal(err) 100 | } 101 | if !reflect.DeepEqual(got, want) { 102 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, want) 103 | } 104 | // nil values... 105 | var empty SetOfTuple 106 | b, err = empty.MarshalMsg(nil) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | if _, err := got.UnmarshalMsg(b); err != nil { 111 | t.Fatal(err) 112 | } 113 | if !reflect.DeepEqual(got, empty) { 114 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, empty) 115 | } 116 | // empty values... 117 | want = SetOfTuple(setofZeroLen) 118 | b, err = want.MarshalMsg(nil) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | if _, err := got.UnmarshalMsg(b); err != nil { 123 | t.Fatal(err) 124 | } 125 | if !reflect.DeepEqual(got, want) { 126 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, want) 127 | } 128 | } 129 | 130 | func TestSetOfTuple_EncodeMsg(t *testing.T) { 131 | var buf bytes.Buffer 132 | enc := msgp.NewWriter(&buf) 133 | dec := msgp.NewReader(&buf) 134 | want := SetOfTuple(setofFilled) 135 | err := want.EncodeMsg(enc) 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | enc.Flush() 140 | var got SetOfTuple 141 | if err := got.DecodeMsg(dec); err != nil { 142 | t.Fatal(err) 143 | } 144 | if !reflect.DeepEqual(got, want) { 145 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, want) 146 | } 147 | // nil values... 148 | var empty SetOfTuple 149 | err = empty.EncodeMsg(enc) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | enc.Flush() 154 | if err := got.DecodeMsg(dec); err != nil { 155 | t.Fatal(err) 156 | } 157 | if !reflect.DeepEqual(got, empty) { 158 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, empty) 159 | } 160 | // empty values... 161 | want = SetOfTuple(setofZeroLen) 162 | err = want.EncodeMsg(enc) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | enc.Flush() 167 | if err := got.DecodeMsg(dec); err != nil { 168 | t.Fatal(err) 169 | } 170 | if !reflect.DeepEqual(got, want) { 171 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", got, want) 172 | } 173 | } 174 | 175 | func TestSetOfSorted_MarshalMsg(t *testing.T) { 176 | a, err := setofSortedFilled.MarshalMsg(nil) 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | b, err := setofSortedFilled.MarshalMsg(nil) 181 | if err != nil { 182 | t.Fatal(err) 183 | } 184 | if !bytes.Equal(a, b) { 185 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", a, b) 186 | } 187 | } 188 | 189 | func TestSetOfSorted_EncodeMsg(t *testing.T) { 190 | var a, b bytes.Buffer 191 | encA := msgp.NewWriter(&a) 192 | encB := msgp.NewWriter(&b) 193 | 194 | err := setofSortedFilled.EncodeMsg(encA) 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | encA.Flush() 199 | err = setofSortedFilled.EncodeMsg(encB) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | encB.Flush() 204 | 205 | if !bytes.Equal(a.Bytes(), b.Bytes()) { 206 | t.Fatalf("unmarshaled value does not match original: %+v != %+v", a, b) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /_generated/vet_copylocks.go: -------------------------------------------------------------------------------- 1 | package _generated 2 | 3 | import "sync" 4 | 5 | //go:generate msgp 6 | //go:generate go vet 7 | 8 | type Foo struct { 9 | I struct{} 10 | lock sync.Mutex 11 | } 12 | -------------------------------------------------------------------------------- /gen/size.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | 8 | "github.com/tinylib/msgp/msgp" 9 | ) 10 | 11 | type sizeState uint8 12 | 13 | const ( 14 | // need to write "s = ..." 15 | assign sizeState = iota 16 | 17 | // need to write "s += ..." 18 | add 19 | 20 | // can just append "+ ..." 21 | expr 22 | ) 23 | 24 | func sizes(w io.Writer) *sizeGen { 25 | return &sizeGen{ 26 | p: printer{w: w}, 27 | state: assign, 28 | } 29 | } 30 | 31 | type sizeGen struct { 32 | passes 33 | p printer 34 | state sizeState 35 | ctx *Context 36 | } 37 | 38 | func (s *sizeGen) Method() Method { return Size } 39 | 40 | func (s *sizeGen) Apply(dirs []string) error { 41 | return nil 42 | } 43 | 44 | func builtinSize(typ string) string { 45 | return "msgp." + typ + "Size" 46 | } 47 | 48 | // this lets us chain together addition 49 | // operations where possible 50 | func (s *sizeGen) addConstant(sz string) { 51 | if !s.p.ok() { 52 | return 53 | } 54 | 55 | switch s.state { 56 | case assign: 57 | s.p.print("\ns = " + sz) 58 | s.state = expr 59 | return 60 | case add: 61 | s.p.print("\ns += " + sz) 62 | s.state = expr 63 | return 64 | case expr: 65 | s.p.print(" + " + sz) 66 | return 67 | } 68 | 69 | panic("unknown size state") 70 | } 71 | 72 | func (s *sizeGen) Execute(p Elem, ctx Context) error { 73 | s.ctx = &ctx 74 | if !s.p.ok() { 75 | return s.p.err 76 | } 77 | p = s.applyall(p) 78 | if p == nil { 79 | return nil 80 | } 81 | if !IsPrintable(p) { 82 | return nil 83 | } 84 | 85 | s.ctx.PushString(p.TypeName()) 86 | 87 | s.p.comment("Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message") 88 | 89 | rcv := imutMethodReceiver(p) 90 | ogVar := p.Varname() 91 | if p.AlwaysPtr(nil) { 92 | rcv = methodReceiver(p) 93 | } 94 | s.p.printf("\nfunc (%s %s) Msgsize() (s int) {", ogVar, rcv) 95 | s.state = assign 96 | next(s, p) 97 | if p.AlwaysPtr(nil) { 98 | p.SetVarname(ogVar) 99 | } 100 | s.p.nakedReturn() 101 | return s.p.err 102 | } 103 | 104 | func (s *sizeGen) gStruct(st *Struct) { 105 | if !s.p.ok() { 106 | return 107 | } 108 | 109 | nfields := uint32(len(st.Fields)) 110 | 111 | if st.AsTuple { 112 | data := msgp.AppendArrayHeader(nil, nfields) 113 | s.addConstant(strconv.Itoa(len(data))) 114 | for i := range st.Fields { 115 | if !s.p.ok() { 116 | return 117 | } 118 | next(s, st.Fields[i].FieldElem) 119 | } 120 | } else { 121 | data := msgp.AppendMapHeader(nil, nfields) 122 | s.addConstant(strconv.Itoa(len(data))) 123 | for i := range st.Fields { 124 | data = data[:0] 125 | data = msgp.AppendString(data, st.Fields[i].FieldTag) 126 | s.addConstant(strconv.Itoa(len(data))) 127 | next(s, st.Fields[i].FieldElem) 128 | } 129 | } 130 | } 131 | 132 | func (s *sizeGen) gPtr(p *Ptr) { 133 | s.state = add // inner must use add 134 | s.p.printf("\nif %s == nil {\ns += msgp.NilSize\n} else {", p.Varname()) 135 | next(s, p.Value) 136 | s.state = add // closing block; reset to add 137 | s.p.closeblock() 138 | } 139 | 140 | func (s *sizeGen) gSlice(sl *Slice) { 141 | if !s.p.ok() { 142 | return 143 | } 144 | 145 | s.addConstant(builtinSize(arrayHeader)) 146 | 147 | // if the slice's element is a fixed size 148 | // (e.g. float64, [32]int, etc.), then 149 | // print the length times the element size directly 150 | if str, ok := fixedsizeExpr(sl.Els); ok { 151 | s.addConstant(fmt.Sprintf("(%s * (%s))", lenExpr(sl), str)) 152 | return 153 | } 154 | 155 | // add inside the range block, and immediately after 156 | s.state = add 157 | s.p.rangeBlock(s.ctx, sl.Index, sl.Varname(), s, sl.Els) 158 | s.state = add 159 | } 160 | 161 | func (s *sizeGen) gArray(a *Array) { 162 | if !s.p.ok() { 163 | return 164 | } 165 | 166 | s.addConstant(builtinSize(arrayHeader)) 167 | 168 | // if the array's children are a fixed 169 | // size, we can compile an expression 170 | // that always represents the array's wire size 171 | if str, ok := fixedsizeExpr(a); ok { 172 | s.addConstant(str) 173 | return 174 | } 175 | 176 | s.state = add 177 | s.p.rangeBlock(s.ctx, a.Index, a.Varname(), s, a.Els) 178 | s.state = add 179 | } 180 | 181 | func (s *sizeGen) gMap(m *Map) { 182 | s.addConstant(builtinSize(mapHeader)) 183 | vn := m.Varname() 184 | s.p.printf("\nif %s != nil {", vn) 185 | s.p.printf("\nfor %s, %s := range %s {", m.Keyidx, m.Validx, vn) 186 | s.p.printf("\n_ = %s", m.Validx) // we may not use the value 187 | s.p.printf("\ns += msgp.StringPrefixSize + len(%s)", m.Keyidx) 188 | s.state = expr 189 | s.ctx.PushVar(m.Keyidx) 190 | next(s, m.Value) 191 | s.ctx.Pop() 192 | s.p.closeblock() 193 | s.p.closeblock() 194 | s.state = add 195 | } 196 | 197 | func (s *sizeGen) gBase(b *BaseElem) { 198 | if !s.p.ok() { 199 | return 200 | } 201 | if b.Convert && b.ShimMode == Convert { 202 | s.state = add 203 | vname := randIdent() 204 | s.p.printf("\nvar %s %s", vname, b.BaseType()) 205 | 206 | // ensure we don't get "unused variable" warnings from outer slice iterations 207 | s.p.printf("\n_ = %s", b.Varname()) 208 | 209 | s.p.printf("\ns += %s", basesizeExpr(b.Value, vname, b.BaseName())) 210 | s.state = expr 211 | 212 | } else { 213 | vname := b.Varname() 214 | if b.Convert { 215 | vname = tobaseConvert(b) 216 | } 217 | s.addConstant(basesizeExpr(b.Value, vname, b.BaseName())) 218 | } 219 | } 220 | 221 | // returns "len(slice)" 222 | func lenExpr(sl *Slice) string { 223 | return "len(" + sl.Varname() + ")" 224 | } 225 | 226 | // is a given primitive always the same (max) 227 | // size on the wire? 228 | func fixedSize(p Primitive) bool { 229 | switch p { 230 | case Intf, Ext, IDENT, Bytes, String: 231 | return false 232 | default: 233 | return true 234 | } 235 | } 236 | 237 | // strip reference from string 238 | func stripRef(s string) string { 239 | if s[0] == '&' { 240 | return s[1:] 241 | } 242 | return s 243 | } 244 | 245 | // return a fixed-size expression, if possible. 246 | // only possible for *BaseElem and *Array. 247 | // returns (expr, ok) 248 | func fixedsizeExpr(e Elem) (string, bool) { 249 | switch e := e.(type) { 250 | case *Array: 251 | if str, ok := fixedsizeExpr(e.Els); ok { 252 | return fmt.Sprintf("(%s * (%s))", e.Size, str), true 253 | } 254 | case *BaseElem: 255 | if fixedSize(e.Value) { 256 | return builtinSize(e.BaseName()), true 257 | } 258 | case *Struct: 259 | var str string 260 | for _, f := range e.Fields { 261 | if fs, ok := fixedsizeExpr(f.FieldElem); ok { 262 | if str == "" { 263 | str = fs 264 | } else { 265 | str += "+" + fs 266 | } 267 | } else { 268 | return "", false 269 | } 270 | } 271 | var hdrlen int 272 | mhdr := msgp.AppendMapHeader(nil, uint32(len(e.Fields))) 273 | hdrlen += len(mhdr) 274 | var strbody []byte 275 | for _, f := range e.Fields { 276 | strbody = msgp.AppendString(strbody[:0], f.FieldTag) 277 | hdrlen += len(strbody) 278 | } 279 | return fmt.Sprintf("%d + %s", hdrlen, str), true 280 | } 281 | return "", false 282 | } 283 | 284 | // print size expression of a variable name 285 | func basesizeExpr(value Primitive, vname, basename string) string { 286 | switch value { 287 | case Ext: 288 | return "msgp.ExtensionPrefixSize + " + stripRef(vname) + ".Len()" 289 | case Intf: 290 | return "msgp.GuessSize(" + vname + ")" 291 | case IDENT: 292 | return vname + ".Msgsize()" 293 | case Bytes: 294 | return "msgp.BytesPrefixSize + len(" + vname + ")" 295 | case String: 296 | return "msgp.StringPrefixSize + len(" + vname + ")" 297 | default: 298 | return builtinSize(basename) 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /gen/testgen.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "io" 5 | "text/template" 6 | ) 7 | 8 | var ( 9 | marshalTestTempl = template.New("MarshalTest") 10 | encodeTestTempl = template.New("EncodeTest") 11 | ) 12 | 13 | // TODO(philhofer): 14 | // for simplicity's sake, right now 15 | // we can only generate tests for types 16 | // that can be initialized with the 17 | // "Type{}" syntax. 18 | // we should support all the types. 19 | 20 | func mtest(w io.Writer) *mtestGen { 21 | return &mtestGen{w: w} 22 | } 23 | 24 | type mtestGen struct { 25 | passes 26 | w io.Writer 27 | } 28 | 29 | func (m *mtestGen) Execute(p Elem, _ Context) error { 30 | p = m.applyall(p) 31 | if p != nil && IsPrintable(p) { 32 | switch p.(type) { 33 | case *Struct, *Array, *Slice, *Map: 34 | return marshalTestTempl.Execute(m.w, p) 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | func (m *mtestGen) Method() Method { return marshaltest } 41 | 42 | type etestGen struct { 43 | passes 44 | w io.Writer 45 | } 46 | 47 | func etest(w io.Writer) *etestGen { 48 | return &etestGen{w: w} 49 | } 50 | 51 | func (e *etestGen) Execute(p Elem, _ Context) error { 52 | p = e.applyall(p) 53 | if p != nil && IsPrintable(p) { 54 | switch p.(type) { 55 | case *Struct, *Array, *Slice, *Map: 56 | return encodeTestTempl.Execute(e.w, p) 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func (e *etestGen) Method() Method { return encodetest } 63 | 64 | func init() { 65 | template.Must(marshalTestTempl.Parse(`func TestMarshalUnmarshal{{.TypeName}}(t *testing.T) { 66 | v := {{.TypeName}}{} 67 | bts, err := v.MarshalMsg(nil) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | left, err := v.UnmarshalMsg(bts) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if len(left) > 0 { 76 | t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) 77 | } 78 | 79 | left, err = msgp.Skip(bts) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | if len(left) > 0 { 84 | t.Errorf("%d bytes left over after Skip(): %q", len(left), left) 85 | } 86 | } 87 | 88 | func BenchmarkMarshalMsg{{.TypeName}}(b *testing.B) { 89 | v := {{.TypeName}}{} 90 | b.ReportAllocs() 91 | b.ResetTimer() 92 | for i:=0; i m { 132 | t.Log("WARNING: TestEncodeDecode{{.TypeName}} Msgsize() is inaccurate") 133 | } 134 | 135 | vn := {{.TypeName}}{} 136 | err := msgp.Decode(&buf, &vn) 137 | if err != nil { 138 | t.Error(err) 139 | } 140 | 141 | buf.Reset() 142 | msgp.Encode(&buf, &v) 143 | err = msgp.NewReader(&buf).Skip() 144 | if err != nil { 145 | t.Error(err) 146 | } 147 | } 148 | 149 | func BenchmarkEncode{{.TypeName}}(b *testing.B) { 150 | v := {{.TypeName}}{} 151 | var buf bytes.Buffer 152 | msgp.Encode(&buf, &v) 153 | b.SetBytes(int64(buf.Len())) 154 | en := msgp.NewWriter(msgp.Nowhere) 155 | b.ReportAllocs() 156 | b.ResetTimer() 157 | for i:=0; i 0 && len(av) > 0 && reflect.DeepEqual(bv, av) { 62 | t.Fatalf("%d vars identical! expected vars to change for %s", idx, fn) 63 | } 64 | delete(varsBefore, fn) 65 | delete(varsAfter, fn) 66 | } 67 | } 68 | 69 | // all of the remaining keys should not have changed 70 | for bmethod, bvars := range varsBefore { 71 | avars := varsAfter.Value(bmethod) 72 | 73 | if !reflect.DeepEqual(bvars, avars) { 74 | t.Fatalf("%d: vars changed! expected vars identical for %s", idx, bmethod) 75 | } 76 | delete(varsBefore, bmethod) 77 | delete(varsAfter, bmethod) 78 | } 79 | 80 | if len(varsBefore) > 0 || len(varsAfter) > 0 { 81 | t.Fatalf("%d: unexpected methods remaining", idx) 82 | } 83 | } 84 | } 85 | 86 | type issue185TplData struct { 87 | Extra bool 88 | } 89 | 90 | func TestIssue185Overlap(t *testing.T) { 91 | overlapCases := []struct { 92 | tpl *template.Template 93 | data issue185TplData 94 | }{ 95 | {tpl: issue185IdentsTpl, data: issue185TplData{Extra: false}}, 96 | {tpl: issue185IdentsTpl, data: issue185TplData{Extra: true}}, 97 | {tpl: issue185ComplexIdentsTpl, data: issue185TplData{Extra: false}}, 98 | {tpl: issue185ComplexIdentsTpl, data: issue185TplData{Extra: true}}, 99 | } 100 | 101 | for idx, o := range overlapCases { 102 | // regenerate the code with extra field(s), extract the generated variable 103 | // names, mapped to function name 104 | mvars, err := loadVars(t, o.tpl, o.data) 105 | if err != nil { 106 | t.Fatalf("%d: could not extract after vars: %v", idx, err) 107 | } 108 | 109 | identCnt := 0 110 | for fn, vars := range mvars { 111 | sort.Strings(vars) 112 | 113 | // Loose sanity check to make sure the tests expectations aren't broken. 114 | // If the prefix ever changes, this needs to change. 115 | for _, v := range vars { 116 | if v[0] == 'z' { 117 | identCnt++ 118 | } 119 | } 120 | 121 | for i := 0; i < len(vars)-1; i++ { 122 | if vars[i] == vars[i+1] { 123 | t.Fatalf("%d: duplicate var %s in function %s", idx, vars[i], fn) 124 | } 125 | } 126 | } 127 | 128 | // one last sanity check: if there aren't any vars that start with 'z', 129 | // this test's expectations are unsatisfiable. 130 | if identCnt == 0 { 131 | t.Fatalf("%d: no generated identifiers found", idx) 132 | } 133 | } 134 | } 135 | 136 | func loadVars(t *testing.T, tpl *template.Template, tplData interface{}) (vars extractedVars, err error) { 137 | tempDir := t.TempDir() 138 | 139 | if !debugTemp { 140 | defer os.RemoveAll(tempDir) 141 | } else { 142 | fmt.Println(tempDir) 143 | } 144 | tfile := filepath.Join(tempDir, "msg.go") 145 | genFile := newFilename(tfile, "") 146 | 147 | if err = goGenerateTpl(tempDir, tfile, tpl, tplData); err != nil { 148 | err = fmt.Errorf("could not generate code: %v", err) 149 | return 150 | } 151 | 152 | vars, err = extractVars(genFile) 153 | if err != nil { 154 | err = fmt.Errorf("could not extract after vars: %v", err) 155 | return 156 | } 157 | 158 | return 159 | } 160 | 161 | type varVisitor struct { 162 | vars []string 163 | fset *token.FileSet 164 | } 165 | 166 | func (v *varVisitor) Visit(node ast.Node) (w ast.Visitor) { 167 | gen, ok := node.(*ast.GenDecl) 168 | if !ok { 169 | return v 170 | } 171 | for _, spec := range gen.Specs { 172 | if vspec, ok := spec.(*ast.ValueSpec); ok { 173 | for _, n := range vspec.Names { 174 | v.vars = append(v.vars, n.Name) 175 | } 176 | } 177 | } 178 | return v 179 | } 180 | 181 | type extractedVars map[string][]string 182 | 183 | func (e extractedVars) Value(key string) []string { 184 | if v, ok := e[key]; ok { 185 | return v 186 | } 187 | panic(fmt.Errorf("unknown key %s", key)) 188 | } 189 | 190 | func extractVars(file string) (extractedVars, error) { 191 | fset := token.NewFileSet() 192 | 193 | f, err := parser.ParseFile(fset, file, nil, 0) 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | vars := make(map[string][]string) 199 | for _, d := range f.Decls { 200 | switch d := d.(type) { 201 | case *ast.FuncDecl: 202 | sn := "" 203 | switch rt := d.Recv.List[0].Type.(type) { 204 | case *ast.Ident: 205 | sn = rt.Name 206 | case *ast.StarExpr: 207 | sn = rt.X.(*ast.Ident).Name 208 | default: 209 | panic("unknown receiver type") 210 | } 211 | 212 | key := fmt.Sprintf("%s.%s", sn, d.Name.Name) 213 | vis := &varVisitor{fset: fset} 214 | ast.Walk(vis, d.Body) 215 | vars[key] = vis.vars 216 | } 217 | } 218 | return vars, nil 219 | } 220 | 221 | func goGenerateTpl(cwd, tfile string, tpl *template.Template, tplData interface{}) error { 222 | outf, err := os.OpenFile(tfile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o600) 223 | if err != nil { 224 | return err 225 | } 226 | defer outf.Close() 227 | 228 | if err := tpl.Execute(outf, tplData); err != nil { 229 | return err 230 | } 231 | 232 | mode := gen.Encode | gen.Decode | gen.Size | gen.Marshal | gen.Unmarshal 233 | 234 | return Run(tfile, mode, false) 235 | } 236 | 237 | var issue185IdentsTpl = template.Must(template.New("").Parse(` 238 | package issue185 239 | 240 | //go:generate msgp 241 | 242 | type Test1 struct { 243 | Foo string 244 | Bar string 245 | {{ if .Extra }}Baz []string{{ end }} 246 | Qux string 247 | } 248 | 249 | type Test2 struct { 250 | Foo string 251 | Bar string 252 | Baz string 253 | } 254 | `)) 255 | 256 | var issue185ComplexIdentsTpl = template.Must(template.New("").Parse(` 257 | package issue185 258 | 259 | //go:generate msgp 260 | 261 | type Test1 struct { 262 | Foo string 263 | Bar string 264 | Baz string 265 | } 266 | 267 | type Test2 struct { 268 | Foo string 269 | Bar string 270 | Baz []string 271 | Qux map[string]string 272 | Yep map[string]map[string]string 273 | Quack struct { 274 | Quack struct { 275 | Quack struct { 276 | {{ if .Extra }}Extra []string{{ end }} 277 | Quack string 278 | } 279 | } 280 | } 281 | Nup struct { 282 | Foo string 283 | Bar string 284 | Baz []string 285 | Qux map[string]string 286 | Yep map[string]map[string]string 287 | } 288 | Ding struct { 289 | Dong struct { 290 | Dung struct { 291 | Thing string 292 | } 293 | } 294 | } 295 | } 296 | 297 | type Test3 struct { 298 | Foo string 299 | Bar string 300 | Baz string 301 | } 302 | `)) 303 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // msgp is a code generation tool for 2 | // creating methods to serialize and de-serialize 3 | // Go data structures to and from MessagePack. 4 | // 5 | // This package is targeted at the `go generate` tool. 6 | // To use it, include the following directive in a 7 | // go source file with types requiring source generation: 8 | // 9 | // //go:generate msgp 10 | // 11 | // The go generate tool should set the proper environment variables for 12 | // the generator to execute without any command-line flags. However, the 13 | // following options are supported, if you need them: 14 | // 15 | // -o = output file name (default is {input}_gen.go) 16 | // -file = input file name (or directory; default is $GOFILE, which is set by the `go generate` command) 17 | // -io = satisfy the `msgp.Decodable` and `msgp.Encodable` interfaces (default is true) 18 | // -marshal = satisfy the `msgp.Marshaler` and `msgp.Unmarshaler` interfaces (default is true) 19 | // -tests = generate tests and benchmarks (default is true) 20 | // 21 | // For more information, please read README.md, and the wiki at github.com/tinylib/msgp 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "os" 28 | "path/filepath" 29 | "strings" 30 | 31 | "github.com/tinylib/msgp/gen" 32 | "github.com/tinylib/msgp/parse" 33 | "github.com/tinylib/msgp/printer" 34 | ) 35 | 36 | var ( 37 | out = flag.String("o", "", "output file") 38 | file = flag.String("file", "", "input file") 39 | encode = flag.Bool("io", true, "create Encode and Decode methods") 40 | marshal = flag.Bool("marshal", true, "create Marshal and Unmarshal methods") 41 | tests = flag.Bool("tests", true, "create tests and benchmarks") 42 | unexported = flag.Bool("unexported", false, "also process unexported types") 43 | verbose = flag.Bool("v", false, "verbose diagnostics") 44 | ) 45 | 46 | func diagf(f string, args ...interface{}) { 47 | if !*verbose { 48 | return 49 | } 50 | if f[len(f)-1] != '\n' { 51 | f += "\n" 52 | } 53 | fmt.Fprintf(os.Stderr, f, args...) 54 | } 55 | 56 | func exitln(res string) { 57 | fmt.Fprintln(os.Stderr, res) 58 | os.Exit(1) 59 | } 60 | 61 | func main() { 62 | flag.Parse() 63 | 64 | if *verbose { 65 | printer.Logf = diagf 66 | parse.Logf = diagf 67 | } 68 | 69 | // GOFILE is set by go generate 70 | if *file == "" { 71 | *file = os.Getenv("GOFILE") 72 | if *file == "" { 73 | exitln("No file to parse.") 74 | } 75 | } 76 | 77 | var mode gen.Method 78 | if *encode { 79 | mode |= (gen.Encode | gen.Decode | gen.Size) 80 | } 81 | if *marshal { 82 | mode |= (gen.Marshal | gen.Unmarshal | gen.Size) 83 | } 84 | if *tests { 85 | mode |= gen.Test 86 | } 87 | 88 | if mode&^gen.Test == 0 { 89 | exitln("No methods to generate; -io=false && -marshal=false") 90 | } 91 | 92 | if err := Run(*file, mode, *unexported); err != nil { 93 | exitln(err.Error()) 94 | } 95 | } 96 | 97 | // Run writes all methods using the associated file or path, e.g. 98 | // 99 | // err := msgp.Run("path/to/myfile.go", gen.Size|gen.Marshal|gen.Unmarshal|gen.Test, false) 100 | func Run(gofile string, mode gen.Method, unexported bool) error { 101 | if mode&^gen.Test == 0 { 102 | return nil 103 | } 104 | diagf("Input: \"%s\"\n", gofile) 105 | fs, err := parse.File(gofile, unexported) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | if len(fs.Identities) == 0 { 111 | diagf("No types requiring code generation were found!") 112 | } 113 | 114 | return printer.PrintFile(newFilename(gofile, fs.Package), fs, mode) 115 | } 116 | 117 | // picks a new file name based on input flags and input filename(s). 118 | func newFilename(old string, pkg string) string { 119 | if *out != "" { 120 | if pre := strings.TrimPrefix(*out, old); len(pre) > 0 && 121 | !strings.HasSuffix(*out, ".go") { 122 | return filepath.Join(old, *out) 123 | } 124 | return *out 125 | } 126 | 127 | if fi, err := os.Stat(old); err == nil && fi.IsDir() { 128 | old = filepath.Join(old, pkg) 129 | } 130 | // new file name is old file name + _gen.go 131 | return strings.TrimSuffix(old, ".go") + "_gen.go" 132 | } 133 | -------------------------------------------------------------------------------- /msgp/advise_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !appengine && !tinygo 2 | // +build linux,!appengine,!tinygo 3 | 4 | package msgp 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | func adviseRead(mem []byte) { 12 | syscall.Madvise(mem, syscall.MADV_SEQUENTIAL|syscall.MADV_WILLNEED) 13 | } 14 | 15 | func adviseWrite(mem []byte) { 16 | syscall.Madvise(mem, syscall.MADV_SEQUENTIAL) 17 | } 18 | 19 | func fallocate(f *os.File, sz int64) error { 20 | err := syscall.Fallocate(int(f.Fd()), 0, 0, sz) 21 | if err == syscall.ENOTSUP { 22 | return f.Truncate(sz) 23 | } 24 | return err 25 | } 26 | -------------------------------------------------------------------------------- /msgp/advise_other.go: -------------------------------------------------------------------------------- 1 | //go:build (!linux && !tinygo && !windows) || appengine 2 | // +build !linux,!tinygo,!windows appengine 3 | 4 | package msgp 5 | 6 | import ( 7 | "os" 8 | ) 9 | 10 | // TODO: darwin, BSD support 11 | 12 | func adviseRead(mem []byte) {} 13 | 14 | func adviseWrite(mem []byte) {} 15 | 16 | func fallocate(f *os.File, sz int64) error { 17 | return f.Truncate(sz) 18 | } 19 | -------------------------------------------------------------------------------- /msgp/circular.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | type timer interface { 4 | StartTimer() 5 | StopTimer() 6 | } 7 | 8 | // EndlessReader is an io.Reader 9 | // that loops over the same data 10 | // endlessly. It is used for benchmarking. 11 | type EndlessReader struct { 12 | tb timer 13 | data []byte 14 | offset int 15 | } 16 | 17 | // NewEndlessReader returns a new endless reader. 18 | // Buffer b cannot be empty 19 | func NewEndlessReader(b []byte, tb timer) *EndlessReader { 20 | if len(b) == 0 { 21 | panic("EndlessReader cannot be of zero length") 22 | } 23 | // Double until we reach 4K. 24 | for len(b) < 4<<10 { 25 | b = append(b, b...) 26 | } 27 | return &EndlessReader{tb: tb, data: b, offset: 0} 28 | } 29 | 30 | // Read implements io.Reader. In practice, it 31 | // always returns (len(p), nil), although it 32 | // fills the supplied slice while the benchmark 33 | // timer is stopped. 34 | func (c *EndlessReader) Read(p []byte) (int, error) { 35 | var n int 36 | l := len(p) 37 | m := len(c.data) 38 | nn := copy(p[n:], c.data[c.offset:]) 39 | n += nn 40 | for n < l { 41 | n += copy(p[n:], c.data[:]) 42 | } 43 | c.offset = (c.offset + l) % m 44 | return n, nil 45 | } 46 | -------------------------------------------------------------------------------- /msgp/defs.go: -------------------------------------------------------------------------------- 1 | // This package is the support library for the msgp code generator (http://github.com/tinylib/msgp). 2 | // 3 | // This package defines the utilites used by the msgp code generator for encoding and decoding MessagePack 4 | // from []byte and io.Reader/io.Writer types. Much of this package is devoted to helping the msgp code 5 | // generator implement the Marshaler/Unmarshaler and Encodable/Decodable interfaces. 6 | // 7 | // This package defines four "families" of functions: 8 | // - AppendXxxx() appends an object to a []byte in MessagePack encoding. 9 | // - ReadXxxxBytes() reads an object from a []byte and returns the remaining bytes. 10 | // - (*Writer).WriteXxxx() writes an object to the buffered *Writer type. 11 | // - (*Reader).ReadXxxx() reads an object from a buffered *Reader type. 12 | // 13 | // Once a type has satisfied the `Encodable` and `Decodable` interfaces, 14 | // it can be written and read from arbitrary `io.Writer`s and `io.Reader`s using 15 | // 16 | // msgp.Encode(io.Writer, msgp.Encodable) 17 | // 18 | // and 19 | // 20 | // msgp.Decode(io.Reader, msgp.Decodable) 21 | // 22 | // There are also methods for converting MessagePack to JSON without 23 | // an explicit de-serialization step. 24 | // 25 | // For additional tips, tricks, and gotchas, please visit 26 | // the wiki at http://github.com/tinylib/msgp 27 | package msgp 28 | 29 | const ( 30 | last4 = 0x0f 31 | first4 = 0xf0 32 | last5 = 0x1f 33 | first3 = 0xe0 34 | last7 = 0x7f 35 | 36 | // recursionLimit is the limit of recursive calls. 37 | // This limits the call depth of dynamic code, like Skip and interface conversions. 38 | recursionLimit = 100000 39 | ) 40 | 41 | func isfixint(b byte) bool { 42 | return b>>7 == 0 43 | } 44 | 45 | func isnfixint(b byte) bool { 46 | return b&first3 == mnfixint 47 | } 48 | 49 | func isfixmap(b byte) bool { 50 | return b&first4 == mfixmap 51 | } 52 | 53 | func isfixarray(b byte) bool { 54 | return b&first4 == mfixarray 55 | } 56 | 57 | func isfixstr(b byte) bool { 58 | return b&first3 == mfixstr 59 | } 60 | 61 | func wfixint(u uint8) byte { 62 | return u & last7 63 | } 64 | 65 | func rfixint(b byte) uint8 { 66 | return b 67 | } 68 | 69 | func wnfixint(i int8) byte { 70 | return byte(i) | mnfixint 71 | } 72 | 73 | func rnfixint(b byte) int8 { 74 | return int8(b) 75 | } 76 | 77 | func rfixmap(b byte) uint8 { 78 | return b & last4 79 | } 80 | 81 | func wfixmap(u uint8) byte { 82 | return mfixmap | (u & last4) 83 | } 84 | 85 | func rfixstr(b byte) uint8 { 86 | return b & last5 87 | } 88 | 89 | func wfixstr(u uint8) byte { 90 | return (u & last5) | mfixstr 91 | } 92 | 93 | func rfixarray(b byte) uint8 { 94 | return (b & last4) 95 | } 96 | 97 | func wfixarray(u uint8) byte { 98 | return (u & last4) | mfixarray 99 | } 100 | 101 | // These are all the byte 102 | // prefixes defined by the 103 | // msgpack standard 104 | const ( 105 | // 0XXXXXXX 106 | mfixint uint8 = 0x00 107 | 108 | // 111XXXXX 109 | mnfixint uint8 = 0xe0 110 | 111 | // 1000XXXX 112 | mfixmap uint8 = 0x80 113 | 114 | // 1001XXXX 115 | mfixarray uint8 = 0x90 116 | 117 | // 101XXXXX 118 | mfixstr uint8 = 0xa0 119 | 120 | mnil uint8 = 0xc0 121 | mfalse uint8 = 0xc2 122 | mtrue uint8 = 0xc3 123 | mbin8 uint8 = 0xc4 124 | mbin16 uint8 = 0xc5 125 | mbin32 uint8 = 0xc6 126 | mext8 uint8 = 0xc7 127 | mext16 uint8 = 0xc8 128 | mext32 uint8 = 0xc9 129 | mfloat32 uint8 = 0xca 130 | mfloat64 uint8 = 0xcb 131 | muint8 uint8 = 0xcc 132 | muint16 uint8 = 0xcd 133 | muint32 uint8 = 0xce 134 | muint64 uint8 = 0xcf 135 | mint8 uint8 = 0xd0 136 | mint16 uint8 = 0xd1 137 | mint32 uint8 = 0xd2 138 | mint64 uint8 = 0xd3 139 | mfixext1 uint8 = 0xd4 140 | mfixext2 uint8 = 0xd5 141 | mfixext4 uint8 = 0xd6 142 | mfixext8 uint8 = 0xd7 143 | mfixext16 uint8 = 0xd8 144 | mstr8 uint8 = 0xd9 145 | mstr16 uint8 = 0xda 146 | mstr32 uint8 = 0xdb 147 | marray16 uint8 = 0xdc 148 | marray32 uint8 = 0xdd 149 | mmap16 uint8 = 0xde 150 | mmap32 uint8 = 0xdf 151 | ) 152 | -------------------------------------------------------------------------------- /msgp/defs_test.go: -------------------------------------------------------------------------------- 1 | package msgp_test 2 | 3 | //go:generate msgp -o=defgen_test.go -tests=false 4 | 5 | type Blobs []Blob 6 | 7 | type Blob struct { 8 | Name string `msg:"name"` 9 | Float float64 `msg:"float"` 10 | Bytes []byte `msg:"bytes"` 11 | Amount int64 `msg:"amount"` 12 | } 13 | -------------------------------------------------------------------------------- /msgp/edit.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Locate returns a []byte pointing to the field 8 | // in a messagepack map with the provided key. (The returned []byte 9 | // points to a sub-slice of 'raw'; Locate does no allocations.) If the 10 | // key doesn't exist in the map, a zero-length []byte will be returned. 11 | func Locate(key string, raw []byte) []byte { 12 | s, n := locate(raw, key) 13 | return raw[s:n] 14 | } 15 | 16 | // Replace takes a key ("key") in a messagepack map ("raw") 17 | // and replaces its value with the one provided and returns 18 | // the new []byte. The returned []byte may point to the same 19 | // memory as "raw". Replace makes no effort to evaluate the validity 20 | // of the contents of 'val'. It may use up to the full capacity of 'raw.' 21 | // Replace returns 'nil' if the field doesn't exist or if the object in 'raw' 22 | // is not a map. 23 | func Replace(key string, raw []byte, val []byte) []byte { 24 | start, end := locate(raw, key) 25 | if start == end { 26 | return nil 27 | } 28 | return replace(raw, start, end, val, true) 29 | } 30 | 31 | // CopyReplace works similarly to Replace except that the returned 32 | // byte slice does not point to the same memory as 'raw'. CopyReplace 33 | // returns 'nil' if the field doesn't exist or 'raw' isn't a map. 34 | func CopyReplace(key string, raw []byte, val []byte) []byte { 35 | start, end := locate(raw, key) 36 | if start == end { 37 | return nil 38 | } 39 | return replace(raw, start, end, val, false) 40 | } 41 | 42 | // Remove removes a key-value pair from 'raw'. It returns 43 | // 'raw' unchanged if the key didn't exist. 44 | func Remove(key string, raw []byte) []byte { 45 | start, end := locateKV(raw, key) 46 | if start == end { 47 | return raw 48 | } 49 | raw = raw[:start+copy(raw[start:], raw[end:])] 50 | return resizeMap(raw, -1) 51 | } 52 | 53 | // HasKey returns whether the map in 'raw' has 54 | // a field with key 'key' 55 | func HasKey(key string, raw []byte) bool { 56 | sz, bts, err := ReadMapHeaderBytes(raw) 57 | if err != nil { 58 | return false 59 | } 60 | var field []byte 61 | for i := uint32(0); i < sz; i++ { 62 | field, bts, err = ReadStringZC(bts) 63 | if err != nil { 64 | return false 65 | } 66 | if UnsafeString(field) == key { 67 | return true 68 | } 69 | } 70 | return false 71 | } 72 | 73 | func replace(raw []byte, start int, end int, val []byte, inplace bool) []byte { 74 | ll := end - start // length of segment to replace 75 | lv := len(val) 76 | 77 | if inplace { 78 | extra := lv - ll 79 | 80 | // fastest case: we're doing 81 | // a 1:1 replacement 82 | if extra == 0 { 83 | copy(raw[start:], val) 84 | return raw 85 | 86 | } else if extra < 0 { 87 | // 'val' smaller than replaced value 88 | // copy in place and shift back 89 | 90 | x := copy(raw[start:], val) 91 | y := copy(raw[start+x:], raw[end:]) 92 | return raw[:start+x+y] 93 | 94 | } else if extra < cap(raw)-len(raw) { 95 | // 'val' less than (cap-len) extra bytes 96 | // copy in place and shift forward 97 | raw = raw[0 : len(raw)+extra] 98 | // shift end forward 99 | copy(raw[end+extra:], raw[end:]) 100 | copy(raw[start:], val) 101 | return raw 102 | } 103 | } 104 | 105 | // we have to allocate new space 106 | out := make([]byte, len(raw)+len(val)-ll) 107 | x := copy(out, raw[:start]) 108 | y := copy(out[x:], val) 109 | copy(out[x+y:], raw[end:]) 110 | return out 111 | } 112 | 113 | // locate does a naive O(n) search for the map key; returns start, end 114 | // (returns 0,0 on error) 115 | func locate(raw []byte, key string) (start int, end int) { 116 | var ( 117 | sz uint32 118 | bts []byte 119 | field []byte 120 | err error 121 | ) 122 | sz, bts, err = ReadMapHeaderBytes(raw) 123 | if err != nil { 124 | return 125 | } 126 | 127 | // loop and locate field 128 | for i := uint32(0); i < sz; i++ { 129 | field, bts, err = ReadStringZC(bts) 130 | if err != nil { 131 | return 0, 0 132 | } 133 | if UnsafeString(field) == key { 134 | // start location 135 | l := len(raw) 136 | start = l - len(bts) 137 | bts, err = Skip(bts) 138 | if err != nil { 139 | return 0, 0 140 | } 141 | end = l - len(bts) 142 | return 143 | } 144 | bts, err = Skip(bts) 145 | if err != nil { 146 | return 0, 0 147 | } 148 | } 149 | return 0, 0 150 | } 151 | 152 | // locate key AND value 153 | func locateKV(raw []byte, key string) (start int, end int) { 154 | var ( 155 | sz uint32 156 | bts []byte 157 | field []byte 158 | err error 159 | ) 160 | sz, bts, err = ReadMapHeaderBytes(raw) 161 | if err != nil { 162 | return 0, 0 163 | } 164 | 165 | for i := uint32(0); i < sz; i++ { 166 | tmp := len(bts) 167 | field, bts, err = ReadStringZC(bts) 168 | if err != nil { 169 | return 0, 0 170 | } 171 | if UnsafeString(field) == key { 172 | start = len(raw) - tmp 173 | bts, err = Skip(bts) 174 | if err != nil { 175 | return 0, 0 176 | } 177 | end = len(raw) - len(bts) 178 | return 179 | } 180 | bts, err = Skip(bts) 181 | if err != nil { 182 | return 0, 0 183 | } 184 | } 185 | return 0, 0 186 | } 187 | 188 | // delta is delta on map size 189 | func resizeMap(raw []byte, delta int64) []byte { 190 | var sz int64 191 | switch raw[0] { 192 | case mmap16: 193 | sz = int64(big.Uint16(raw[1:])) 194 | if sz+delta <= math.MaxUint16 { 195 | big.PutUint16(raw[1:], uint16(sz+delta)) 196 | return raw 197 | } 198 | if cap(raw)-len(raw) >= 2 { 199 | raw = raw[0 : len(raw)+2] 200 | copy(raw[5:], raw[3:]) 201 | raw[0] = mmap32 202 | big.PutUint32(raw[1:], uint32(sz+delta)) 203 | return raw 204 | } 205 | n := make([]byte, 0, len(raw)+5) 206 | n = AppendMapHeader(n, uint32(sz+delta)) 207 | return append(n, raw[3:]...) 208 | 209 | case mmap32: 210 | sz = int64(big.Uint32(raw[1:])) 211 | big.PutUint32(raw[1:], uint32(sz+delta)) 212 | return raw 213 | 214 | default: 215 | sz = int64(rfixmap(raw[0])) 216 | if sz+delta < 16 { 217 | raw[0] = wfixmap(uint8(sz + delta)) 218 | return raw 219 | } else if sz+delta <= math.MaxUint16 { 220 | if cap(raw)-len(raw) >= 2 { 221 | raw = raw[0 : len(raw)+2] 222 | copy(raw[3:], raw[1:]) 223 | raw[0] = mmap16 224 | big.PutUint16(raw[1:], uint16(sz+delta)) 225 | return raw 226 | } 227 | n := make([]byte, 0, len(raw)+5) 228 | n = AppendMapHeader(n, uint32(sz+delta)) 229 | return append(n, raw[1:]...) 230 | } 231 | if cap(raw)-len(raw) >= 4 { 232 | raw = raw[0 : len(raw)+4] 233 | copy(raw[5:], raw[1:]) 234 | raw[0] = mmap32 235 | big.PutUint32(raw[1:], uint32(sz+delta)) 236 | return raw 237 | } 238 | n := make([]byte, 0, len(raw)+5) 239 | n = AppendMapHeader(n, uint32(sz+delta)) 240 | return append(n, raw[1:]...) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /msgp/edit_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestRemove(t *testing.T) { 10 | var buf bytes.Buffer 11 | w := NewWriter(&buf) 12 | w.WriteMapHeader(3) 13 | w.WriteString("first") 14 | w.WriteFloat64(-3.1) 15 | w.WriteString("second") 16 | w.WriteString("DELETE ME!!!") 17 | w.WriteString("third") 18 | w.WriteBytes([]byte("blah")) 19 | w.Flush() 20 | 21 | raw := Remove("second", buf.Bytes()) 22 | 23 | m, _, err := ReadMapStrIntfBytes(raw, nil) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if len(m) != 2 { 28 | t.Errorf("expected %d fields; found %d", 2, len(m)) 29 | } 30 | if _, ok := m["first"]; !ok { 31 | t.Errorf("field %q not found", "first") 32 | } 33 | if _, ok := m["third"]; !ok { 34 | t.Errorf("field %q not found", "third") 35 | } 36 | if _, ok := m["second"]; ok { 37 | t.Errorf("field %q (deleted field) still present", "second") 38 | } 39 | } 40 | 41 | func TestLocate(t *testing.T) { 42 | var buf bytes.Buffer 43 | en := NewWriter(&buf) 44 | en.WriteMapHeader(2) 45 | en.WriteString("thing_one") 46 | en.WriteString("value_one") 47 | en.WriteString("thing_two") 48 | en.WriteFloat64(2.0) 49 | en.Flush() 50 | 51 | field := Locate("thing_one", buf.Bytes()) 52 | if len(field) == 0 { 53 | t.Fatal("field not found") 54 | } 55 | 56 | if !HasKey("thing_one", buf.Bytes()) { 57 | t.Fatal("field not found") 58 | } 59 | 60 | var zbuf bytes.Buffer 61 | w := NewWriter(&zbuf) 62 | w.WriteString("value_one") 63 | w.Flush() 64 | 65 | if !bytes.Equal(zbuf.Bytes(), field) { 66 | t.Errorf("got %q; wanted %q", field, zbuf.Bytes()) 67 | } 68 | 69 | zbuf.Reset() 70 | w.WriteFloat64(2.0) 71 | w.Flush() 72 | field = Locate("thing_two", buf.Bytes()) 73 | if len(field) == 0 { 74 | t.Fatal("field not found") 75 | } 76 | if !bytes.Equal(zbuf.Bytes(), field) { 77 | t.Errorf("got %q; wanted %q", field, zbuf.Bytes()) 78 | } 79 | 80 | field = Locate("nope", buf.Bytes()) 81 | if len(field) != 0 { 82 | t.Fatalf("wanted a zero-length returned slice") 83 | } 84 | } 85 | 86 | func TestReplace(t *testing.T) { 87 | // there are 4 cases that need coverage: 88 | // - new value is smaller than old value 89 | // - new value is the same size as the old value 90 | // - new value is larger than old, but fits within cap(b) 91 | // - new value is larger than old, and doesn't fit within cap(b) 92 | 93 | var buf bytes.Buffer 94 | en := NewWriter(&buf) 95 | en.WriteMapHeader(3) 96 | en.WriteString("thing_one") 97 | en.WriteString("value_one") 98 | en.WriteString("thing_two") 99 | en.WriteFloat64(2.0) 100 | en.WriteString("some_bytes") 101 | en.WriteBytes([]byte("here are some bytes")) 102 | en.Flush() 103 | 104 | // same-size replacement 105 | var fbuf bytes.Buffer 106 | w := NewWriter(&fbuf) 107 | w.WriteFloat64(4.0) 108 | w.Flush() 109 | 110 | // replace 2.0 with 4.0 in field two 111 | raw := Replace("thing_two", buf.Bytes(), fbuf.Bytes()) 112 | if len(raw) == 0 { 113 | t.Fatal("field not found") 114 | } 115 | var err error 116 | m := make(map[string]interface{}) 117 | m, _, err = ReadMapStrIntfBytes(raw, m) 118 | if err != nil { 119 | t.Logf("%q", raw) 120 | t.Fatal(err) 121 | } 122 | 123 | if !reflect.DeepEqual(m["thing_two"], 4.0) { 124 | t.Errorf("wanted %v; got %v", 4.0, m["thing_two"]) 125 | } 126 | 127 | // smaller-size replacement 128 | // replace 2.0 with []byte("hi!") 129 | fbuf.Reset() 130 | w.WriteBytes([]byte("hi!")) 131 | w.Flush() 132 | raw = Replace("thing_two", raw, fbuf.Bytes()) 133 | if len(raw) == 0 { 134 | t.Fatal("field not found") 135 | } 136 | 137 | m, _, err = ReadMapStrIntfBytes(raw, m) 138 | if err != nil { 139 | t.Logf("%q", raw) 140 | t.Fatal(err) 141 | } 142 | 143 | if !reflect.DeepEqual(m["thing_two"], []byte("hi!")) { 144 | t.Errorf("wanted %v; got %v", []byte("hi!"), m["thing_two"]) 145 | } 146 | 147 | // larger-size replacement 148 | fbuf.Reset() 149 | w.WriteBytes([]byte("some even larger bytes than before")) 150 | w.Flush() 151 | raw = Replace("some_bytes", raw, fbuf.Bytes()) 152 | if len(raw) == 0 { 153 | t.Logf("%q", raw) 154 | t.Fatal(err) 155 | } 156 | 157 | m, _, err = ReadMapStrIntfBytes(raw, m) 158 | if err != nil { 159 | t.Logf("%q", raw) 160 | t.Fatal(err) 161 | } 162 | 163 | if !reflect.DeepEqual(m["some_bytes"], []byte("some even larger bytes than before")) { 164 | t.Errorf("wanted %v; got %v", []byte("hello there!"), m["some_bytes"]) 165 | } 166 | 167 | // identical in-place replacement 168 | field := Locate("some_bytes", raw) 169 | newraw := CopyReplace("some_bytes", raw, field) 170 | 171 | if !bytes.Equal(newraw, raw) { 172 | t.Logf("in: %q", raw) 173 | t.Logf("out: %q", newraw) 174 | t.Error("bytes not equal after copyreplace") 175 | } 176 | } 177 | 178 | func BenchmarkLocate(b *testing.B) { 179 | var buf bytes.Buffer 180 | en := NewWriter(&buf) 181 | en.WriteMapHeader(3) 182 | en.WriteString("thing_one") 183 | en.WriteString("value_one") 184 | en.WriteString("thing_two") 185 | en.WriteFloat64(2.0) 186 | en.WriteString("thing_three") 187 | en.WriteBytes([]byte("hello!")) 188 | en.Flush() 189 | 190 | raw := buf.Bytes() 191 | // bytes/s will be the number of bytes traversed per unit of time 192 | field := Locate("thing_three", raw) 193 | b.SetBytes(int64(len(raw) - len(field))) 194 | b.ReportAllocs() 195 | b.ResetTimer() 196 | for i := 0; i < b.N; i++ { 197 | Locate("thing_three", raw) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /msgp/elsize.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | func calcBytespec(v byte) bytespec { 4 | // single byte values 5 | switch v { 6 | 7 | case mnil: 8 | return bytespec{size: 1, extra: constsize, typ: NilType} 9 | case mfalse: 10 | return bytespec{size: 1, extra: constsize, typ: BoolType} 11 | case mtrue: 12 | return bytespec{size: 1, extra: constsize, typ: BoolType} 13 | case mbin8: 14 | return bytespec{size: 2, extra: extra8, typ: BinType} 15 | case mbin16: 16 | return bytespec{size: 3, extra: extra16, typ: BinType} 17 | case mbin32: 18 | return bytespec{size: 5, extra: extra32, typ: BinType} 19 | case mext8: 20 | return bytespec{size: 3, extra: extra8, typ: ExtensionType} 21 | case mext16: 22 | return bytespec{size: 4, extra: extra16, typ: ExtensionType} 23 | case mext32: 24 | return bytespec{size: 6, extra: extra32, typ: ExtensionType} 25 | case mfloat32: 26 | return bytespec{size: 5, extra: constsize, typ: Float32Type} 27 | case mfloat64: 28 | return bytespec{size: 9, extra: constsize, typ: Float64Type} 29 | case muint8: 30 | return bytespec{size: 2, extra: constsize, typ: UintType} 31 | case muint16: 32 | return bytespec{size: 3, extra: constsize, typ: UintType} 33 | case muint32: 34 | return bytespec{size: 5, extra: constsize, typ: UintType} 35 | case muint64: 36 | return bytespec{size: 9, extra: constsize, typ: UintType} 37 | case mint8: 38 | return bytespec{size: 2, extra: constsize, typ: IntType} 39 | case mint16: 40 | return bytespec{size: 3, extra: constsize, typ: IntType} 41 | case mint32: 42 | return bytespec{size: 5, extra: constsize, typ: IntType} 43 | case mint64: 44 | return bytespec{size: 9, extra: constsize, typ: IntType} 45 | case mfixext1: 46 | return bytespec{size: 3, extra: constsize, typ: ExtensionType} 47 | case mfixext2: 48 | return bytespec{size: 4, extra: constsize, typ: ExtensionType} 49 | case mfixext4: 50 | return bytespec{size: 6, extra: constsize, typ: ExtensionType} 51 | case mfixext8: 52 | return bytespec{size: 10, extra: constsize, typ: ExtensionType} 53 | case mfixext16: 54 | return bytespec{size: 18, extra: constsize, typ: ExtensionType} 55 | case mstr8: 56 | return bytespec{size: 2, extra: extra8, typ: StrType} 57 | case mstr16: 58 | return bytespec{size: 3, extra: extra16, typ: StrType} 59 | case mstr32: 60 | return bytespec{size: 5, extra: extra32, typ: StrType} 61 | case marray16: 62 | return bytespec{size: 3, extra: array16v, typ: ArrayType} 63 | case marray32: 64 | return bytespec{size: 5, extra: array32v, typ: ArrayType} 65 | case mmap16: 66 | return bytespec{size: 3, extra: map16v, typ: MapType} 67 | case mmap32: 68 | return bytespec{size: 5, extra: map32v, typ: MapType} 69 | } 70 | 71 | switch { 72 | 73 | // fixint 74 | case v >= mfixint && v < 0x80: 75 | return bytespec{size: 1, extra: constsize, typ: IntType} 76 | 77 | // fixstr gets constsize, since the prefix yields the size 78 | case v >= mfixstr && v < 0xc0: 79 | return bytespec{size: 1 + rfixstr(v), extra: constsize, typ: StrType} 80 | 81 | // fixmap 82 | case v >= mfixmap && v < 0x90: 83 | return bytespec{size: 1, extra: varmode(2 * rfixmap(v)), typ: MapType} 84 | 85 | // fixarray 86 | case v >= mfixarray && v < 0xa0: 87 | return bytespec{size: 1, extra: varmode(rfixarray(v)), typ: ArrayType} 88 | 89 | // nfixint 90 | case v >= mnfixint && uint16(v) < 0x100: 91 | return bytespec{size: 1, extra: constsize, typ: IntType} 92 | 93 | } 94 | 95 | // 0xC1 is unused per the spec and falls through to here, 96 | // everything else is covered above 97 | 98 | return bytespec{} 99 | } 100 | 101 | func getType(v byte) Type { 102 | return getBytespec(v).typ 103 | } 104 | 105 | // a valid bytespsec has 106 | // non-zero 'size' and 107 | // non-zero 'typ' 108 | type bytespec struct { 109 | size uint8 // prefix size information 110 | extra varmode // extra size information 111 | typ Type // type 112 | _ byte // makes bytespec 4 bytes (yes, this matters) 113 | } 114 | 115 | // size mode 116 | // if positive, # elements for composites 117 | type varmode int8 118 | 119 | const ( 120 | constsize varmode = 0 // constant size (size bytes + uint8(varmode) objects) 121 | extra8 varmode = -1 // has uint8(p[1]) extra bytes 122 | extra16 varmode = -2 // has be16(p[1:]) extra bytes 123 | extra32 varmode = -3 // has be32(p[1:]) extra bytes 124 | map16v varmode = -4 // use map16 125 | map32v varmode = -5 // use map32 126 | array16v varmode = -6 // use array16 127 | array32v varmode = -7 // use array32 128 | ) 129 | -------------------------------------------------------------------------------- /msgp/elsize_default.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | // +build !tinygo 3 | 4 | package msgp 5 | 6 | // size of every object on the wire, 7 | // plus type information. gives us 8 | // constant-time type information 9 | // for traversing composite objects. 10 | var sizes [256]bytespec 11 | 12 | func init() { 13 | for i := 0; i < 256; i++ { 14 | sizes[i] = calcBytespec(byte(i)) 15 | } 16 | } 17 | 18 | // getBytespec gets inlined to a simple array index 19 | func getBytespec(v byte) bytespec { 20 | return sizes[v] 21 | } 22 | -------------------------------------------------------------------------------- /msgp/elsize_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import "testing" 4 | 5 | func TestBytespec(t *testing.T) { 6 | // verify that bytespec refactor for TinyGo behaves the same as the old code 7 | 8 | // previous sizes array setup verbatim: 9 | 10 | // size of every object on the wire, 11 | // plus type information. gives us 12 | // constant-time type information 13 | // for traversing composite objects. 14 | // 15 | sizes := [256]bytespec{ 16 | mnil: {size: 1, extra: constsize, typ: NilType}, 17 | mfalse: {size: 1, extra: constsize, typ: BoolType}, 18 | mtrue: {size: 1, extra: constsize, typ: BoolType}, 19 | mbin8: {size: 2, extra: extra8, typ: BinType}, 20 | mbin16: {size: 3, extra: extra16, typ: BinType}, 21 | mbin32: {size: 5, extra: extra32, typ: BinType}, 22 | mext8: {size: 3, extra: extra8, typ: ExtensionType}, 23 | mext16: {size: 4, extra: extra16, typ: ExtensionType}, 24 | mext32: {size: 6, extra: extra32, typ: ExtensionType}, 25 | mfloat32: {size: 5, extra: constsize, typ: Float32Type}, 26 | mfloat64: {size: 9, extra: constsize, typ: Float64Type}, 27 | muint8: {size: 2, extra: constsize, typ: UintType}, 28 | muint16: {size: 3, extra: constsize, typ: UintType}, 29 | muint32: {size: 5, extra: constsize, typ: UintType}, 30 | muint64: {size: 9, extra: constsize, typ: UintType}, 31 | mint8: {size: 2, extra: constsize, typ: IntType}, 32 | mint16: {size: 3, extra: constsize, typ: IntType}, 33 | mint32: {size: 5, extra: constsize, typ: IntType}, 34 | mint64: {size: 9, extra: constsize, typ: IntType}, 35 | mfixext1: {size: 3, extra: constsize, typ: ExtensionType}, 36 | mfixext2: {size: 4, extra: constsize, typ: ExtensionType}, 37 | mfixext4: {size: 6, extra: constsize, typ: ExtensionType}, 38 | mfixext8: {size: 10, extra: constsize, typ: ExtensionType}, 39 | mfixext16: {size: 18, extra: constsize, typ: ExtensionType}, 40 | mstr8: {size: 2, extra: extra8, typ: StrType}, 41 | mstr16: {size: 3, extra: extra16, typ: StrType}, 42 | mstr32: {size: 5, extra: extra32, typ: StrType}, 43 | marray16: {size: 3, extra: array16v, typ: ArrayType}, 44 | marray32: {size: 5, extra: array32v, typ: ArrayType}, 45 | mmap16: {size: 3, extra: map16v, typ: MapType}, 46 | mmap32: {size: 5, extra: map32v, typ: MapType}, 47 | } 48 | 49 | // set up fixed fields 50 | 51 | // fixint 52 | for i := mfixint; i < 0x80; i++ { 53 | sizes[i] = bytespec{size: 1, extra: constsize, typ: IntType} 54 | } 55 | 56 | // nfixint 57 | for i := uint16(mnfixint); i < 0x100; i++ { 58 | sizes[uint8(i)] = bytespec{size: 1, extra: constsize, typ: IntType} 59 | } 60 | 61 | // fixstr gets constsize, 62 | // since the prefix yields the size 63 | for i := mfixstr; i < 0xc0; i++ { 64 | sizes[i] = bytespec{size: 1 + rfixstr(i), extra: constsize, typ: StrType} 65 | } 66 | 67 | // fixmap 68 | for i := mfixmap; i < 0x90; i++ { 69 | sizes[i] = bytespec{size: 1, extra: varmode(2 * rfixmap(i)), typ: MapType} 70 | } 71 | 72 | // fixarray 73 | for i := mfixarray; i < 0xa0; i++ { 74 | sizes[i] = bytespec{size: 1, extra: varmode(rfixarray(i)), typ: ArrayType} 75 | } 76 | 77 | // compare all values to calcBytespec 78 | for i := 0; i < 256; i++ { 79 | sizeb := sizes[byte(i)] 80 | cb := calcBytespec(byte(i)) 81 | if sizeb != cb { 82 | t.Errorf("at index 0x%x calculated sizes array %#v does not match calcBytespec %#v", i, sizeb, cb) 83 | } 84 | 85 | // all except the unused byte C1 should have non-zero size 86 | if i != 0xC1 && sizeb.size == 0 { 87 | t.Errorf("unexpected zero size for index 0x%x", i) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /msgp/elsize_tinygo.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package msgp 5 | 6 | // for tinygo, getBytespec just calls calcBytespec 7 | // a simple/slow function with a switch statement - 8 | // doesn't require any heap alloc, moves the space 9 | // requirements into code instad of ram 10 | 11 | func getBytespec(v byte) bytespec { 12 | return calcBytespec(v) 13 | } 14 | -------------------------------------------------------------------------------- /msgp/errors_default.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | // +build !tinygo 3 | 4 | package msgp 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | ) 10 | 11 | // ctxString converts the incoming interface{} slice into a single string. 12 | func ctxString(ctx []interface{}) string { 13 | out := "" 14 | for idx, cv := range ctx { 15 | if idx > 0 { 16 | out += "/" 17 | } 18 | out += fmt.Sprintf("%v", cv) 19 | } 20 | return out 21 | } 22 | 23 | func quoteStr(s string) string { 24 | return strconv.Quote(s) 25 | } 26 | -------------------------------------------------------------------------------- /msgp/errors_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestWrapVanillaErrorWithNoAdditionalContext(t *testing.T) { 12 | err := errors.New("test") 13 | w := WrapError(err) 14 | if w == err { 15 | t.Fatal() 16 | } 17 | if w.Error() != err.Error() { 18 | t.Fatal() 19 | } 20 | if w.(errWrapped).Resumable() { 21 | t.Fatal() 22 | } 23 | } 24 | 25 | func TestWrapVanillaErrorWithAdditionalContext(t *testing.T) { 26 | err := errors.New("test") 27 | w := WrapError(err, "foo", "bar") 28 | if w == err { 29 | t.Fatal() 30 | } 31 | if w.Error() == err.Error() { 32 | t.Fatal() 33 | } 34 | if w.(Error).Resumable() { 35 | t.Fatal() 36 | } 37 | if !strings.HasPrefix(w.Error(), err.Error()) { 38 | t.Fatal() 39 | } 40 | rest := w.Error()[len(err.Error()):] 41 | if rest != " at foo/bar" { 42 | t.Fatal() 43 | } 44 | } 45 | 46 | func TestWrapResumableError(t *testing.T) { 47 | err := ArrayError{} 48 | w := WrapError(err) 49 | if !w.(Error).Resumable() { 50 | t.Fatal() 51 | } 52 | } 53 | 54 | func TestWrapMultiple(t *testing.T) { 55 | err := &TypeError{} 56 | w := WrapError(WrapError(err, "b"), "a") 57 | expected := `msgp: attempted to decode type "" with method for "" at a/b` 58 | if expected != w.Error() { 59 | t.Fatal() 60 | } 61 | } 62 | 63 | func TestCause(t *testing.T) { 64 | for idx, err := range []error{ 65 | errors.New("test"), 66 | ArrayError{}, 67 | &ErrUnsupportedType{}, 68 | } { 69 | t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { 70 | cerr := WrapError(err, "test") 71 | if cerr == err { 72 | t.Fatal() 73 | } 74 | if Cause(err) != err { 75 | t.Fatal() 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestCauseShortByte(t *testing.T) { 82 | err := ErrShortBytes 83 | cerr := WrapError(err, "test") 84 | if cerr != err { 85 | t.Fatal() 86 | } 87 | if Cause(err) != err { 88 | t.Fatal() 89 | } 90 | } 91 | 92 | func TestUnwrap(t *testing.T) { 93 | // check errors that get wrapped 94 | for idx, err := range []error{ 95 | errors.New("test"), 96 | io.EOF, 97 | } { 98 | t.Run(fmt.Sprintf("wrapped_%d", idx), func(t *testing.T) { 99 | cerr := WrapError(err, "test") 100 | if cerr == err { 101 | t.Fatal() 102 | } 103 | uwerr := errors.Unwrap(cerr) 104 | if uwerr != err { 105 | t.Fatal() 106 | } 107 | if !errors.Is(cerr, err) { 108 | t.Fatal() 109 | } 110 | }) 111 | } 112 | 113 | // check errors where only context is applied 114 | for idx, err := range []error{ 115 | ArrayError{}, 116 | &ErrUnsupportedType{}, 117 | } { 118 | t.Run(fmt.Sprintf("ctx_only_%d", idx), func(t *testing.T) { 119 | cerr := WrapError(err, "test") 120 | if cerr == err { 121 | t.Fatal() 122 | } 123 | if errors.Unwrap(cerr) != nil { 124 | t.Fatal() 125 | } 126 | }) 127 | } 128 | } 129 | 130 | func TestSimpleQuoteStr(t *testing.T) { 131 | // check some cases for simpleQuoteStr 132 | type tcase struct { 133 | in string 134 | out string 135 | } 136 | tcaseList := []tcase{ 137 | { 138 | in: ``, 139 | out: `""`, 140 | }, 141 | { 142 | in: `abc`, 143 | out: `"abc"`, 144 | }, 145 | { 146 | in: `"`, 147 | out: `"\""`, 148 | }, 149 | { 150 | in: `'`, 151 | out: `"'"`, 152 | }, 153 | { 154 | in: `on🔥!`, 155 | out: `"on\xf0\x9f\x94\xa5!"`, 156 | }, 157 | { 158 | in: "line\r\nbr", 159 | out: `"line\r\nbr"`, 160 | }, 161 | { 162 | in: "\x00", 163 | out: `"\x00"`, 164 | }, 165 | { // invalid UTF-8 should make no difference but check it regardless 166 | in: "not\x80valid", 167 | out: `"not\x80valid"`, 168 | }, 169 | } 170 | 171 | for i, tc := range tcaseList { 172 | tc := tc 173 | t.Run(fmt.Sprint(i), func(t *testing.T) { 174 | out := simpleQuoteStr(tc.in) 175 | if out != tc.out { 176 | t.Errorf("input %q; expected: %s; but got: %s", tc.in, tc.out, out) 177 | } 178 | }) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /msgp/errors_tinygo.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package msgp 5 | 6 | import ( 7 | "reflect" 8 | ) 9 | 10 | // ctxString converts the incoming interface{} slice into a single string, 11 | // without using fmt under tinygo 12 | func ctxString(ctx []interface{}) string { 13 | out := "" 14 | for idx, cv := range ctx { 15 | if idx > 0 { 16 | out += "/" 17 | } 18 | out += ifToStr(cv) 19 | } 20 | return out 21 | } 22 | 23 | type stringer interface { 24 | String() string 25 | } 26 | 27 | func ifToStr(i interface{}) string { 28 | switch v := i.(type) { 29 | case stringer: 30 | return v.String() 31 | case error: 32 | return v.Error() 33 | case string: 34 | return v 35 | default: 36 | return reflect.ValueOf(i).String() 37 | } 38 | } 39 | 40 | func quoteStr(s string) string { 41 | return simpleQuoteStr(s) 42 | } 43 | -------------------------------------------------------------------------------- /msgp/extension_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | var extSizes = [...]int{0, 1, 2, 4, 8, 16, int(tint8), int(tuint16), int(tuint32)} 10 | 11 | func randomExt() RawExtension { 12 | e := RawExtension{} 13 | e.Type = int8(rand.Int()) 14 | e.Data = RandBytes(extSizes[rand.Intn(len(extSizes))]) 15 | return e 16 | } 17 | 18 | func TestReadWriteExtension(t *testing.T) { 19 | var buf bytes.Buffer 20 | en := NewWriter(&buf) 21 | dc := NewReader(&buf) 22 | 23 | t.Run("interface", func(t *testing.T) { 24 | for i := 0; i < 25; i++ { 25 | buf.Reset() 26 | e := randomExt() 27 | en.WriteExtension(&e) 28 | en.Flush() 29 | err := dc.ReadExtension(&e) 30 | if err != nil { 31 | t.Errorf("error with extension (length %d): %s", len(buf.Bytes()), err) 32 | } 33 | } 34 | }) 35 | 36 | t.Run("raw", func(t *testing.T) { 37 | for i := 0; i < 25; i++ { 38 | buf.Reset() 39 | e := randomExt() 40 | en.WriteExtensionRaw(e.Type, e.Data) 41 | en.Flush() 42 | typ, payload, err := dc.ReadExtensionRaw() 43 | if err != nil { 44 | t.Errorf("error with extension (length %d): %s", len(buf.Bytes()), err) 45 | } 46 | if typ != e.Type || !bytes.Equal(payload, e.Data) { 47 | t.Errorf("extension mismatch: %d %x != %d %x", typ, payload, e.Type, e.Data) 48 | } 49 | } 50 | }) 51 | } 52 | 53 | func TestReadWriteLargeExtensionRaw(t *testing.T) { 54 | var buf bytes.Buffer 55 | en := NewWriter(&buf) 56 | dc := NewReader(&buf) 57 | 58 | largeExt := RawExtension{Type: int8(rand.Int()), Data: RandBytes(int(tuint32))} 59 | 60 | err := en.WriteExtensionRaw(largeExt.Type, largeExt.Data) 61 | if err != nil { 62 | t.Errorf("error with large extension write: %s", err) 63 | } 64 | // write a nil as a marker 65 | err = en.WriteNil() 66 | if err != nil { 67 | t.Errorf("error with large extension write: %s", err) 68 | } 69 | en.Flush() 70 | 71 | typ, payload, err := dc.ReadExtensionRaw() 72 | if err != nil { 73 | t.Errorf("error with large extension read: %s", err) 74 | } 75 | if typ != largeExt.Type || !bytes.Equal(payload, largeExt.Data) { 76 | t.Errorf("large extension mismatch: %d %x != %d %x", typ, payload, largeExt.Type, largeExt.Data) 77 | } 78 | err = dc.ReadNil() 79 | if err != nil { 80 | t.Errorf("error with large extension read: %s", err) 81 | } 82 | } 83 | 84 | func TestExtensionRawStackBuffer(t *testing.T) { 85 | var buf bytes.Buffer 86 | en := NewWriter(&buf) 87 | dc := NewReader(&buf) 88 | 89 | const bufSize = 128 90 | e := &RawExtension{Type: int8(rand.Int()), Data: RandBytes(bufSize)} 91 | 92 | allocs := testing.AllocsPerRun(100, func() { 93 | buf.Reset() 94 | 95 | var staticBuf [bufSize]byte 96 | slc := e.Data[:rand.Intn(bufSize)] 97 | copy(staticBuf[:], slc) 98 | 99 | err := en.WriteExtensionRaw(e.Type, staticBuf[:len(slc)]) 100 | if err != nil { 101 | t.Errorf("error writing extension: %s", err) 102 | } 103 | en.Flush() 104 | 105 | typ, payload, err := dc.ReadExtensionRaw() 106 | if err != nil { 107 | t.Errorf("error reading extension: %s", err) 108 | } 109 | if typ != e.Type || !bytes.Equal(payload, slc) { 110 | t.Errorf("extension mismatch: %d %x != %d %x", typ, payload, e.Type, slc) 111 | } 112 | }) 113 | 114 | if allocs != 0 { 115 | t.Errorf("using stack allocated buffer with WriteExtensionRaw caused %f allocations", allocs) 116 | } 117 | } 118 | 119 | func TestReadWriteExtensionBytes(t *testing.T) { 120 | var bts []byte 121 | 122 | for i := 0; i < 24; i++ { 123 | e := randomExt() 124 | bts, _ = AppendExtension(bts[0:0], &e) 125 | _, err := ReadExtensionBytes(bts, &e) 126 | if err != nil { 127 | t.Errorf("error with extension (length %d): %s", len(bts), err) 128 | } 129 | } 130 | } 131 | 132 | func TestAppendAndWriteCompatibility(t *testing.T) { 133 | var bts []byte 134 | var buf bytes.Buffer 135 | en := NewWriter(&buf) 136 | 137 | for i := 0; i < 24; i++ { 138 | buf.Reset() 139 | e := randomExt() 140 | bts, _ = AppendExtension(bts[0:0], &e) 141 | en.WriteExtension(&e) 142 | en.Flush() 143 | 144 | if !bytes.Equal(buf.Bytes(), bts) { 145 | t.Errorf("the outputs are different:\n\t%x\n\t%x", buf.Bytes(), bts) 146 | } 147 | 148 | _, err := ReadExtensionBytes(bts, &e) 149 | if err != nil { 150 | t.Errorf("error with extension (length %d): %s", len(bts), err) 151 | } 152 | } 153 | } 154 | 155 | func BenchmarkExtensionReadWrite(b *testing.B) { 156 | var buf bytes.Buffer 157 | en := NewWriter(&buf) 158 | dc := NewReader(&buf) 159 | 160 | b.Run("interface", func(b *testing.B) { 161 | b.ReportAllocs() 162 | 163 | for i := 0; i < b.N; i++ { 164 | buf.Reset() 165 | 166 | e := randomExt() 167 | err := en.WriteExtension(&e) 168 | if err != nil { 169 | b.Errorf("error writing extension: %s", err) 170 | } 171 | en.Flush() 172 | 173 | err = dc.ReadExtension(&e) 174 | if err != nil { 175 | b.Errorf("error reading extension: %s", err) 176 | } 177 | } 178 | }) 179 | 180 | b.Run("raw", func(b *testing.B) { 181 | // this should have zero allocations 182 | b.ReportAllocs() 183 | 184 | for i := 0; i < b.N; i++ { 185 | buf.Reset() 186 | 187 | e := randomExt() 188 | err := en.WriteExtensionRaw(e.Type, e.Data) 189 | if err != nil { 190 | b.Errorf("error writing extension: %s", err) 191 | } 192 | en.Flush() 193 | 194 | typ, payload, err := dc.ReadExtensionRaw() 195 | if err != nil { 196 | b.Errorf("error reading extension: %s", err) 197 | } 198 | if typ != e.Type || !bytes.Equal(payload, e.Data) { 199 | b.Errorf("extension mismatch: %d %x != %d %x", typ, payload, e.Type, e.Data) 200 | } 201 | 202 | buf.Reset() 203 | } 204 | }) 205 | } 206 | -------------------------------------------------------------------------------- /msgp/file.go: -------------------------------------------------------------------------------- 1 | //go:build (linux || darwin || dragonfly || freebsd || illumos || netbsd || openbsd) && !appengine && !tinygo 2 | // +build linux darwin dragonfly freebsd illumos netbsd openbsd 3 | // +build !appengine 4 | // +build !tinygo 5 | 6 | package msgp 7 | 8 | import ( 9 | "os" 10 | "syscall" 11 | ) 12 | 13 | // ReadFile reads a file into 'dst' using 14 | // a read-only memory mapping. Consequently, 15 | // the file must be mmap-able, and the 16 | // Unmarshaler should never write to 17 | // the source memory. (Methods generated 18 | // by the msgp tool obey that constraint, but 19 | // user-defined implementations may not.) 20 | // 21 | // Reading and writing through file mappings 22 | // is only efficient for large files; small 23 | // files are best read and written using 24 | // the ordinary streaming interfaces. 25 | func ReadFile(dst Unmarshaler, file *os.File) error { 26 | stat, err := file.Stat() 27 | if err != nil { 28 | return err 29 | } 30 | data, err := syscall.Mmap(int(file.Fd()), 0, int(stat.Size()), syscall.PROT_READ, syscall.MAP_SHARED) 31 | if err != nil { 32 | return err 33 | } 34 | adviseRead(data) 35 | _, err = dst.UnmarshalMsg(data) 36 | uerr := syscall.Munmap(data) 37 | if err == nil { 38 | err = uerr 39 | } 40 | return err 41 | } 42 | 43 | // MarshalSizer is the combination 44 | // of the Marshaler and Sizer 45 | // interfaces. 46 | type MarshalSizer interface { 47 | Marshaler 48 | Sizer 49 | } 50 | 51 | // WriteFile writes a file from 'src' using 52 | // memory mapping. It overwrites the entire 53 | // contents of the previous file. 54 | // The mapping size is calculated 55 | // using the `Msgsize()` method 56 | // of 'src', so it must produce a result 57 | // equal to or greater than the actual encoded 58 | // size of the object. Otherwise, 59 | // a fault (SIGBUS) will occur. 60 | // 61 | // Reading and writing through file mappings 62 | // is only efficient for large files; small 63 | // files are best read and written using 64 | // the ordinary streaming interfaces. 65 | // 66 | // NOTE: The performance of this call 67 | // is highly OS- and filesystem-dependent. 68 | // Users should take care to test that this 69 | // performs as expected in a production environment. 70 | // (Linux users should run a kernel and filesystem 71 | // that support fallocate(2) for the best results.) 72 | func WriteFile(src MarshalSizer, file *os.File) error { 73 | sz := src.Msgsize() 74 | err := fallocate(file, int64(sz)) 75 | if err != nil { 76 | return err 77 | } 78 | data, err := syscall.Mmap(int(file.Fd()), 0, sz, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) 79 | if err != nil { 80 | return err 81 | } 82 | adviseWrite(data) 83 | chunk := data[:0] 84 | chunk, err = src.MarshalMsg(chunk) 85 | if err != nil { 86 | return err 87 | } 88 | uerr := syscall.Munmap(data) 89 | if uerr != nil { 90 | return uerr 91 | } 92 | return file.Truncate(int64(len(chunk))) 93 | } 94 | -------------------------------------------------------------------------------- /msgp/file_port.go: -------------------------------------------------------------------------------- 1 | //go:build windows || appengine || tinygo 2 | // +build windows appengine tinygo 3 | 4 | package msgp 5 | 6 | import ( 7 | "io" 8 | "os" 9 | ) 10 | 11 | // MarshalSizer is the combination 12 | // of the Marshaler and Sizer 13 | // interfaces. 14 | type MarshalSizer interface { 15 | Marshaler 16 | Sizer 17 | } 18 | 19 | func ReadFile(dst Unmarshaler, file *os.File) error { 20 | if u, ok := dst.(Decodable); ok { 21 | return u.DecodeMsg(NewReader(file)) 22 | } 23 | 24 | data, err := io.ReadAll(file) 25 | if err != nil { 26 | return err 27 | } 28 | _, err = dst.UnmarshalMsg(data) 29 | return err 30 | } 31 | 32 | func WriteFile(src MarshalSizer, file *os.File) error { 33 | if e, ok := src.(Encodable); ok { 34 | w := NewWriter(file) 35 | err := e.EncodeMsg(w) 36 | if err == nil { 37 | err = w.Flush() 38 | } 39 | return err 40 | } 41 | 42 | raw, err := src.MarshalMsg(nil) 43 | if err != nil { 44 | return err 45 | } 46 | _, err = file.Write(raw) 47 | return err 48 | } 49 | -------------------------------------------------------------------------------- /msgp/file_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin || dragonfly || freebsd || illumos || netbsd || openbsd 2 | // +build linux darwin dragonfly freebsd illumos netbsd openbsd 3 | 4 | package msgp_test 5 | 6 | import ( 7 | "bytes" 8 | "crypto/rand" 9 | "io" 10 | prand "math/rand" 11 | "os" 12 | "testing" 13 | 14 | "github.com/tinylib/msgp/msgp" 15 | ) 16 | 17 | type rawBytes []byte 18 | 19 | func (r rawBytes) MarshalMsg(b []byte) ([]byte, error) { 20 | return msgp.AppendBytes(b, []byte(r)), nil 21 | } 22 | 23 | func (r rawBytes) Msgsize() int { 24 | return msgp.BytesPrefixSize + len(r) 25 | } 26 | 27 | func (r *rawBytes) UnmarshalMsg(b []byte) ([]byte, error) { 28 | tmp, out, err := msgp.ReadBytesBytes(b, (*(*[]byte)(r))[:0]) 29 | *r = rawBytes(tmp) 30 | return out, err 31 | } 32 | 33 | func TestReadWriteFile(t *testing.T) { 34 | t.Parallel() 35 | 36 | f, err := os.Create("tmpfile") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | defer func() { 41 | f.Close() 42 | os.Remove("tmpfile") 43 | }() 44 | 45 | data := make([]byte, 1024*1024) 46 | rand.Read(data) 47 | 48 | err = msgp.WriteFile(rawBytes(data), f) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | var out rawBytes 54 | f.Seek(0, io.SeekStart) 55 | err = msgp.ReadFile(&out, f) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | if !bytes.Equal([]byte(out), []byte(data)) { 61 | t.Fatal("Input and output not equal.") 62 | } 63 | } 64 | 65 | var ( 66 | blobstrings = []string{"", "a string", "a longer string here!"} 67 | blobfloats = []float64{0.0, -1.0, 1.0, 3.1415926535} 68 | blobints = []int64{0, 1, -1, 80000, 1 << 30} 69 | blobbytes = [][]byte{{}, []byte("hello"), []byte(`{"is_json":true,"is_compact":"unable to determine"}`)} 70 | ) 71 | 72 | func BenchmarkWriteReadFile(b *testing.B) { 73 | // let's not run out of disk space... 74 | if b.N > 10000000 { 75 | b.N = 10000000 //nolint:staticcheck // ignoring "SA3001: should not assign to b.N (staticcheck)" as this should not usually happen. 76 | } 77 | 78 | fname := "bench-tmpfile" 79 | f, err := os.Create(fname) 80 | if err != nil { 81 | b.Fatal(err) 82 | } 83 | defer func(f *os.File, name string) { 84 | f.Close() 85 | os.Remove(name) 86 | }(f, fname) 87 | 88 | data := make(Blobs, b.N) 89 | 90 | for i := range data { 91 | data[i].Name = blobstrings[prand.Intn(len(blobstrings))] 92 | data[i].Float = blobfloats[prand.Intn(len(blobfloats))] 93 | data[i].Amount = blobints[prand.Intn(len(blobints))] 94 | data[i].Bytes = blobbytes[prand.Intn(len(blobbytes))] 95 | } 96 | 97 | b.SetBytes(int64(data.Msgsize() / b.N)) 98 | b.ResetTimer() 99 | err = msgp.WriteFile(data, f) 100 | if err != nil { 101 | b.Fatal(err) 102 | } 103 | err = msgp.ReadFile(&data, f) 104 | if err != nil { 105 | b.Fatal(err) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /msgp/floatbench_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkReadWriteFloat32(b *testing.B) { 8 | var f float32 = 3.9081 9 | bts := AppendFloat32([]byte{}, f) 10 | b.ResetTimer() 11 | for i := 0; i < b.N; i++ { 12 | bts = AppendFloat32(bts[0:0], f) 13 | f, bts, _ = ReadFloat32Bytes(bts) 14 | } 15 | } 16 | 17 | func BenchmarkReadWriteFloat64(b *testing.B) { 18 | var f float64 = 3.9081 19 | bts := AppendFloat64([]byte{}, f) 20 | b.ResetTimer() 21 | for i := 0; i < b.N; i++ { 22 | bts = AppendFloat64(bts[0:0], f) 23 | f, bts, _ = ReadFloat64Bytes(bts) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /msgp/integers.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import "encoding/binary" 4 | 5 | /* ---------------------------------- 6 | integer encoding utilities 7 | (inline-able) 8 | 9 | TODO(tinylib): there are faster, 10 | albeit non-portable solutions 11 | to the code below. implement 12 | byteswap? 13 | ---------------------------------- */ 14 | 15 | func putMint64(b []byte, i int64) { 16 | _ = b[8] // bounds check elimination 17 | 18 | b[0] = mint64 19 | b[1] = byte(i >> 56) 20 | b[2] = byte(i >> 48) 21 | b[3] = byte(i >> 40) 22 | b[4] = byte(i >> 32) 23 | b[5] = byte(i >> 24) 24 | b[6] = byte(i >> 16) 25 | b[7] = byte(i >> 8) 26 | b[8] = byte(i) 27 | } 28 | 29 | func getMint64(b []byte) int64 { 30 | _ = b[8] // bounds check elimination 31 | 32 | return (int64(b[1]) << 56) | (int64(b[2]) << 48) | 33 | (int64(b[3]) << 40) | (int64(b[4]) << 32) | 34 | (int64(b[5]) << 24) | (int64(b[6]) << 16) | 35 | (int64(b[7]) << 8) | (int64(b[8])) 36 | } 37 | 38 | func putMint32(b []byte, i int32) { 39 | _ = b[4] // bounds check elimination 40 | 41 | b[0] = mint32 42 | b[1] = byte(i >> 24) 43 | b[2] = byte(i >> 16) 44 | b[3] = byte(i >> 8) 45 | b[4] = byte(i) 46 | } 47 | 48 | func getMint32(b []byte) int32 { 49 | _ = b[4] // bounds check elimination 50 | 51 | return (int32(b[1]) << 24) | (int32(b[2]) << 16) | (int32(b[3]) << 8) | (int32(b[4])) 52 | } 53 | 54 | func putMint16(b []byte, i int16) { 55 | _ = b[2] // bounds check elimination 56 | 57 | b[0] = mint16 58 | b[1] = byte(i >> 8) 59 | b[2] = byte(i) 60 | } 61 | 62 | func getMint16(b []byte) (i int16) { 63 | _ = b[2] // bounds check elimination 64 | 65 | return (int16(b[1]) << 8) | int16(b[2]) 66 | } 67 | 68 | func putMint8(b []byte, i int8) { 69 | _ = b[1] // bounds check elimination 70 | 71 | b[0] = mint8 72 | b[1] = byte(i) 73 | } 74 | 75 | func getMint8(b []byte) (i int8) { 76 | return int8(b[1]) 77 | } 78 | 79 | func putMuint64(b []byte, u uint64) { 80 | _ = b[8] // bounds check elimination 81 | 82 | b[0] = muint64 83 | b[1] = byte(u >> 56) 84 | b[2] = byte(u >> 48) 85 | b[3] = byte(u >> 40) 86 | b[4] = byte(u >> 32) 87 | b[5] = byte(u >> 24) 88 | b[6] = byte(u >> 16) 89 | b[7] = byte(u >> 8) 90 | b[8] = byte(u) 91 | } 92 | 93 | func getMuint64(b []byte) uint64 { 94 | _ = b[8] // bounds check elimination 95 | 96 | return (uint64(b[1]) << 56) | (uint64(b[2]) << 48) | 97 | (uint64(b[3]) << 40) | (uint64(b[4]) << 32) | 98 | (uint64(b[5]) << 24) | (uint64(b[6]) << 16) | 99 | (uint64(b[7]) << 8) | (uint64(b[8])) 100 | } 101 | 102 | func putMuint32(b []byte, u uint32) { 103 | _ = b[4] // bounds check elimination 104 | 105 | b[0] = muint32 106 | b[1] = byte(u >> 24) 107 | b[2] = byte(u >> 16) 108 | b[3] = byte(u >> 8) 109 | b[4] = byte(u) 110 | } 111 | 112 | func getMuint32(b []byte) uint32 { 113 | _ = b[4] // bounds check elimination 114 | 115 | return (uint32(b[1]) << 24) | (uint32(b[2]) << 16) | (uint32(b[3]) << 8) | (uint32(b[4])) 116 | } 117 | 118 | func putMuint16(b []byte, u uint16) { 119 | _ = b[2] // bounds check elimination 120 | 121 | b[0] = muint16 122 | b[1] = byte(u >> 8) 123 | b[2] = byte(u) 124 | } 125 | 126 | func getMuint16(b []byte) uint16 { 127 | _ = b[2] // bounds check elimination 128 | 129 | return (uint16(b[1]) << 8) | uint16(b[2]) 130 | } 131 | 132 | func putMuint8(b []byte, u uint8) { 133 | _ = b[1] // bounds check elimination 134 | 135 | b[0] = muint8 136 | b[1] = byte(u) 137 | } 138 | 139 | func getMuint8(b []byte) uint8 { 140 | return uint8(b[1]) 141 | } 142 | 143 | func getUnix(b []byte) (sec int64, nsec int32) { 144 | sec = int64(binary.BigEndian.Uint64(b)) 145 | nsec = int32(binary.BigEndian.Uint32(b[8:])) 146 | 147 | return 148 | } 149 | 150 | func putUnix(b []byte, sec int64, nsec int32) { 151 | binary.BigEndian.PutUint64(b, uint64(sec)) 152 | binary.BigEndian.PutUint32(b[8:], uint32(nsec)) 153 | } 154 | 155 | /* ----------------------------- 156 | prefix utilities 157 | ----------------------------- */ 158 | 159 | // write prefix and uint8 160 | func prefixu8(b []byte, pre byte, sz uint8) { 161 | _ = b[1] // bounds check elimination 162 | 163 | b[0] = pre 164 | b[1] = byte(sz) 165 | } 166 | 167 | // write prefix and big-endian uint16 168 | func prefixu16(b []byte, pre byte, sz uint16) { 169 | _ = b[2] // bounds check elimination 170 | 171 | b[0] = pre 172 | b[1] = byte(sz >> 8) 173 | b[2] = byte(sz) 174 | } 175 | 176 | // write prefix and big-endian uint32 177 | func prefixu32(b []byte, pre byte, sz uint32) { 178 | _ = b[4] // bounds check elimination 179 | 180 | b[0] = pre 181 | b[1] = byte(sz >> 24) 182 | b[2] = byte(sz >> 16) 183 | b[3] = byte(sz >> 8) 184 | b[4] = byte(sz) 185 | } 186 | 187 | func prefixu64(b []byte, pre byte, sz uint64) { 188 | _ = b[8] // bounds check elimination 189 | 190 | b[0] = pre 191 | b[1] = byte(sz >> 56) 192 | b[2] = byte(sz >> 48) 193 | b[3] = byte(sz >> 40) 194 | b[4] = byte(sz >> 32) 195 | b[5] = byte(sz >> 24) 196 | b[6] = byte(sz >> 16) 197 | b[7] = byte(sz >> 8) 198 | b[8] = byte(sz) 199 | } 200 | -------------------------------------------------------------------------------- /msgp/integers_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkIntegers(b *testing.B) { 9 | bytes64 := make([]byte, 9) 10 | bytes32 := make([]byte, 5) 11 | bytes16 := make([]byte, 3) 12 | 13 | b.Run("Int64", func(b *testing.B) { 14 | b.Run("Put", func(b *testing.B) { 15 | for i := 0; i < b.N; i++ { 16 | putMint64(bytes64, -1234567890123456789) 17 | } 18 | }) 19 | b.Run("Get", func(b *testing.B) { 20 | putMint64(bytes64, -1234567890123456789) 21 | for i := 0; i < b.N; i++ { 22 | getMint64(bytes64) 23 | } 24 | }) 25 | }) 26 | b.Run("Int32", func(b *testing.B) { 27 | b.Run("Put", func(b *testing.B) { 28 | for i := 0; i < b.N; i++ { 29 | putMint32(bytes32, -123456789) 30 | } 31 | }) 32 | b.Run("Get", func(b *testing.B) { 33 | putMint32(bytes32, -123456789) 34 | for i := 0; i < b.N; i++ { 35 | getMint32(bytes32) 36 | } 37 | }) 38 | }) 39 | b.Run("Int16", func(b *testing.B) { 40 | b.Run("Put", func(b *testing.B) { 41 | for i := 0; i < b.N; i++ { 42 | putMint16(bytes16, -12345) 43 | } 44 | }) 45 | b.Run("Get", func(b *testing.B) { 46 | putMint16(bytes16, -12345) 47 | for i := 0; i < b.N; i++ { 48 | getMint16(bytes16) 49 | } 50 | }) 51 | }) 52 | 53 | b.Run("Uint64", func(b *testing.B) { 54 | b.Run("Put", func(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | putMuint64(bytes64, 1234567890123456789) 57 | } 58 | }) 59 | b.Run("Get", func(b *testing.B) { 60 | putMuint64(bytes64, 1234567890123456789) 61 | for i := 0; i < b.N; i++ { 62 | getMuint64(bytes64) 63 | } 64 | }) 65 | }) 66 | b.Run("Uint32", func(b *testing.B) { 67 | b.Run("Put", func(b *testing.B) { 68 | for i := 0; i < b.N; i++ { 69 | putMuint32(bytes32, 123456789) 70 | } 71 | }) 72 | b.Run("Get", func(b *testing.B) { 73 | putMuint32(bytes32, 123456789) 74 | for i := 0; i < b.N; i++ { 75 | getMuint32(bytes32) 76 | } 77 | }) 78 | }) 79 | b.Run("Uint16", func(b *testing.B) { 80 | b.Run("Put", func(b *testing.B) { 81 | for i := 0; i < b.N; i++ { 82 | putMuint16(bytes16, 12345) 83 | } 84 | }) 85 | b.Run("Get", func(b *testing.B) { 86 | putMuint16(bytes16, 12345) 87 | for i := 0; i < b.N; i++ { 88 | getMuint16(bytes16) 89 | } 90 | }) 91 | }) 92 | } 93 | 94 | func BenchmarkIntegersUnix(b *testing.B) { 95 | bytes := make([]byte, 12) 96 | var sec int64 = 1609459200 97 | var nsec int32 = 123456789 98 | 99 | b.Run("Get", func(b *testing.B) { 100 | binary.BigEndian.PutUint64(bytes, uint64(sec)) 101 | binary.BigEndian.PutUint32(bytes[8:], uint32(nsec)) 102 | for i := 0; i < b.N; i++ { 103 | getUnix(bytes) 104 | } 105 | }) 106 | 107 | b.Run("Put", func(b *testing.B) { 108 | for i := 0; i < b.N; i++ { 109 | putUnix(bytes, sec, nsec) 110 | } 111 | }) 112 | } 113 | 114 | func BenchmarkIntegersPrefix(b *testing.B) { 115 | bytesU16 := make([]byte, 3) 116 | bytesU32 := make([]byte, 5) 117 | bytesU64 := make([]byte, 9) 118 | 119 | b.Run("u16", func(b *testing.B) { 120 | var pre byte = 0x01 121 | var sz uint16 = 12345 122 | for i := 0; i < b.N; i++ { 123 | prefixu16(bytesU16, pre, sz) 124 | } 125 | }) 126 | b.Run("u32", func(b *testing.B) { 127 | var pre byte = 0x02 128 | var sz uint32 = 123456789 129 | for i := 0; i < b.N; i++ { 130 | prefixu32(bytesU32, pre, sz) 131 | } 132 | }) 133 | b.Run("u64", func(b *testing.B) { 134 | var pre byte = 0x03 135 | var sz uint64 = 1234567890123456789 136 | for i := 0; i < b.N; i++ { 137 | prefixu64(bytesU64, pre, sz) 138 | } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /msgp/json_bytes_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestUnmarshalJSON(t *testing.T) { 11 | var buf bytes.Buffer 12 | enc := NewWriter(&buf) 13 | enc.WriteMapHeader(5) 14 | 15 | enc.WriteString("thing_1") 16 | enc.WriteString("a string object") 17 | 18 | enc.WriteString("a_map") 19 | enc.WriteMapHeader(2) 20 | 21 | // INNER 22 | enc.WriteString("cmplx") 23 | enc.WriteComplex64(complex(1.0, 1.0)) 24 | enc.WriteString("int_b") 25 | enc.WriteInt64(-100) 26 | 27 | enc.WriteString("an extension") 28 | enc.WriteExtension(&RawExtension{Type: 1, Data: []byte("blaaahhh")}) 29 | 30 | enc.WriteString("some bytes") 31 | enc.WriteBytes([]byte("here are some bytes")) 32 | 33 | enc.WriteString("now") 34 | enc.WriteTime(time.Now()) 35 | 36 | enc.Flush() 37 | 38 | var js bytes.Buffer 39 | _, err := UnmarshalAsJSON(&js, buf.Bytes()) 40 | if err != nil { 41 | t.Logf("%s", js.Bytes()) 42 | t.Fatal(err) 43 | } 44 | mp := make(map[string]interface{}) 45 | err = json.Unmarshal(js.Bytes(), &mp) 46 | if err != nil { 47 | t.Log(js.String()) 48 | t.Fatalf("Error unmarshaling: %s", err) 49 | } 50 | 51 | if len(mp) != 5 { 52 | t.Errorf("map length should be %d, not %d", 5, len(mp)) 53 | } 54 | 55 | so, ok := mp["thing_1"] 56 | if !ok || so != "a string object" { 57 | t.Errorf("expected %q; got %q", "a string object", so) 58 | } 59 | 60 | if _, ok := mp["now"]; !ok { 61 | t.Error(`"now" field doesn't exist`) 62 | } 63 | 64 | c, ok := mp["a_map"] 65 | if !ok { 66 | t.Error(`"a_map" field doesn't exist`) 67 | } else { 68 | if m, ok := c.(map[string]interface{}); ok { 69 | if _, ok := m["cmplx"]; !ok { 70 | t.Error(`"a_map.cmplx" doesn't exist`) 71 | } 72 | } else { 73 | t.Error(`can't type-assert "c" to map[string]interface{}`) 74 | } 75 | } 76 | 77 | t.Logf("JSON: %s", js.Bytes()) 78 | } 79 | 80 | func BenchmarkUnmarshalAsJSON(b *testing.B) { 81 | var buf bytes.Buffer 82 | enc := NewWriter(&buf) 83 | enc.WriteMapHeader(4) 84 | 85 | enc.WriteString("thing_1") 86 | enc.WriteString("a string object") 87 | 88 | enc.WriteString("a_first_map") 89 | enc.WriteMapHeader(2) 90 | enc.WriteString("float_a") 91 | enc.WriteFloat32(1.0) 92 | enc.WriteString("int_b") 93 | enc.WriteInt64(-100) 94 | 95 | enc.WriteString("an array") 96 | enc.WriteArrayHeader(2) 97 | enc.WriteBool(true) 98 | enc.WriteUint(2089) 99 | 100 | enc.WriteString("a_second_map") 101 | enc.WriteMapStrStr(map[string]string{ 102 | "internal_one": "blah", 103 | "internal_two": "blahhh...", 104 | }) 105 | enc.Flush() 106 | 107 | var js bytes.Buffer 108 | bts := buf.Bytes() 109 | _, err := UnmarshalAsJSON(&js, bts) 110 | if err != nil { 111 | b.Fatal(err) 112 | } 113 | b.SetBytes(int64(len(js.Bytes()))) 114 | b.ResetTimer() 115 | b.ReportAllocs() 116 | for i := 0; i < b.N; i++ { 117 | js.Reset() 118 | UnmarshalAsJSON(&js, bts) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /msgp/json_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "reflect" 7 | "testing" 8 | "unicode/utf8" 9 | ) 10 | 11 | func TestCopyJSON(t *testing.T) { 12 | var buf bytes.Buffer 13 | enc := NewWriter(&buf) 14 | const mapLength = 6 15 | enc.WriteMapHeader(mapLength) 16 | 17 | enc.WriteString("thing_1") 18 | enc.WriteString("a string object") 19 | 20 | enc.WriteString("a_map") 21 | enc.WriteMapHeader(2) 22 | enc.WriteString("float_a") 23 | enc.WriteFloat32(1.0) 24 | enc.WriteString("int_b") 25 | enc.WriteInt64(-100) 26 | 27 | enc.WriteString("some bytes") 28 | enc.WriteBytes([]byte("here are some bytes")) 29 | enc.WriteString("a bool") 30 | enc.WriteBool(true) 31 | 32 | enc.WriteString("a map") 33 | enc.WriteMapStrStr(map[string]string{ 34 | "internal_one": "blah", 35 | "internal_two": "blahhh...", 36 | }) 37 | enc.WriteString("float64") 38 | const encodedFloat64 = 1672209023 39 | enc.WriteFloat64(encodedFloat64) 40 | enc.Flush() 41 | 42 | var js bytes.Buffer 43 | _, err := CopyToJSON(&js, &buf) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | mp := make(map[string]interface{}) 48 | err = json.Unmarshal(js.Bytes(), &mp) 49 | if err != nil { 50 | t.Log(js.String()) 51 | t.Fatalf("Error unmarshaling: %s", err) 52 | } 53 | 54 | if len(mp) != mapLength { 55 | t.Errorf("map length should be %d, not %d", mapLength, len(mp)) 56 | } 57 | 58 | so, ok := mp["thing_1"] 59 | if !ok || so != "a string object" { 60 | t.Errorf("expected %q; got %q", "a string object", so) 61 | } 62 | 63 | in, ok := mp["a map"] 64 | if !ok { 65 | t.Error("no key 'a map'") 66 | } 67 | if inm, ok := in.(map[string]interface{}); !ok { 68 | t.Error("inner map not type-assertable to map[string]interface{}") 69 | } else { 70 | inm1, ok := inm["internal_one"] 71 | if !ok || !reflect.DeepEqual(inm1, "blah") { 72 | t.Errorf("inner map field %q should be %q, not %q", "internal_one", "blah", inm1) 73 | } 74 | } 75 | if actual := mp["float64"]; float64(encodedFloat64) != actual.(float64) { 76 | t.Errorf("expected %G, got %G", float64(encodedFloat64), actual) 77 | } 78 | } 79 | 80 | // Encoder should generate valid utf-8 even if passed bad input 81 | func TestCopyJSONNegativeUTF8(t *testing.T) { 82 | // Single string with non-compliant utf-8 byte 83 | stringWithBadUTF8 := []byte{ 84 | 0xa1, 0xe0, 85 | } 86 | 87 | src := bytes.NewBuffer(stringWithBadUTF8) 88 | 89 | var js bytes.Buffer 90 | _, err := CopyToJSON(&js, src) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | // Even though we provided bad input, should have escaped the naughty character 96 | if !utf8.Valid(js.Bytes()) { 97 | t.Errorf("Expected JSON to be valid utf-8 even when provided bad input") 98 | } 99 | 100 | // Expect a bad character string 101 | expected := `"\ufffd"` 102 | if js.String() != expected { 103 | t.Errorf("Expected: '%s', got: '%s'", expected, js.String()) 104 | } 105 | } 106 | 107 | func BenchmarkCopyToJSON(b *testing.B) { 108 | var buf bytes.Buffer 109 | enc := NewWriter(&buf) 110 | enc.WriteMapHeader(4) 111 | 112 | enc.WriteString("thing_1") 113 | enc.WriteString("a string object") 114 | 115 | enc.WriteString("a_first_map") 116 | enc.WriteMapHeader(2) 117 | enc.WriteString("float_a") 118 | enc.WriteFloat32(1.0) 119 | enc.WriteString("int_b") 120 | enc.WriteInt64(-100) 121 | 122 | enc.WriteString("an array") 123 | enc.WriteArrayHeader(2) 124 | enc.WriteBool(true) 125 | enc.WriteUint(2089) 126 | 127 | enc.WriteString("a_second_map") 128 | enc.WriteMapStrStr(map[string]string{ 129 | "internal_one": "blah", 130 | "internal_two": "blahhh...", 131 | }) 132 | enc.Flush() 133 | 134 | var js bytes.Buffer 135 | bts := buf.Bytes() 136 | _, err := CopyToJSON(&js, &buf) 137 | if err != nil { 138 | b.Fatal(err) 139 | } 140 | b.SetBytes(int64(len(js.Bytes()))) 141 | b.ResetTimer() 142 | b.ReportAllocs() 143 | for i := 0; i < b.N; i++ { 144 | js.Reset() 145 | CopyToJSON(&js, bytes.NewReader(bts)) 146 | } 147 | } 148 | 149 | func BenchmarkStdlibJSON(b *testing.B) { 150 | obj := map[string]interface{}{ 151 | "thing_1": "a string object", 152 | "a_first_map": map[string]interface{}{ 153 | "float_a": float32(1.0), 154 | "float_b": -100, 155 | }, 156 | "an array": []interface{}{ 157 | "part_A", 158 | "part_B", 159 | }, 160 | "a_second_map": map[string]interface{}{ 161 | "internal_one": "blah", 162 | "internal_two": "blahhh...", 163 | }, 164 | } 165 | var js bytes.Buffer 166 | err := json.NewEncoder(&js).Encode(&obj) 167 | if err != nil { 168 | b.Fatal(err) 169 | } 170 | b.SetBytes(int64(len(js.Bytes()))) 171 | b.ResetTimer() 172 | b.ReportAllocs() 173 | for i := 0; i < b.N; i++ { 174 | js.Reset() 175 | json.NewEncoder(&js).Encode(&obj) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /msgp/number.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | ) 7 | 8 | // The portable parts of the Number implementation 9 | 10 | // Number can be 11 | // an int64, uint64, float32, 12 | // or float64 internally. 13 | // It can decode itself 14 | // from any of the native 15 | // messagepack number types. 16 | // The zero-value of Number 17 | // is Int(0). Using the equality 18 | // operator with Number compares 19 | // both the type and the value 20 | // of the number. 21 | type Number struct { 22 | // internally, this 23 | // is just a tagged union. 24 | // the raw bits of the number 25 | // are stored the same way regardless. 26 | bits uint64 27 | typ Type 28 | } 29 | 30 | // AsInt sets the number to an int64. 31 | func (n *Number) AsInt(i int64) { 32 | // we always store int(0) 33 | // as {0, InvalidType} in 34 | // order to preserve 35 | // the behavior of the == operator 36 | if i == 0 { 37 | n.typ = InvalidType 38 | n.bits = 0 39 | return 40 | } 41 | 42 | n.typ = IntType 43 | n.bits = uint64(i) 44 | } 45 | 46 | // AsUint sets the number to a uint64. 47 | func (n *Number) AsUint(u uint64) { 48 | n.typ = UintType 49 | n.bits = u 50 | } 51 | 52 | // AsFloat32 sets the value of the number 53 | // to a float32. 54 | func (n *Number) AsFloat32(f float32) { 55 | n.typ = Float32Type 56 | n.bits = uint64(math.Float32bits(f)) 57 | } 58 | 59 | // AsFloat64 sets the value of the 60 | // number to a float64. 61 | func (n *Number) AsFloat64(f float64) { 62 | n.typ = Float64Type 63 | n.bits = math.Float64bits(f) 64 | } 65 | 66 | // Int casts the number as an int64, and 67 | // returns whether or not that was the 68 | // underlying type. 69 | func (n *Number) Int() (int64, bool) { 70 | return int64(n.bits), n.typ == IntType || n.typ == InvalidType 71 | } 72 | 73 | // Uint casts the number as a uint64, and returns 74 | // whether or not that was the underlying type. 75 | func (n *Number) Uint() (uint64, bool) { 76 | return n.bits, n.typ == UintType 77 | } 78 | 79 | // Float casts the number to a float64, and 80 | // returns whether or not that was the underlying 81 | // type (either a float64 or a float32). 82 | func (n *Number) Float() (float64, bool) { 83 | switch n.typ { 84 | case Float32Type: 85 | return float64(math.Float32frombits(uint32(n.bits))), true 86 | case Float64Type: 87 | return math.Float64frombits(n.bits), true 88 | default: 89 | return 0.0, false 90 | } 91 | } 92 | 93 | // Type will return one of: 94 | // Float64Type, Float32Type, UintType, or IntType. 95 | func (n *Number) Type() Type { 96 | if n.typ == InvalidType { 97 | return IntType 98 | } 99 | return n.typ 100 | } 101 | 102 | // DecodeMsg implements msgp.Decodable 103 | func (n *Number) DecodeMsg(r *Reader) error { 104 | typ, err := r.NextType() 105 | if err != nil { 106 | return err 107 | } 108 | switch typ { 109 | case Float32Type: 110 | f, err := r.ReadFloat32() 111 | if err != nil { 112 | return err 113 | } 114 | n.AsFloat32(f) 115 | return nil 116 | case Float64Type: 117 | f, err := r.ReadFloat64() 118 | if err != nil { 119 | return err 120 | } 121 | n.AsFloat64(f) 122 | return nil 123 | case IntType: 124 | i, err := r.ReadInt64() 125 | if err != nil { 126 | return err 127 | } 128 | n.AsInt(i) 129 | return nil 130 | case UintType: 131 | u, err := r.ReadUint64() 132 | if err != nil { 133 | return err 134 | } 135 | n.AsUint(u) 136 | return nil 137 | default: 138 | return TypeError{Encoded: typ, Method: IntType} 139 | } 140 | } 141 | 142 | // UnmarshalMsg implements msgp.Unmarshaler 143 | func (n *Number) UnmarshalMsg(b []byte) ([]byte, error) { 144 | typ := NextType(b) 145 | switch typ { 146 | case IntType: 147 | i, o, err := ReadInt64Bytes(b) 148 | if err != nil { 149 | return b, err 150 | } 151 | n.AsInt(i) 152 | return o, nil 153 | case UintType: 154 | u, o, err := ReadUint64Bytes(b) 155 | if err != nil { 156 | return b, err 157 | } 158 | n.AsUint(u) 159 | return o, nil 160 | case Float64Type: 161 | f, o, err := ReadFloat64Bytes(b) 162 | if err != nil { 163 | return b, err 164 | } 165 | n.AsFloat64(f) 166 | return o, nil 167 | case Float32Type: 168 | f, o, err := ReadFloat32Bytes(b) 169 | if err != nil { 170 | return b, err 171 | } 172 | n.AsFloat32(f) 173 | return o, nil 174 | default: 175 | return b, TypeError{Method: IntType, Encoded: typ} 176 | } 177 | } 178 | 179 | // MarshalMsg implements msgp.Marshaler 180 | func (n *Number) MarshalMsg(b []byte) ([]byte, error) { 181 | switch n.typ { 182 | case IntType: 183 | return AppendInt64(b, int64(n.bits)), nil 184 | case UintType: 185 | return AppendUint64(b, uint64(n.bits)), nil 186 | case Float64Type: 187 | return AppendFloat64(b, math.Float64frombits(n.bits)), nil 188 | case Float32Type: 189 | return AppendFloat32(b, math.Float32frombits(uint32(n.bits))), nil 190 | default: 191 | return AppendInt64(b, 0), nil 192 | } 193 | } 194 | 195 | // EncodeMsg implements msgp.Encodable 196 | func (n *Number) EncodeMsg(w *Writer) error { 197 | switch n.typ { 198 | case IntType: 199 | return w.WriteInt64(int64(n.bits)) 200 | case UintType: 201 | return w.WriteUint64(n.bits) 202 | case Float64Type: 203 | return w.WriteFloat64(math.Float64frombits(n.bits)) 204 | case Float32Type: 205 | return w.WriteFloat32(math.Float32frombits(uint32(n.bits))) 206 | default: 207 | return w.WriteInt64(0) 208 | } 209 | } 210 | 211 | // Msgsize implements msgp.Sizer 212 | func (n *Number) Msgsize() int { 213 | switch n.typ { 214 | case Float32Type: 215 | return Float32Size 216 | case Float64Type: 217 | return Float64Size 218 | case IntType: 219 | return Int64Size 220 | case UintType: 221 | return Uint64Size 222 | default: 223 | return 1 // fixint(0) 224 | } 225 | } 226 | 227 | // MarshalJSON implements json.Marshaler 228 | func (n *Number) MarshalJSON() ([]byte, error) { 229 | t := n.Type() 230 | if t == InvalidType { 231 | return []byte{'0'}, nil 232 | } 233 | out := make([]byte, 0, 32) 234 | switch t { 235 | case Float32Type, Float64Type: 236 | f, _ := n.Float() 237 | return strconv.AppendFloat(out, f, 'f', -1, 64), nil 238 | case IntType: 239 | i, _ := n.Int() 240 | return strconv.AppendInt(out, i, 10), nil 241 | case UintType: 242 | u, _ := n.Uint() 243 | return strconv.AppendUint(out, u, 10), nil 244 | default: 245 | panic("(*Number).typ is invalid") 246 | } 247 | } 248 | 249 | // String implements fmt.Stringer 250 | func (n *Number) String() string { 251 | switch n.typ { 252 | case InvalidType: 253 | return "0" 254 | case Float32Type, Float64Type: 255 | f, _ := n.Float() 256 | return strconv.FormatFloat(f, 'f', -1, 64) 257 | case IntType: 258 | i, _ := n.Int() 259 | return strconv.FormatInt(i, 10) 260 | case UintType: 261 | u, _ := n.Uint() 262 | return strconv.FormatUint(u, 10) 263 | default: 264 | panic("(*Number).typ is invalid") 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /msgp/number_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestNumber(t *testing.T) { 9 | n := Number{} 10 | 11 | if n.Type() != IntType { 12 | t.Errorf("expected zero-value type to be %s; got %s", IntType, n.Type()) 13 | } 14 | 15 | if n.String() != "0" { 16 | t.Errorf("expected Number{}.String() to be \"0\" but got %q", n.String()) 17 | } 18 | 19 | n.AsInt(248) 20 | i, ok := n.Int() 21 | if !ok || i != 248 || n.Type() != IntType || n.String() != "248" { 22 | t.Errorf("%d in; %d out!", 248, i) 23 | } 24 | 25 | n.AsFloat64(3.141) 26 | f, ok := n.Float() 27 | if !ok || f != 3.141 || n.Type() != Float64Type || n.String() != "3.141" { 28 | t.Errorf("%f in; %f out!", 3.141, f) 29 | } 30 | 31 | n.AsUint(40000) 32 | u, ok := n.Uint() 33 | if !ok || u != 40000 || n.Type() != UintType || n.String() != "40000" { 34 | t.Errorf("%d in; %d out!", 40000, u) 35 | } 36 | 37 | nums := []interface{}{ 38 | float64(3.14159), 39 | int64(-29081), 40 | uint64(90821983), 41 | float32(3.141), 42 | } 43 | 44 | var dat []byte 45 | var buf bytes.Buffer 46 | wr := NewWriter(&buf) 47 | for _, n := range nums { 48 | dat, _ = AppendIntf(dat, n) 49 | wr.WriteIntf(n) 50 | } 51 | wr.Flush() 52 | 53 | mout := make([]Number, len(nums)) 54 | dout := make([]Number, len(nums)) 55 | 56 | rd := NewReader(&buf) 57 | unm := dat 58 | for i := range nums { 59 | var err error 60 | unm, err = mout[i].UnmarshalMsg(unm) 61 | if err != nil { 62 | t.Fatal("unmarshal error:", err) 63 | } 64 | err = dout[i].DecodeMsg(rd) 65 | if err != nil { 66 | t.Fatal("decode error:", err) 67 | } 68 | if mout[i] != dout[i] { 69 | t.Errorf("for %#v, got %#v from unmarshal and %#v from decode", nums[i], mout[i], dout[i]) 70 | } 71 | } 72 | 73 | buf.Reset() 74 | var odat []byte 75 | for i := range nums { 76 | var err error 77 | odat, err = mout[i].MarshalMsg(odat) 78 | if err != nil { 79 | t.Fatal("marshal error:", err) 80 | } 81 | err = dout[i].EncodeMsg(wr) 82 | if err != nil { 83 | t.Fatal("encode error", err) 84 | } 85 | } 86 | wr.Flush() 87 | 88 | if !bytes.Equal(dat, odat) { 89 | t.Errorf("marshal: expected output %#v; got %#v", dat, odat) 90 | } 91 | 92 | if !bytes.Equal(dat, buf.Bytes()) { 93 | t.Errorf("encode: expected output %#v; got %#v", dat, buf.Bytes()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /msgp/purego.go: -------------------------------------------------------------------------------- 1 | //go:build (purego && !unsafe) || appengine 2 | // +build purego,!unsafe appengine 3 | 4 | package msgp 5 | 6 | // let's just assume appengine 7 | // uses 64-bit hardware... 8 | const smallint = false 9 | 10 | func UnsafeString(b []byte) string { 11 | return string(b) 12 | } 13 | 14 | func UnsafeBytes(s string) []byte { 15 | return []byte(s) 16 | } 17 | -------------------------------------------------------------------------------- /msgp/raw_test.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // all standard interfaces 10 | type allifaces interface { 11 | Encodable 12 | Decodable 13 | Marshaler 14 | Unmarshaler 15 | Sizer 16 | } 17 | 18 | func TestRaw(t *testing.T) { 19 | bts := make([]byte, 0, 512) 20 | bts = AppendMapHeader(bts, 3) 21 | bts = AppendString(bts, "key_one") 22 | bts = AppendFloat64(bts, -1.0) 23 | bts = AppendString(bts, "key_two") 24 | bts = AppendString(bts, "value_two") 25 | bts = AppendString(bts, "key_three") 26 | bts = AppendTime(bts, time.Now()) 27 | 28 | var r Raw 29 | 30 | // verify that Raw satisfies 31 | // the interfaces we want it to 32 | var _ allifaces = &r 33 | 34 | // READ TESTS 35 | 36 | extra, err := r.UnmarshalMsg(bts) 37 | if err != nil { 38 | t.Fatal("error from UnmarshalMsg:", err) 39 | } 40 | if len(extra) != 0 { 41 | t.Errorf("expected 0 bytes left; found %d", len(extra)) 42 | } 43 | if !bytes.Equal([]byte(r), bts) { 44 | t.Fatal("value of raw and input slice are not equal after UnmarshalMsg") 45 | } 46 | 47 | r = r[:0] 48 | 49 | var buf bytes.Buffer 50 | buf.Write(bts) 51 | 52 | rd := NewReader(&buf) 53 | 54 | err = r.DecodeMsg(rd) 55 | if err != nil { 56 | t.Fatal("error from DecodeMsg:", err) 57 | } 58 | 59 | if !bytes.Equal([]byte(r), bts) { 60 | t.Fatal("value of raw and input slice are not equal after DecodeMsg") 61 | } 62 | 63 | // WRITE TESTS 64 | 65 | buf.Reset() 66 | wr := NewWriter(&buf) 67 | err = r.EncodeMsg(wr) 68 | if err != nil { 69 | t.Fatal("error from EncodeMsg:", err) 70 | } 71 | 72 | wr.Flush() 73 | if !bytes.Equal(buf.Bytes(), bts) { 74 | t.Fatal("value of buf.Bytes() and input slice are not equal after EncodeMsg") 75 | } 76 | 77 | var outsl []byte 78 | outsl, err = r.MarshalMsg(outsl) 79 | if err != nil { 80 | t.Fatal("error from MarshalMsg:", err) 81 | } 82 | if !bytes.Equal(outsl, bts) { 83 | t.Fatal("value of output and input of MarshalMsg are not equal.") 84 | } 85 | } 86 | 87 | func TestNullRaw(t *testing.T) { 88 | // Marshal/Unmarshal 89 | var x, y Raw 90 | if bts, err := x.MarshalMsg(nil); err != nil { 91 | t.Fatal(err) 92 | } else if _, err = y.UnmarshalMsg(bts); err != nil { 93 | t.Fatal(err) 94 | } 95 | if !bytes.Equal(x, y) { 96 | t.Fatal("compare") 97 | } 98 | 99 | // Encode/Decode 100 | var buf bytes.Buffer 101 | wr := NewWriter(&buf) 102 | if err := x.EncodeMsg(wr); err != nil { 103 | t.Fatal(err) 104 | } 105 | wr.Flush() 106 | rd := NewReader(&buf) 107 | if err := y.DecodeMsg(rd); err != nil { 108 | t.Fatal(err) 109 | } 110 | if !bytes.Equal(x, y) { 111 | t.Fatal("compare") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /msgp/setof/setof.go: -------------------------------------------------------------------------------- 1 | // Package setof allows serializing sets map[T]struct{} as arrays. 2 | // 3 | // Nil maps are preserved as a nil value on stream. 4 | // 5 | // A deterministic, sorted version is available, with slightly lower performance. 6 | 7 | package setof 8 | -------------------------------------------------------------------------------- /msgp/size.go: -------------------------------------------------------------------------------- 1 | package msgp 2 | 3 | // The sizes provided 4 | // are the worst-case 5 | // encoded sizes for 6 | // each type. For variable- 7 | // length types ([]byte, string), 8 | // the total encoded size is 9 | // the prefix size plus the 10 | // length of the object. 11 | const ( 12 | Int64Size = 9 13 | IntSize = Int64Size 14 | UintSize = Int64Size 15 | Int8Size = 2 16 | Int16Size = 3 17 | Int32Size = 5 18 | Uint8Size = 2 19 | ByteSize = Uint8Size 20 | Uint16Size = 3 21 | Uint32Size = 5 22 | Uint64Size = Int64Size 23 | Float64Size = 9 24 | Float32Size = 5 25 | Complex64Size = 10 26 | Complex128Size = 18 27 | 28 | DurationSize = Int64Size 29 | TimeSize = 15 30 | BoolSize = 1 31 | NilSize = 1 32 | JSONNumberSize = Int64Size // Same as Float64Size 33 | 34 | MapHeaderSize = 5 35 | ArrayHeaderSize = 5 36 | 37 | BytesPrefixSize = 5 38 | StringPrefixSize = 5 39 | ExtensionPrefixSize = 6 40 | ) 41 | -------------------------------------------------------------------------------- /msgp/unsafe.go: -------------------------------------------------------------------------------- 1 | //go:build (!purego && !appengine) || (!appengine && purego && unsafe) 2 | // +build !purego,!appengine !appengine,purego,unsafe 3 | 4 | package msgp 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // NOTE: 11 | // all of the definition in this file 12 | // should be repeated in appengine.go, 13 | // but without using unsafe 14 | 15 | const ( 16 | // spec says int and uint are always 17 | // the same size, but that int/uint 18 | // size may not be machine word size 19 | smallint = unsafe.Sizeof(int(0)) == 4 20 | ) 21 | 22 | // UnsafeString returns the byte slice as a volatile string 23 | // THIS SHOULD ONLY BE USED BY THE CODE GENERATOR. 24 | // THIS IS EVIL CODE. 25 | // YOU HAVE BEEN WARNED. 26 | func UnsafeString(b []byte) string { 27 | return *(*string)(unsafe.Pointer(&b)) 28 | } 29 | 30 | // UnsafeBytes returns the string as a byte slice 31 | // 32 | // Deprecated: 33 | // Since this code is no longer used by the code generator, 34 | // UnsafeBytes(s) is precisely equivalent to []byte(s) 35 | func UnsafeBytes(s string) []byte { 36 | return []byte(s) 37 | } 38 | -------------------------------------------------------------------------------- /parse/directives.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "strings" 8 | 9 | "github.com/tinylib/msgp/gen" 10 | ) 11 | 12 | const linePrefix = "//msgp:" 13 | 14 | // func(args, fileset) 15 | type directive func([]string, *FileSet) error 16 | 17 | // func(passName, args, printer) 18 | type passDirective func(gen.Method, []string, *gen.Printer) error 19 | 20 | // map of all recognized directives 21 | // 22 | // to add a directive, define a func([]string, *FileSet) error 23 | // and then add it to this list. 24 | var directives = map[string]directive{ 25 | "shim": applyShim, 26 | "replace": replace, 27 | "ignore": ignore, 28 | "tuple": astuple, 29 | "compactfloats": compactfloats, 30 | "clearomitted": clearomitted, 31 | "newtime": newtime, 32 | "timezone": newtimezone, 33 | } 34 | 35 | // map of all recognized directives which will be applied 36 | // before process() is called 37 | // 38 | // to add an early directive, define a func([]string, *FileSet) error 39 | // and then add it to this list. 40 | var earlyDirectives = map[string]directive{ 41 | "tag": tag, 42 | "pointer": pointer, 43 | } 44 | 45 | var passDirectives = map[string]passDirective{ 46 | "ignore": passignore, 47 | } 48 | 49 | func passignore(m gen.Method, text []string, p *gen.Printer) error { 50 | pushstate(m.String()) 51 | for _, a := range text { 52 | p.ApplyDirective(m, gen.IgnoreTypename(a)) 53 | infof("ignoring %s\n", a) 54 | } 55 | popstate() 56 | return nil 57 | } 58 | 59 | // find all comment lines that begin with //msgp: 60 | func yieldComments(c []*ast.CommentGroup) []string { 61 | var out []string 62 | for _, cg := range c { 63 | for _, line := range cg.List { 64 | if strings.HasPrefix(line.Text, linePrefix) { 65 | out = append(out, strings.TrimPrefix(line.Text, linePrefix)) 66 | } 67 | } 68 | } 69 | return out 70 | } 71 | 72 | //msgp:shim {Type} as:{NewType} using:{toFunc/fromFunc} mode:{Mode} 73 | func applyShim(text []string, f *FileSet) error { 74 | if len(text) < 4 || len(text) > 5 { 75 | return fmt.Errorf("shim directive should have 3 or 4 arguments; found %d", len(text)-1) 76 | } 77 | 78 | name := text[1] 79 | be := gen.Ident(strings.TrimPrefix(strings.TrimSpace(text[2]), "as:")) // parse as::{base} 80 | if name[0] == '*' { 81 | name = name[1:] 82 | be.Needsref(true) 83 | } 84 | be.Alias(name) 85 | 86 | usestr := strings.TrimPrefix(strings.TrimSpace(text[3]), "using:") // parse using::{method/method} 87 | 88 | methods := strings.Split(usestr, "/") 89 | if len(methods) != 2 { 90 | return fmt.Errorf("expected 2 using::{} methods; found %d (%q)", len(methods), text[3]) 91 | } 92 | 93 | be.ShimToBase = methods[0] 94 | be.ShimFromBase = methods[1] 95 | 96 | if len(text) == 5 { 97 | modestr := strings.TrimPrefix(strings.TrimSpace(text[4]), "mode:") // parse mode::{mode} 98 | switch modestr { 99 | case "cast": 100 | be.ShimMode = gen.Cast 101 | case "convert": 102 | be.ShimMode = gen.Convert 103 | default: 104 | return fmt.Errorf("invalid shim mode; found %s, expected 'cast' or 'convert", modestr) 105 | } 106 | } 107 | 108 | infof("%s -> %s\n", name, be.Value.String()) 109 | f.findShim(name, be, true) 110 | 111 | return nil 112 | } 113 | 114 | //msgp:replace {Type} with:{NewType} 115 | func replace(text []string, f *FileSet) error { 116 | if len(text) != 3 { 117 | return fmt.Errorf("replace directive should have only 2 arguments; found %d", len(text)-1) 118 | } 119 | 120 | name := text[1] 121 | replacement := strings.TrimPrefix(strings.TrimSpace(text[2]), "with:") 122 | 123 | expr, err := parser.ParseExpr(replacement) 124 | if err != nil { 125 | return err 126 | } 127 | e := f.parseExpr(expr) 128 | e.AlwaysPtr(&f.pointerRcv) 129 | 130 | if be, ok := e.(*gen.BaseElem); ok { 131 | be.Convert = true 132 | be.Alias(name) 133 | if be.Value == gen.IDENT { 134 | be.ShimToBase = "(*" + replacement + ")" 135 | be.Needsref(true) 136 | } 137 | } 138 | 139 | infof("%s -> %s\n", name, replacement) 140 | f.findShim(name, e, false) 141 | 142 | return nil 143 | } 144 | 145 | //msgp:ignore {TypeA} {TypeB}... 146 | func ignore(text []string, f *FileSet) error { 147 | if len(text) < 2 { 148 | return nil 149 | } 150 | for _, item := range text[1:] { 151 | name := strings.TrimSpace(item) 152 | if _, ok := f.Identities[name]; ok { 153 | delete(f.Identities, name) 154 | infof("ignoring %s\n", name) 155 | } 156 | } 157 | return nil 158 | } 159 | 160 | //msgp:tuple {TypeA} {TypeB}... 161 | func astuple(text []string, f *FileSet) error { 162 | if len(text) < 2 { 163 | return nil 164 | } 165 | for _, item := range text[1:] { 166 | name := strings.TrimSpace(item) 167 | if el, ok := f.Identities[name]; ok { 168 | if st, ok := el.(*gen.Struct); ok { 169 | st.AsTuple = true 170 | infof(name) 171 | } else { 172 | warnf("%s: only structs can be tuples\n", name) 173 | } 174 | } 175 | } 176 | return nil 177 | } 178 | 179 | //msgp:tag {tagname} 180 | func tag(text []string, f *FileSet) error { 181 | if len(text) != 2 { 182 | return nil 183 | } 184 | f.tagName = strings.TrimSpace(text[1]) 185 | return nil 186 | } 187 | 188 | //msgp:pointer 189 | func pointer(text []string, f *FileSet) error { 190 | f.pointerRcv = true 191 | return nil 192 | } 193 | 194 | //msgp:compactfloats 195 | func compactfloats(text []string, f *FileSet) error { 196 | f.CompactFloats = true 197 | return nil 198 | } 199 | 200 | //msgp:clearomitted 201 | func clearomitted(text []string, f *FileSet) error { 202 | f.ClearOmitted = true 203 | return nil 204 | } 205 | 206 | //msgp:newtime 207 | func newtime(text []string, f *FileSet) error { 208 | f.NewTime = true 209 | return nil 210 | } 211 | 212 | //msgp:timezone 213 | func newtimezone(text []string, f *FileSet) error { 214 | if len(text) != 2 { 215 | return fmt.Errorf("timezone directive should have only 1 argument; found %d", len(text)-1) 216 | } 217 | switch strings.ToLower(strings.TrimSpace(text[1])) { 218 | case "local": 219 | f.AsUTC = false 220 | case "utc": 221 | f.AsUTC = true 222 | default: 223 | return fmt.Errorf("timezone directive should be either 'local' or 'utc'; found %q", text[1]) 224 | } 225 | return nil 226 | } 227 | -------------------------------------------------------------------------------- /parse/inline.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/tinylib/msgp/gen" 7 | ) 8 | 9 | // This file defines when and how we 10 | // propagate type information from 11 | // one type declaration to another. 12 | // After the processing pass, every 13 | // non-primitive type is marshalled/unmarshalled/etc. 14 | // through a function call. Here, we propagate 15 | // the type information into the caller's type 16 | // tree *if* the child type is simple enough. 17 | // 18 | // For example, types like 19 | // 20 | // type A [4]int 21 | // 22 | // will get pushed into parent methods, 23 | // whereas types like 24 | // 25 | // type B [3]map[string]struct{A, B [4]string} 26 | // 27 | // will not. 28 | 29 | // this is an approximate measure 30 | // of the number of children in a node 31 | const maxComplex = 5 32 | 33 | // begin recursive search for identities with the 34 | // given name and replace them with e 35 | func (f *FileSet) findShim(id string, e gen.Elem, addID bool) { 36 | for name, el := range f.Identities { 37 | pushstate(name) 38 | switch el := el.(type) { 39 | case *gen.Struct: 40 | for i := range el.Fields { 41 | f.nextShim(&el.Fields[i].FieldElem, id, e) 42 | } 43 | case *gen.Array: 44 | f.nextShim(&el.Els, id, e) 45 | case *gen.Slice: 46 | f.nextShim(&el.Els, id, e) 47 | case *gen.Map: 48 | f.nextShim(&el.Value, id, e) 49 | case *gen.Ptr: 50 | f.nextShim(&el.Value, id, e) 51 | } 52 | popstate() 53 | } 54 | if addID { 55 | f.Identities[id] = e 56 | } 57 | } 58 | 59 | func (f *FileSet) nextShim(ref *gen.Elem, id string, e gen.Elem) { 60 | if (*ref).TypeName() == id { 61 | vn := (*ref).Varname() 62 | *ref = e.Copy() 63 | (*ref).SetVarname(vn) 64 | } else { 65 | switch el := (*ref).(type) { 66 | case *gen.Struct: 67 | for i := range el.Fields { 68 | f.nextShim(&el.Fields[i].FieldElem, id, e) 69 | } 70 | case *gen.Array: 71 | f.nextShim(&el.Els, id, e) 72 | case *gen.Slice: 73 | f.nextShim(&el.Els, id, e) 74 | case *gen.Map: 75 | f.nextShim(&el.Value, id, e) 76 | case *gen.Ptr: 77 | f.nextShim(&el.Value, id, e) 78 | } 79 | } 80 | } 81 | 82 | // propInline identifies and inlines candidates 83 | func (f *FileSet) propInline() { 84 | type gelem struct { 85 | name string 86 | el gen.Elem 87 | } 88 | 89 | all := make([]gelem, 0, len(f.Identities)) 90 | 91 | for name, el := range f.Identities { 92 | all = append(all, gelem{name: name, el: el}) 93 | } 94 | 95 | // make sure we process inlining determinstically: 96 | // start with the least-complex elems; 97 | // use identifier names as a tie-breaker 98 | sort.Slice(all, func(i, j int) bool { 99 | ig, jg := &all[i], &all[j] 100 | ic, jc := ig.el.Complexity(), jg.el.Complexity() 101 | return ic < jc || (ic == jc && ig.name < jg.name) 102 | }) 103 | 104 | for i := range all { 105 | name := all[i].name 106 | pushstate(name) 107 | switch el := all[i].el.(type) { 108 | case *gen.Struct: 109 | for i := range el.Fields { 110 | f.nextInline(&el.Fields[i].FieldElem, name) 111 | } 112 | case *gen.Array: 113 | f.nextInline(&el.Els, name) 114 | case *gen.Slice: 115 | f.nextInline(&el.Els, name) 116 | case *gen.Map: 117 | f.nextInline(&el.Value, name) 118 | case *gen.Ptr: 119 | f.nextInline(&el.Value, name) 120 | } 121 | popstate() 122 | } 123 | } 124 | 125 | const fatalloop = `detected infinite recursion in inlining loop! 126 | Please file a bug at github.com/tinylib/msgp/issues! 127 | Thanks! 128 | ` 129 | 130 | func (f *FileSet) nextInline(ref *gen.Elem, root string) { 131 | switch el := (*ref).(type) { 132 | case *gen.BaseElem: 133 | // ensure that we're not inlining 134 | // a type into itself 135 | typ := el.TypeName() 136 | if el.Value == gen.IDENT && typ != root { 137 | if node, ok := f.Identities[typ]; ok && node.Complexity() < maxComplex { 138 | infof("inlining %s\n", typ) 139 | 140 | // This should never happen; it will cause 141 | // infinite recursion. 142 | if node == *ref { 143 | panic(fatalloop) 144 | } 145 | 146 | *ref = node.Copy() 147 | f.nextInline(ref, node.TypeName()) 148 | } else if !ok && !el.Resolved() { 149 | // this is the point at which we're sure that 150 | // we've got a type that isn't a primitive, 151 | // a library builtin, or a processed type 152 | warnf("unresolved identifier: %s\n", typ) 153 | } 154 | } 155 | case *gen.Struct: 156 | for i := range el.Fields { 157 | f.nextInline(&el.Fields[i].FieldElem, root) 158 | } 159 | case *gen.Array: 160 | f.nextInline(&el.Els, root) 161 | case *gen.Slice: 162 | f.nextInline(&el.Els, root) 163 | case *gen.Map: 164 | f.nextInline(&el.Value, root) 165 | case *gen.Ptr: 166 | f.nextInline(&el.Value, root) 167 | default: 168 | panic("bad elem type") 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /printer/print.go: -------------------------------------------------------------------------------- 1 | package printer 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/tinylib/msgp/gen" 11 | "github.com/tinylib/msgp/parse" 12 | "golang.org/x/tools/imports" 13 | ) 14 | 15 | var Logf func(s string, v ...interface{}) 16 | 17 | // PrintFile prints the methods for the provided list 18 | // of elements to the given file name and canonical 19 | // package path. 20 | func PrintFile(file string, f *parse.FileSet, mode gen.Method) error { 21 | out, tests, err := generate(f, mode) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | // we'll run goimports on the main file 27 | // in another goroutine, and run it here 28 | // for the test file. empirically, this 29 | // takes about the same amount of time as 30 | // doing them in serial when GOMAXPROCS=1, 31 | // and faster otherwise. 32 | res := goformat(file, out.Bytes()) 33 | if tests != nil { 34 | testfile := strings.TrimSuffix(file, ".go") + "_test.go" 35 | err = format(testfile, tests.Bytes()) 36 | if err != nil { 37 | return err 38 | } 39 | if Logf != nil { 40 | Logf("Wrote and formatted \"%s\"\n", testfile) 41 | } 42 | } 43 | err = <-res 44 | if err != nil { 45 | os.WriteFile(file+".broken", out.Bytes(), os.ModePerm) 46 | if Logf != nil { 47 | Logf("Error: %s. Wrote broken output to %s\n", err, file+".broken") 48 | } 49 | 50 | return err 51 | } 52 | return nil 53 | } 54 | 55 | func format(file string, data []byte) error { 56 | out, err := imports.Process(file, data, nil) 57 | if err != nil { 58 | return err 59 | } 60 | return os.WriteFile(file, out, 0o600) 61 | } 62 | 63 | func goformat(file string, data []byte) <-chan error { 64 | out := make(chan error, 1) 65 | go func(file string, data []byte, end chan error) { 66 | end <- format(file, data) 67 | if Logf != nil { 68 | Logf("Wrote and formatted \"%s\"\n", file) 69 | } 70 | }(file, data, out) 71 | return out 72 | } 73 | 74 | func dedupImports(imp []string) []string { 75 | m := make(map[string]struct{}) 76 | for i := range imp { 77 | m[imp[i]] = struct{}{} 78 | } 79 | r := []string{} 80 | for k := range m { 81 | r = append(r, k) 82 | } 83 | return r 84 | } 85 | 86 | func generate(f *parse.FileSet, mode gen.Method) (*bytes.Buffer, *bytes.Buffer, error) { 87 | outbuf := bytes.NewBuffer(make([]byte, 0, 4096)) 88 | writePkgHeader(outbuf, f.Package) 89 | 90 | myImports := []string{"github.com/tinylib/msgp/msgp"} 91 | for _, imp := range f.Imports { 92 | if imp.Name != nil { 93 | // have an alias, include it. 94 | myImports = append(myImports, imp.Name.Name+` `+imp.Path.Value) 95 | } else { 96 | myImports = append(myImports, imp.Path.Value) 97 | } 98 | } 99 | dedup := dedupImports(myImports) 100 | writeImportHeader(outbuf, dedup...) 101 | 102 | var testbuf *bytes.Buffer 103 | var testwr io.Writer 104 | if mode&gen.Test == gen.Test { 105 | testbuf = bytes.NewBuffer(make([]byte, 0, 4096)) 106 | writePkgHeader(testbuf, f.Package) 107 | if mode&(gen.Encode|gen.Decode) != 0 { 108 | writeImportHeader(testbuf, "bytes", "github.com/tinylib/msgp/msgp", "testing") 109 | } else { 110 | writeImportHeader(testbuf, "github.com/tinylib/msgp/msgp", "testing") 111 | } 112 | testwr = testbuf 113 | } 114 | return outbuf, testbuf, f.PrintTo(gen.NewPrinter(mode, outbuf, testwr)) 115 | } 116 | 117 | func writePkgHeader(b *bytes.Buffer, name string) { 118 | b.WriteString("package ") 119 | b.WriteString(name) 120 | b.WriteByte('\n') 121 | // write generated code marker 122 | // https://github.com/tinylib/msgp/issues/229 123 | // https://golang.org/s/generatedcode 124 | b.WriteString("// Code generated by github.com/tinylib/msgp DO NOT EDIT.\n\n") 125 | } 126 | 127 | func writeImportHeader(b *bytes.Buffer, imports ...string) { 128 | b.WriteString("import (\n") 129 | for _, im := range imports { 130 | if im[len(im)-1] == '"' { 131 | // support aliased imports 132 | fmt.Fprintf(b, "\t%s\n", im) 133 | } else { 134 | fmt.Fprintf(b, "\t%q\n", im) 135 | } 136 | } 137 | b.WriteString(")\n\n") 138 | } 139 | -------------------------------------------------------------------------------- /tinygotest/.gitignore: -------------------------------------------------------------------------------- 1 | *_gen.go 2 | *_gen_test.go 3 | *.out 4 | *.bin 5 | *.hex 6 | *.wasm 7 | *.wasi 8 | nativebin 9 | tmp* 10 | 11 | -------------------------------------------------------------------------------- /tinygotest/testdata/empty/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // baseline file just so we can compare the size 4 | 5 | func main() { 6 | println("successfully did nothing") 7 | } 8 | -------------------------------------------------------------------------------- /tinygotest/testdata/roundtrip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/tinylib/msgp/msgp" 7 | ) 8 | 9 | //go:generate msgp 10 | 11 | type SomeStruct struct { 12 | A string `msg:"a"` 13 | } 14 | 15 | type EmbeddedStruct struct { 16 | A string `msg:"a"` 17 | } 18 | 19 | // Example provides a decent variety of types and features and 20 | // lets us test for basic functionality in TinyGo. 21 | type Example struct { 22 | Interface interface{} `msg:"interface"` 23 | Any any `msg:"any"` 24 | Int64 int64 `msg:"int64"` 25 | Uint64 uint64 `msg:"uint64"` 26 | Int32 int32 `msg:"int32"` 27 | Uint32 uint32 `msg:"uint32"` 28 | Int16 int32 `msg:"int16"` 29 | Uint16 uint32 `msg:"uint16"` 30 | Int8 int32 `msg:"int8"` 31 | Byte byte `msg:"byte"` 32 | Float64 float64 `msg:"float64"` 33 | Float32 float32 `msg:"float32"` 34 | String string `msg:"string"` 35 | ByteSlice []byte `msg:"byte_slice"` 36 | StringSlice []string `msg:"string_slice"` 37 | IntArray [2]int `msg:"int_array"` 38 | SomeStruct SomeStruct `msg:"some_struct"` 39 | 40 | EmbeddedStruct 41 | 42 | Omitted string `msg:"-"` 43 | 44 | OmitEmptyString string `msg:"omit_empty_string,omitempty"` 45 | } 46 | 47 | // Setup populuates the struct with test data 48 | func (e *Example) Setup() { 49 | e.Interface = 10 50 | e.Any = "any" 51 | e.Int64 = 10 52 | e.Uint64 = 11 53 | e.Int32 = 12 54 | e.Uint32 = 13 55 | e.Int16 = 14 56 | e.Uint16 = 15 57 | e.Int8 = 16 58 | e.Byte = 17 59 | e.Float64 = 18.1 60 | e.Float32 = 19.2 61 | e.String = "astr" 62 | e.ByteSlice = []byte("bstr") 63 | e.StringSlice = []string{"a", "b"} 64 | e.IntArray = [...]int{20, 21} 65 | e.SomeStruct = SomeStruct{A: "x"} 66 | 67 | e.EmbeddedStruct = EmbeddedStruct{A: "y"} 68 | 69 | e.Omitted = "nope" 70 | 71 | e.OmitEmptyString = "here" 72 | } 73 | 74 | func (e *Example) Eq(e2 *Example) bool { 75 | if int64(e.Interface.(int)) != e2.Interface.(int64) { 76 | return false 77 | } 78 | if e.Any.(string) != e2.Any.(string) { 79 | return false 80 | } 81 | if e.Int64 != e2.Int64 { 82 | return false 83 | } 84 | if e.Uint64 != e2.Uint64 { 85 | return false 86 | } 87 | if e.Int32 != e2.Int32 { 88 | return false 89 | } 90 | if e.Uint32 != e2.Uint32 { 91 | return false 92 | } 93 | if e.Int16 != e2.Int16 { 94 | return false 95 | } 96 | if e.Uint16 != e2.Uint16 { 97 | return false 98 | } 99 | if e.Int8 != e2.Int8 { 100 | return false 101 | } 102 | if e.Byte != e2.Byte { 103 | return false 104 | } 105 | if e.Float64 != e2.Float64 { 106 | return false 107 | } 108 | if e.Float32 != e2.Float32 { 109 | return false 110 | } 111 | if e.String != e2.String { 112 | return false 113 | } 114 | if bytes.Compare(e.ByteSlice, e2.ByteSlice) != 0 { 115 | return false 116 | } 117 | if len(e.StringSlice) != len(e2.StringSlice) { 118 | return false 119 | } 120 | for i := 0; i < len(e.StringSlice); i++ { 121 | if e.StringSlice[i] != e2.StringSlice[i] { 122 | return false 123 | } 124 | } 125 | if len(e.IntArray) != len(e2.IntArray) { 126 | return false 127 | } 128 | for i := 0; i < len(e.IntArray); i++ { 129 | if e.IntArray[i] != e2.IntArray[i] { 130 | return false 131 | } 132 | } 133 | if e.SomeStruct.A != e2.SomeStruct.A { 134 | return false 135 | } 136 | 137 | if e.EmbeddedStruct.A != e2.EmbeddedStruct.A { 138 | return false 139 | } 140 | 141 | if e.Omitted != e2.Omitted { 142 | return false 143 | } 144 | 145 | if e.OmitEmptyString != e2.OmitEmptyString { 146 | return false 147 | } 148 | 149 | return true 150 | } 151 | 152 | var buf [256]byte 153 | 154 | func main() { 155 | var e Example 156 | e.Setup() 157 | 158 | b, err := e.MarshalMsg(buf[:0]) 159 | if err != nil { 160 | panic(err) 161 | } 162 | b1 := b 163 | 164 | var e2 Example 165 | _, err = e2.UnmarshalMsg(b) 166 | if err != nil { 167 | panic(err) 168 | } 169 | 170 | println("marshal/unmarshal done: ", &e2) 171 | 172 | e.Omitted = "" 173 | if !e.Eq(&e2) { 174 | panic("comparison after marshal/unmarhsal failed") 175 | } 176 | 177 | var wbuf bytes.Buffer 178 | mw := msgp.NewWriterSize(&wbuf, 64) 179 | 180 | e.Omitted = "other" 181 | err = e.EncodeMsg(mw) 182 | if err != nil { 183 | panic(err) 184 | } 185 | mw.Flush() 186 | 187 | e2 = Example{} 188 | mr := msgp.NewReaderSize(bytes.NewReader(wbuf.Bytes()), 64) 189 | err = e2.DecodeMsg(mr) 190 | if err != nil { 191 | panic(err) 192 | } 193 | 194 | println("writer/reader done: ", &e2) 195 | e.Omitted = "" 196 | if !e.Eq(&e2) { 197 | panic("comparison after writer/reader failed") 198 | } 199 | 200 | if bytes.Compare(wbuf.Bytes(), b1) != 0 { 201 | panic("writer and marshal produced different results") 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /tinygotest/testdata/simple_bytes_append/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/tinylib/msgp/msgp" 4 | 5 | type Example struct { 6 | I int 7 | S string 8 | } 9 | 10 | var buf [64]byte 11 | 12 | func main() { 13 | e := Example{ 14 | I: 1, 15 | S: "2", 16 | } 17 | 18 | b := buf[:0] 19 | b = msgp.AppendMapHeader(b, 2) 20 | b = msgp.AppendString(b, "i") 21 | b = msgp.AppendInt(b, e.I) 22 | b = msgp.AppendString(b, "s") 23 | b = msgp.AppendString(b, e.S) 24 | 25 | println("done, len(b):", len(b)) 26 | for i := 0; i < len(b); i++ { 27 | print(b[i], " ") 28 | } 29 | println() 30 | } 31 | -------------------------------------------------------------------------------- /tinygotest/testdata/simple_bytes_read/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/tinylib/msgp/msgp" 4 | 5 | type Example struct { 6 | I int 7 | S string 8 | } 9 | 10 | func main() { 11 | b := []byte{130, 161, 105, 1, 161, 115, 161, 50} 12 | 13 | e := Example{} 14 | 15 | sz, b, err := msgp.ReadMapHeaderBytes(b) 16 | if err != nil { 17 | panic(err) 18 | } 19 | if sz != 2 { 20 | panic("bad sz") 21 | } 22 | 23 | for i := 0; i < int(sz); i++ { 24 | var sb []byte 25 | var i32 int32 26 | var err error 27 | sb, b, err = msgp.ReadStringZC(b) 28 | switch string(sb) { 29 | case "i": 30 | i32, b, err = msgp.ReadInt32Bytes(b) 31 | if err != nil { 32 | panic(err) 33 | } 34 | e.I = int(i32) 35 | case "s": 36 | sb, b, err = msgp.ReadStringZC(b) 37 | if err != nil { 38 | panic(err) 39 | } 40 | e.S = string(sb) 41 | default: 42 | panic("unexpected field:" + string(sb)) 43 | } 44 | } 45 | 46 | if len(b) != 0 { 47 | panic("unexpected extra") 48 | } 49 | 50 | if e.I != 1 || e.S != "2" { 51 | panic("not equal") 52 | } 53 | 54 | println("done") 55 | } 56 | -------------------------------------------------------------------------------- /tinygotest/testdata/simple_marshal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate msgp 4 | 5 | type Example struct { 6 | I int `msg:"i"` 7 | S string `msg:"s"` 8 | } 9 | 10 | func main() { 11 | e := Example{ 12 | I: 1, 13 | S: "2", 14 | } 15 | b, err := e.MarshalMsg(nil) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | println("done, len(b):", len(b)) 21 | for i := 0; i < len(b); i++ { 22 | print(b[i], " ") 23 | } 24 | println() 25 | } 26 | -------------------------------------------------------------------------------- /tinygotest/testdata/simple_roundtrip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate msgp 4 | 5 | type Example struct { 6 | I int `msg:"i"` 7 | S string `msg:"s"` 8 | // NOTE: floats omitted intentionally, many MCUs don't have 9 | // native support for it so the binary size bloats just due to 10 | // the software float implementation 11 | } 12 | 13 | func main() { 14 | e := Example{ 15 | I: 1, 16 | S: "2", 17 | } 18 | b, err := e.MarshalMsg(nil) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | var e2 Example 24 | extra, err := e2.UnmarshalMsg(b) 25 | if err != nil { 26 | panic(err) 27 | } 28 | if len(extra) != 0 { 29 | panic("unexpected extra") 30 | } 31 | 32 | if e.I != e2.I || e.S != e2.S { 33 | panic("not equal") 34 | } 35 | 36 | println("done, len(b):", len(b)) 37 | } 38 | -------------------------------------------------------------------------------- /tinygotest/testdata/simple_unmarshal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate msgp 4 | 5 | type Example struct { 6 | I int `msg:"i"` 7 | S string `msg:"s"` 8 | } 9 | 10 | func main() { 11 | b := []byte{130, 161, 105, 1, 161, 115, 161, 50} 12 | 13 | var e2 Example 14 | extra, err := e2.UnmarshalMsg(b) 15 | if err != nil { 16 | panic(err) 17 | } 18 | if len(extra) != 0 { 19 | panic("unexpected extra") 20 | } 21 | 22 | if e2.I != 1 || e2.S != "2" { 23 | panic("not equal") 24 | } 25 | 26 | println("done, len(b):", len(b)) 27 | } 28 | -------------------------------------------------------------------------------- /tinygotest/tinygo_test.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 || darwin 2 | 3 | package tinygotest 4 | 5 | // NOTE: this is intended to be run with `go test` not `tinygo test`, 6 | // as it then in turn performs various `tinygo build` commands and 7 | // verifies the output. 8 | 9 | import ( 10 | "bytes" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "runtime" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | // empty gives us a baseline to compare sizes (and to prove that tinygo itself isn't broken) 20 | 21 | func TestEmptyRun(t *testing.T) { 22 | // build and run native executable 23 | tinygoBuild(t, "empty") 24 | run(t, "empty", "nativebin") 25 | } 26 | 27 | func TestEmptyBuild(t *testing.T) { 28 | // build for various environments 29 | tinygoBuild(t, "empty", buildOnlyTargets...) 30 | reportSizes(t, "empty") 31 | } 32 | 33 | // roundtrip provides reasonable coverage for various cases, but is 34 | // fairly bloated in terms of size compared to simpler examples 35 | 36 | func TestRoundtripRun(t *testing.T) { 37 | // build and run native executable 38 | goGenerate(t, "roundtrip") 39 | tinygoBuild(t, "roundtrip") 40 | run(t, "roundtrip", "nativebin") 41 | } 42 | 43 | func TestRoundtripBuild(t *testing.T) { 44 | // build for various environments 45 | goGenerate(t, "roundtrip") 46 | tinygoBuild(t, "roundtrip", buildOnlyTargets...) 47 | reportSizes(t, "roundtrip") 48 | } 49 | 50 | // simple_roundtrip is a minimal marshal+unmarshal example and should show 51 | // the baseline size when only using minimal functionality and marshal+unmarshal 52 | 53 | func TestSimpleRoundtripRun(t *testing.T) { 54 | // build and run native executable 55 | goGenerate(t, "simple_roundtrip") 56 | tinygoBuild(t, "simple_roundtrip") 57 | run(t, "simple_roundtrip", "nativebin") 58 | } 59 | 60 | func TestSimpleRoundtripBuild(t *testing.T) { 61 | // build for various environments 62 | goGenerate(t, "simple_roundtrip") 63 | tinygoBuild(t, "simple_roundtrip", buildOnlyTargets...) 64 | sizes := reportSizes(t, "simple_roundtrip") 65 | // this was ~13k at the time this test was written, 66 | // if it bloats up to over 20k, we assume something is wrong 67 | // (e.g. "fmt" or other large package being using) 68 | sz := sizes["arduino-nano33.bin"] 69 | if sz > 20000 { 70 | t.Errorf("arduino-nano33.bin is larger than expected: %d", sz) 71 | } 72 | } 73 | 74 | // simple_marshal is just the MarshalMsg part 75 | 76 | func TestSimpleMarshalRun(t *testing.T) { 77 | // build and run native executable 78 | goGenerate(t, "simple_marshal") 79 | tinygoBuild(t, "simple_marshal") 80 | run(t, "simple_marshal", "nativebin") 81 | } 82 | 83 | func TestSimpleMarshalBuild(t *testing.T) { 84 | // build for various environments 85 | goGenerate(t, "simple_marshal") 86 | tinygoBuild(t, "simple_marshal", buildOnlyTargets...) 87 | reportSizes(t, "simple_marshal") 88 | } 89 | 90 | // simple_unmarshal is just the UnmarshalMsg part 91 | 92 | func TestSimpleUnmarshalRun(t *testing.T) { 93 | // build and run native executable 94 | goGenerate(t, "simple_unmarshal") 95 | tinygoBuild(t, "simple_unmarshal") 96 | run(t, "simple_unmarshal", "nativebin") 97 | } 98 | 99 | func TestSimpleUnmarshalBuild(t *testing.T) { 100 | // build for various environments 101 | goGenerate(t, "simple_unmarshal") 102 | tinygoBuild(t, "simple_unmarshal", buildOnlyTargets...) 103 | reportSizes(t, "simple_unmarshal") 104 | } 105 | 106 | // simple_bytes_append is AppendX() methods without code generation 107 | 108 | func TestSimpleBytesAppendRun(t *testing.T) { 109 | // build and run native executable 110 | goGenerate(t, "simple_bytes_append") 111 | tinygoBuild(t, "simple_bytes_append") 112 | run(t, "simple_bytes_append", "nativebin") 113 | } 114 | 115 | func TestSimpleBytesAppendBuild(t *testing.T) { 116 | // build for various environments 117 | goGenerate(t, "simple_bytes_append") 118 | tinygoBuild(t, "simple_bytes_append", buildOnlyTargets...) 119 | reportSizes(t, "simple_bytes_append") 120 | } 121 | 122 | // simple_bytes_append is ReadX() methods without code generation 123 | 124 | func TestSimpleBytesReadRun(t *testing.T) { 125 | // build and run native executable 126 | goGenerate(t, "simple_bytes_read") 127 | tinygoBuild(t, "simple_bytes_read") 128 | run(t, "simple_bytes_read", "nativebin") 129 | } 130 | 131 | func TestSimpleBytesReadBuild(t *testing.T) { 132 | // build for various environments 133 | goGenerate(t, "simple_bytes_read") 134 | tinygoBuild(t, "simple_bytes_read", buildOnlyTargets...) 135 | reportSizes(t, "simple_bytes_read") 136 | } 137 | 138 | // do builds for these tinygo boards/environments to make sure 139 | // it at least compiles 140 | var buildOnlyTargets = []string{ 141 | "arduino-nano33", // ARM Cortex-M0, SAMD21 142 | "feather-m4", // ARM Cortex-M4, SAMD51 143 | "wasm", // WebAssembly 144 | 145 | // "arduino-nano", // AVR - roundtrip build currently fails with: could not store type code number inside interface type code 146 | // "esp32-coreboard-v2", // ESP - xtensa seems to require additional setup, currently errors with: No available targets are compatible with triple "xtensa" 147 | } 148 | 149 | func run(t *testing.T, dir, exe string, args ...string) { 150 | t.Helper() 151 | if runtime.GOOS == "windows" { 152 | exe += ".exe" 153 | } 154 | cmd := exec.Command("./" + exe) 155 | wd, err := os.Getwd() 156 | if err != nil { 157 | t.Fatal(err) 158 | } 159 | cmd.Dir = filepath.Join(wd, "testdata", dir) 160 | cmd.Args = args 161 | b, err := cmd.CombinedOutput() 162 | if len(bytes.TrimSpace(b)) > 0 { 163 | t.Logf("%s: %s %v; output: %s", dir, exe, args, b) 164 | } 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | } 169 | 170 | func goGenerate(t *testing.T, dir string) { 171 | t.Helper() 172 | t.Logf("%s: go generate", dir) 173 | cmd := exec.Command("go", "generate") 174 | wd, err := os.Getwd() 175 | if err != nil { 176 | t.Fatal(err) 177 | } 178 | cmd.Dir = filepath.Join(wd, "testdata", dir) 179 | b, err := cmd.CombinedOutput() 180 | if len(bytes.TrimSpace(b)) > 0 { 181 | t.Logf("%s: go generate output: %s", dir, b) 182 | } 183 | if err != nil { 184 | t.Fatal(err) 185 | } 186 | } 187 | 188 | func tinygoBuild(t *testing.T, dir string, targets ...string) { 189 | t.Helper() 190 | 191 | wd, err := os.Getwd() 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | dirabs := filepath.Join(wd, "testdata", dir) 196 | 197 | // no targets specified implies just build the native executable 198 | if len(targets) == 0 { 199 | targets = []string{""} 200 | } 201 | 202 | for _, tgt := range targets { 203 | 204 | ext := ".bin" 205 | if tgt == "wasm" { 206 | ext = ".wasm" 207 | } 208 | dst := tgt + ext 209 | if tgt == "" { 210 | dst = "nativebin" 211 | if runtime.GOOS == "windows" { 212 | dst += ".exe" 213 | } 214 | } 215 | 216 | var args []string 217 | if tgt == "" { // empty target means the native platform 218 | args = []string{"build", "-o=" + dst, "."} 219 | } else { 220 | args = []string{"build", "-target=" + tgt, "-o=" + dst, "."} 221 | } 222 | 223 | t.Logf("%s: tinygo %v", dir, args) 224 | cmd := exec.Command("tinygo", args...) 225 | cmd.Dir = dirabs 226 | b, err := cmd.CombinedOutput() 227 | if len(bytes.TrimSpace(b)) > 0 { 228 | t.Logf("%s: tinygo build %v; output: %s", dir, args, b) 229 | } 230 | if err != nil { 231 | // See https://github.com/tinygo-org/tinygo/issues/3977 232 | if strings.Contains(string(b), "could not find wasm-opt") { 233 | t.Skipf("skipping wasm test because wasm-opt is not installed") 234 | } 235 | t.Fatal(err) 236 | } 237 | } 238 | } 239 | 240 | var spacePad = strings.Repeat(" ", 64) 241 | 242 | func reportSizes(t *testing.T, dir string) (ret map[string]int64) { 243 | ret = make(map[string]int64) 244 | var fnl []string 245 | for _, gl := range []string{"*.bin", "*.wasm"} { 246 | fnl2, err := filepath.Glob(filepath.Join("testdata", dir, gl)) 247 | if err != nil { 248 | t.Fatal(err) 249 | } 250 | fnl = append(fnl, fnl2...) 251 | } 252 | for _, fn := range fnl { 253 | st, err := os.Stat(fn) 254 | if err != nil { 255 | t.Fatal(err) 256 | } 257 | _, jfn := filepath.Split(fn) 258 | t.Logf("size report - %s %6d bytes", (jfn + spacePad)[:24], st.Size()) 259 | ret[jfn] = st.Size() 260 | } 261 | return ret 262 | } 263 | --------------------------------------------------------------------------------