From 7127298e5fefee4e9d87b7fedeff16786627c54d Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 08:16:00 +1000 Subject: [PATCH 01/10] Extract init function, so that obj file can be used elsewhere --- types/buildid_darwin.go | 15 +++++++++++++++ types/func_darwin.go | 16 +++++----------- types/func_linux.go | 15 ++++----------- types/obj_darwin.go | 17 +++++++++++++++++ types/obj_linux.go | 12 ++++++++++++ 5 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 types/buildid_darwin.go create mode 100644 types/obj_darwin.go create mode 100644 types/obj_linux.go diff --git a/types/buildid_darwin.go b/types/buildid_darwin.go new file mode 100644 index 0000000..1f25472 --- /dev/null +++ b/types/buildid_darwin.go @@ -0,0 +1,15 @@ +package types + +import ( + "debug/macho" + "os" +) + +func init() { + f, err := macho.Open(os.Args[0]) + if err != nil { + panic("cannot read Mach-O binary: " + err.Error()) + } + defer f.Close() + +} diff --git a/types/func_darwin.go b/types/func_darwin.go index 9016e5f..8dafa61 100644 --- a/types/func_darwin.go +++ b/types/func_darwin.go @@ -2,26 +2,20 @@ package types import ( "debug/macho" - "os" + "fmt" ) -func init() { - f, err := macho.Open(os.Args[0]) - if err != nil { - panic("cannot read Mach-O binary: " + err.Error()) - } - +func initMachOFunctionTables(f *macho.File) error { pclntab := f.Section("__gopclntab") pclntabData, err := readAll(pclntab, pclntab.Size) if err != nil { - panic("cannot read pclntab: " + err.Error()) + return fmt.Errorf("cannot read pclntab: %w", err) } - symtab := f.Section("__gosymtab") symtabData, err := readAll(symtab, symtab.Size) if err != nil { - panic("cannot read symtab: " + err.Error()) + return fmt.Errorf("cannot read symtab: %w", err) } - initFunctionTables(pclntabData, symtabData) + return nil } diff --git a/types/func_linux.go b/types/func_linux.go index a0f6759..ccbe5fb 100644 --- a/types/func_linux.go +++ b/types/func_linux.go @@ -2,26 +2,19 @@ package types import ( "debug/elf" - "os" ) -func init() { - f, err := elf.Open(os.Args[0]) - if err != nil { - panic("cannot read elf binary: " + err.Error()) - } - +func initELFFunctionTables(f *elf.File) error { pclntab := f.Section(".gopclntab") pclntabData, err := readAll(pclntab, pclntab.Size) if err != nil { - panic("cannot read pclntab: " + err.Error()) + return fmt.Errorf("cannot read pclntab: %w", err) } - symtab := f.Section(".gosymtab") symtabData, err := readAll(symtab, symtab.Size) if err != nil { - panic("cannot read symtab: " + err.Error()) + return fmt.Errorf("cannot read symtab: %w", err) } - initFunctionTables(pclntabData, symtabData) + return nil } diff --git a/types/obj_darwin.go b/types/obj_darwin.go new file mode 100644 index 0000000..850b7db --- /dev/null +++ b/types/obj_darwin.go @@ -0,0 +1,17 @@ +package types + +import ( + "debug/macho" + "os" +) + +func init() { + f, err := macho.Open(os.Args[0]) + if err != nil { + panic("cannot read Mach-O binary: " + err.Error()) + } + + if err := initMachOFunctionTables(f); err != nil { + panic(err) + } +} diff --git a/types/obj_linux.go b/types/obj_linux.go new file mode 100644 index 0000000..3336875 --- /dev/null +++ b/types/obj_linux.go @@ -0,0 +1,12 @@ +package types + +func init() { + f, err := elf.Open(os.Args[0]) + if err != nil { + panic("cannot read elf binary: " + err.Error()) + } + + if err := initELFFunctionTables(f); err != nil { + panic(err) + } +} From 098f0854e84852022212c08730859afc92a79207 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 08:16:12 +1000 Subject: [PATCH 02/10] Close file once no longer needed --- types/obj_darwin.go | 1 + types/obj_linux.go | 1 + 2 files changed, 2 insertions(+) diff --git a/types/obj_darwin.go b/types/obj_darwin.go index 850b7db..e19ee9c 100644 --- a/types/obj_darwin.go +++ b/types/obj_darwin.go @@ -10,6 +10,7 @@ func init() { if err != nil { panic("cannot read Mach-O binary: " + err.Error()) } + defer f.Close() if err := initMachOFunctionTables(f); err != nil { panic(err) diff --git a/types/obj_linux.go b/types/obj_linux.go index 3336875..736f2be 100644 --- a/types/obj_linux.go +++ b/types/obj_linux.go @@ -5,6 +5,7 @@ func init() { if err != nil { panic("cannot read elf binary: " + err.Error()) } + defer f.Close() if err := initELFFunctionTables(f); err != nil { panic(err) From a1a3fcbbb93aa1bdbd42a0683b2219313524a9e8 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 08:17:16 +1000 Subject: [PATCH 03/10] Move helpers --- types/buildid_darwin.go | 15 --------------- types/func_darwin.go | 21 --------------------- types/func_linux.go | 20 -------------------- types/obj_darwin.go | 16 ++++++++++++++++ types/obj_linux.go | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 56 deletions(-) delete mode 100644 types/buildid_darwin.go delete mode 100644 types/func_darwin.go delete mode 100644 types/func_linux.go diff --git a/types/buildid_darwin.go b/types/buildid_darwin.go deleted file mode 100644 index 1f25472..0000000 --- a/types/buildid_darwin.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -import ( - "debug/macho" - "os" -) - -func init() { - f, err := macho.Open(os.Args[0]) - if err != nil { - panic("cannot read Mach-O binary: " + err.Error()) - } - defer f.Close() - -} diff --git a/types/func_darwin.go b/types/func_darwin.go deleted file mode 100644 index 8dafa61..0000000 --- a/types/func_darwin.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - "debug/macho" - "fmt" -) - -func initMachOFunctionTables(f *macho.File) error { - pclntab := f.Section("__gopclntab") - pclntabData, err := readAll(pclntab, pclntab.Size) - if err != nil { - return fmt.Errorf("cannot read pclntab: %w", err) - } - symtab := f.Section("__gosymtab") - symtabData, err := readAll(symtab, symtab.Size) - if err != nil { - return fmt.Errorf("cannot read symtab: %w", err) - } - initFunctionTables(pclntabData, symtabData) - return nil -} diff --git a/types/func_linux.go b/types/func_linux.go deleted file mode 100644 index ccbe5fb..0000000 --- a/types/func_linux.go +++ /dev/null @@ -1,20 +0,0 @@ -package types - -import ( - "debug/elf" -) - -func initELFFunctionTables(f *elf.File) error { - pclntab := f.Section(".gopclntab") - pclntabData, err := readAll(pclntab, pclntab.Size) - if err != nil { - return fmt.Errorf("cannot read pclntab: %w", err) - } - symtab := f.Section(".gosymtab") - symtabData, err := readAll(symtab, symtab.Size) - if err != nil { - return fmt.Errorf("cannot read symtab: %w", err) - } - initFunctionTables(pclntabData, symtabData) - return nil -} diff --git a/types/obj_darwin.go b/types/obj_darwin.go index e19ee9c..937ed36 100644 --- a/types/obj_darwin.go +++ b/types/obj_darwin.go @@ -2,6 +2,7 @@ package types import ( "debug/macho" + "fmt" "os" ) @@ -16,3 +17,18 @@ func init() { panic(err) } } + +func initMachOFunctionTables(f *macho.File) error { + pclntab := f.Section("__gopclntab") + pclntabData, err := readAll(pclntab, pclntab.Size) + if err != nil { + return fmt.Errorf("cannot read pclntab: %w", err) + } + symtab := f.Section("__gosymtab") + symtabData, err := readAll(symtab, symtab.Size) + if err != nil { + return fmt.Errorf("cannot read symtab: %w", err) + } + initFunctionTables(pclntabData, symtabData) + return nil +} diff --git a/types/obj_linux.go b/types/obj_linux.go index 736f2be..798eb0a 100644 --- a/types/obj_linux.go +++ b/types/obj_linux.go @@ -11,3 +11,18 @@ func init() { panic(err) } } + +func initELFFunctionTables(f *elf.File) error { + pclntab := f.Section(".gopclntab") + pclntabData, err := readAll(pclntab, pclntab.Size) + if err != nil { + return fmt.Errorf("cannot read pclntab: %w", err) + } + symtab := f.Section(".gosymtab") + symtabData, err := readAll(symtab, symtab.Size) + if err != nil { + return fmt.Errorf("cannot read symtab: %w", err) + } + initFunctionTables(pclntabData, symtabData) + return nil +} From 86401b46bdeca85c9c65218306d864cbfcc91577 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 08:45:39 +1000 Subject: [PATCH 04/10] Parse build ID from mach-o binaries --- types/buildid.go | 41 +++++++++++++++++++++++++++++++++++++++++ types/func.go | 6 +++++- types/obj_darwin.go | 31 +++++++++++++++++++++---------- types/obj_linux.go | 24 +++++++++++++----------- 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 types/buildid.go diff --git a/types/buildid.go b/types/buildid.go new file mode 100644 index 0000000..7c7aef5 --- /dev/null +++ b/types/buildid.go @@ -0,0 +1,41 @@ +package types + +import ( + "bytes" + "errors" + "strconv" +) + +// BuildID returns the build identifier for the binary +// this function was called from. +func BuildID() string { + return buildid +} + +func parseBuildID(data []byte) error { + // From https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/buildid.go#L300 + i := bytes.Index(data, buildIDPrefix) + if i < 0 { + return errBuildIDNotFound + } + j := bytes.Index(data[i+len(buildIDPrefix):], buildIDEnd) + if j < 0 { + return errBuildIDNotFound + } + quoted := data[i+len(buildIDPrefix)-1 : i+len(buildIDPrefix)+j+1] + id, err := strconv.Unquote(string(quoted)) + if err != nil { + return errBuildIDNotFound + } + buildid = id + return nil +} + +var ( + buildIDPrefix = []byte("\xff Go build ID: \"") + buildIDEnd = []byte("\"\n \xff") + + errBuildIDNotFound = errors.New("build ID not found") + + buildid string +) diff --git a/types/func.go b/types/func.go index 2d08929..7dca674 100644 --- a/types/func.go +++ b/types/func.go @@ -4,6 +4,7 @@ package types import ( "debug/gosym" + "errors" "io" "reflect" "runtime" @@ -150,7 +151,10 @@ func initFunctionTables(pclntab, symtab []byte) { } } -func readAll(r io.ReaderAt, size uint64) ([]byte, error) { +func readSection(r io.ReaderAt, size uint64) ([]byte, error) { + if r == nil { + return nil, errors.New("section missing") + } b := make([]byte, size) n, err := r.ReadAt(b, 0) if err != nil && n < len(b) { diff --git a/types/obj_darwin.go b/types/obj_darwin.go index 937ed36..dbaa758 100644 --- a/types/obj_darwin.go +++ b/types/obj_darwin.go @@ -2,7 +2,6 @@ package types import ( "debug/macho" - "fmt" "os" ) @@ -13,22 +12,34 @@ func init() { } defer f.Close() - if err := initMachOFunctionTables(f); err != nil { - panic(err) - } + initMachOFunctionTables(f) + initMachOBuildID(f) } -func initMachOFunctionTables(f *macho.File) error { +func initMachOFunctionTables(f *macho.File) { pclntab := f.Section("__gopclntab") - pclntabData, err := readAll(pclntab, pclntab.Size) + pclntabData, err := readSection(pclntab, pclntab.Size) if err != nil { - return fmt.Errorf("cannot read pclntab: %w", err) + panic("cannot read pclntab: " + err.Error()) } symtab := f.Section("__gosymtab") - symtabData, err := readAll(symtab, symtab.Size) + symtabData, err := readSection(symtab, symtab.Size) if err != nil { - return fmt.Errorf("cannot read symtab: %w", err) + panic("cannot read symtab: " + err.Error()) } initFunctionTables(pclntabData, symtabData) - return nil +} + +func initMachOBuildID(f *macho.File) { + text := f.Section("__text") + + // Read up to 32KB from the text section. + // See https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/note.go#L199 + buf, err := readSection(text, min(text.Size, 32*1024)) + if err != nil { + panic("cannot read __text: " + err.Error()) + } + if err := parseBuildID(buf); err != nil { + panic(err) + } } diff --git a/types/obj_linux.go b/types/obj_linux.go index 798eb0a..124401c 100644 --- a/types/obj_linux.go +++ b/types/obj_linux.go @@ -1,28 +1,30 @@ package types +import ( + "debug/elf" + "os" +) + func init() { - f, err := elf.Open(os.Args[0]) + obj, err := elf.Open(os.Args[0]) if err != nil { panic("cannot read elf binary: " + err.Error()) } - defer f.Close() + defer obj.Close() - if err := initELFFunctionTables(f); err != nil { - panic(err) - } + initELFFunctionTables(obj) } -func initELFFunctionTables(f *elf.File) error { +func initELFFunctionTables(f *elf.File) { pclntab := f.Section(".gopclntab") - pclntabData, err := readAll(pclntab, pclntab.Size) + pclntabData, err := readSection(pclntab, pclntab.Size) if err != nil { - return fmt.Errorf("cannot read pclntab: %w", err) + panic("cannot read pclntab: " + err.Error()) } symtab := f.Section(".gosymtab") - symtabData, err := readAll(symtab, symtab.Size) + symtabData, err := readSection(symtab, symtab.Size) if err != nil { - return fmt.Errorf("cannot read symtab: %w", err) + panic("cannot read symtab: " + err.Error()) } initFunctionTables(pclntabData, symtabData) - return nil } From 94fa74b031d23482a01be4767da28fe483e47a7d Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 08:45:50 +1000 Subject: [PATCH 05/10] Add a quick util to print build ID --- cmd/buildid/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 cmd/buildid/main.go diff --git a/cmd/buildid/main.go b/cmd/buildid/main.go new file mode 100644 index 0000000..dba7914 --- /dev/null +++ b/cmd/buildid/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "github.com/stealthrocket/coroutine/types" +) + +func main() { + fmt.Println(types.BuildID()) +} From b31bdb2552aa9c0f7d8ece3fc5ec596f672f84a6 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 09:18:08 +1000 Subject: [PATCH 06/10] Move mach-o parser to _darwin file --- types/buildid.go | 34 +--------------------------------- types/obj_darwin.go | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/types/buildid.go b/types/buildid.go index 7c7aef5..70df11a 100644 --- a/types/buildid.go +++ b/types/buildid.go @@ -1,41 +1,9 @@ package types -import ( - "bytes" - "errors" - "strconv" -) - // BuildID returns the build identifier for the binary // this function was called from. func BuildID() string { return buildid } -func parseBuildID(data []byte) error { - // From https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/buildid.go#L300 - i := bytes.Index(data, buildIDPrefix) - if i < 0 { - return errBuildIDNotFound - } - j := bytes.Index(data[i+len(buildIDPrefix):], buildIDEnd) - if j < 0 { - return errBuildIDNotFound - } - quoted := data[i+len(buildIDPrefix)-1 : i+len(buildIDPrefix)+j+1] - id, err := strconv.Unquote(string(quoted)) - if err != nil { - return errBuildIDNotFound - } - buildid = id - return nil -} - -var ( - buildIDPrefix = []byte("\xff Go build ID: \"") - buildIDEnd = []byte("\"\n \xff") - - errBuildIDNotFound = errors.New("build ID not found") - - buildid string -) +var buildid string diff --git a/types/obj_darwin.go b/types/obj_darwin.go index dbaa758..0326318 100644 --- a/types/obj_darwin.go +++ b/types/obj_darwin.go @@ -1,8 +1,10 @@ package types import ( + "bytes" "debug/macho" "os" + "strconv" ) func init() { @@ -35,11 +37,29 @@ func initMachOBuildID(f *macho.File) { // Read up to 32KB from the text section. // See https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/note.go#L199 - buf, err := readSection(text, min(text.Size, 32*1024)) + data, err := readSection(text, min(text.Size, 32*1024)) if err != nil { panic("cannot read __text: " + err.Error()) } - if err := parseBuildID(buf); err != nil { - panic(err) + + // From https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/buildid.go#L300 + i := bytes.Index(data, buildIDPrefix) + if i < 0 { + panic("build ID not found") + } + j := bytes.Index(data[i+len(buildIDPrefix):], buildIDEnd) + if j < 0 { + panic("build ID not found") + } + quoted := data[i+len(buildIDPrefix)-1 : i+len(buildIDPrefix)+j+1] + id, err := strconv.Unquote(string(quoted)) + if err != nil { + panic("build ID not found") } + buildid = id } + +var ( + buildIDPrefix = []byte("\xff Go build ID: \"") + buildIDEnd = []byte("\"\n \xff") +) From 2f2ab32b8dff5898b9ef6f8b9869e25ee561500c Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 09:22:28 +1000 Subject: [PATCH 07/10] Parse build ID from ELF binaries --- types/obj_linux.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/types/obj_linux.go b/types/obj_linux.go index 124401c..978fa98 100644 --- a/types/obj_linux.go +++ b/types/obj_linux.go @@ -1,18 +1,20 @@ package types import ( + "bytes" "debug/elf" "os" ) func init() { - obj, err := elf.Open(os.Args[0]) + f, err := elf.Open(os.Args[0]) if err != nil { panic("cannot read elf binary: " + err.Error()) } - defer obj.Close() + defer f.Close() - initELFFunctionTables(obj) + initELFFunctionTables(f) + initELFBuildID(f) } func initELFFunctionTables(f *elf.File) { @@ -28,3 +30,27 @@ func initELFFunctionTables(f *elf.File) { } initFunctionTables(pclntabData, symtabData) } + +func initELFBuildID(f *elf.File) { + noteSection := f.Section(".note.go.buildid") + note, err := readSection(noteSection, noteSection.Size) + if err != nil { + panic("cannot read build ID: " + err.Error()) + } + + // See https://github.com/golang/go/blob/3803c858/src/cmd/internal/buildid/note.go#L135C3-L135C3 + nameSize := f.ByteOrder.Uint32(note) + valSize := f.ByteOrder.Uint32(note[4:]) + tag := f.ByteOrder.Uint32(note[8:]) + nname := note[12:16] + if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == buildIDTag && bytes.Equal(nname, buildIDNote) { + buildid = string(note[16 : 16+valSize]) + } else { + panic("build ID not found") + } +} + +var ( + buildIDNote = []byte("Go\x00\x00") + buildIDTag = uint32(4) +) From 0362e4e298da230dee0dde23ff9f2e98151a77a5 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 09:39:13 +1000 Subject: [PATCH 08/10] Include build ID when serializing objects Check the build ID matches when deserializing objects --- cmd/buildid/main.go | 11 ----------- coroutine_durable.go | 5 ++++- types/buildid.go | 9 ++------- types/obj_darwin.go | 2 +- types/obj_linux.go | 2 +- types/serde.go | 34 +++++++++++++++++++++++++++++----- types/serde_test.go | 20 ++++++++++++++++---- 7 files changed, 53 insertions(+), 30 deletions(-) delete mode 100644 cmd/buildid/main.go diff --git a/cmd/buildid/main.go b/cmd/buildid/main.go deleted file mode 100644 index dba7914..0000000 --- a/cmd/buildid/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/stealthrocket/coroutine/types" -) - -func main() { - fmt.Println(types.BuildID()) -} diff --git a/coroutine_durable.go b/coroutine_durable.go index e565e4d..bf73d8c 100644 --- a/coroutine_durable.go +++ b/coroutine_durable.go @@ -93,7 +93,10 @@ func (c *Context[R, S]) Marshal() ([]byte, error) { // context. func (c *Context[R, S]) Unmarshal(b []byte) (int, error) { start := len(b) - v, b := types.Deserialize(b) + v, b, err := types.Deserialize(b) + if err != nil { + return 0, err + } s := v.(*serializedCoroutine) c.entry = s.entry c.Stack = s.stack diff --git a/types/buildid.go b/types/buildid.go index 70df11a..ea97546 100644 --- a/types/buildid.go +++ b/types/buildid.go @@ -1,9 +1,4 @@ package types -// BuildID returns the build identifier for the binary -// this function was called from. -func BuildID() string { - return buildid -} - -var buildid string +// buildID is the build identifier for the binary. +var buildID string diff --git a/types/obj_darwin.go b/types/obj_darwin.go index 0326318..755164e 100644 --- a/types/obj_darwin.go +++ b/types/obj_darwin.go @@ -56,7 +56,7 @@ func initMachOBuildID(f *macho.File) { if err != nil { panic("build ID not found") } - buildid = id + buildID = id } var ( diff --git a/types/obj_linux.go b/types/obj_linux.go index 978fa98..29559c7 100644 --- a/types/obj_linux.go +++ b/types/obj_linux.go @@ -44,7 +44,7 @@ func initELFBuildID(f *elf.File) { tag := f.ByteOrder.Uint32(note[8:]) nname := note[12:16] if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == buildIDTag && bytes.Equal(nname, buildIDNote) { - buildid = string(note[16 : 16+valSize]) + buildID = string(note[16 : 16+valSize]) } else { panic("build ID not found") } diff --git a/types/serde.go b/types/serde.go index a550c1c..3ec13a1 100644 --- a/types/serde.go +++ b/types/serde.go @@ -7,6 +7,7 @@ package types import ( "encoding/binary" + "errors" "fmt" "reflect" "unsafe" @@ -15,6 +16,10 @@ import ( // sID is the unique sID of a pointer or type in the serialized format. type sID int64 +// ErrBuildIDMismatch is an error that occurs when a program attempts +// to deserialize objects from another build. +var ErrBuildIDMismatch = errors.New("build ID mismatch") + // Serialize x. // // The output of Serialize can be reconstructed back to a Go value using @@ -35,14 +40,17 @@ func Serialize(x any) []byte { } // Deserialize value from b. Return left over bytes. -func Deserialize(b []byte) (interface{}, []byte) { - d := newDeserializer(b) +func Deserialize(b []byte) (interface{}, []byte, error) { + d, err := newDeserializer(b) + if err != nil { + return nil, nil, err + } var x interface{} px := &x t := reflect.TypeOf(px).Elem() p := unsafe.Pointer(px) deserializeInterface(d, t, p) - return x, d.b + return x, d.b, nil } type Deserializer struct { @@ -54,11 +62,22 @@ type Deserializer struct { b []byte } -func newDeserializer(b []byte) *Deserializer { +func newDeserializer(b []byte) (*Deserializer, error) { + buildIDLength, n := binary.Varint(b) + if n <= 0 || buildIDLength <= 0 || buildIDLength > int64(len(buildID)) || int64(len(b)-n) < buildIDLength { + return nil, fmt.Errorf("missing or invalid build ID") + } + b = b[n:] + serializedBuildID := string(b[:buildIDLength]) + b = b[buildIDLength:] + if serializedBuildID != buildID { + return nil, fmt.Errorf("%w: got %v, expect %v", ErrBuildIDMismatch, serializedBuildID, buildID) + } + return &Deserializer{ ptrs: make(map[sID]unsafe.Pointer), b: b, - } + }, nil } func (d *Deserializer) readPtr() (unsafe.Pointer, sID) { @@ -123,9 +142,14 @@ type Serializer struct { } func newSerializer() *Serializer { + b := make([]byte, 0, 128) + b = binary.AppendVarint(b, int64(len(buildID))) + b = append(b, buildID...) + return &Serializer{ ptrs: make(map[unsafe.Pointer]sID), scanptrs: make(map[reflect.Value]struct{}), + b: b, } } diff --git a/types/serde_test.go b/types/serde_test.go index 4e08de4..5ac3094 100644 --- a/types/serde_test.go +++ b/types/serde_test.go @@ -40,7 +40,10 @@ func TestSerdeTime(t *testing.T) { func testSerdeTime(t *testing.T, x time.Time) { b := Serialize(x) - out, _ := Deserialize(b) + out, _, err := Deserialize(b) + if err != nil { + t.Fatal(err) + } if !x.Equal(out.(time.Time)) { t.Errorf("expected %v, got %v", x, out) @@ -120,7 +123,10 @@ func TestReflect(t *testing.T) { typ := reflect.TypeOf(x) t.Run(fmt.Sprintf("%d-%s", i, typ), func(t *testing.T) { b := Serialize(x) - out, b := Deserialize(b) + out, b, err := Deserialize(b) + if err != nil { + t.Fatal(err) + } assertEqual(t, x, out) @@ -302,7 +308,10 @@ func TestReflectCustom(t *testing.T) { // unserializable function in CheckRedirect. b := Serialize(x) - out, b := Deserialize(b) + out, b, err := Deserialize(b) + if err != nil { + t.Fatal(err) + } assertEqual(t, x.Timeout, out.(http.Client).Timeout) @@ -615,7 +624,10 @@ func assertRoundTrip[T any](t *testing.T, orig T) T { t.Helper() b := Serialize(orig) - out, b := Deserialize(b) + out, b, err := Deserialize(b) + if err != nil { + t.Fatal(err) + } assertEqual(t, orig, out) From fddb6ae78c27ffe019c8114e9be6a9183b1c7d25 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 09:48:48 +1000 Subject: [PATCH 09/10] Return a higher-level error when state is invalid --- coroutine.go | 12 +++++++++--- coroutine_durable.go | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/coroutine.go b/coroutine.go index c51a86e..4a27c6a 100644 --- a/coroutine.go +++ b/coroutine.go @@ -104,6 +104,12 @@ func LoadContext[R, S any]() *Context[R, S] { } } -// ErrNotDurable is an error that occurs when attempting to -// serialize a coroutine that is not durable. -var ErrNotDurable = errors.New("only durable coroutines can be serialized") +var ( + // ErrNotDurable is an error that occurs when attempting to + // serialize a coroutine that is not durable. + ErrNotDurable = errors.New("only durable coroutines can be serialized") + + // ErrInvalidState is an error that occurs when attempting to + // deserialize a coroutine that was serialized in another build. + ErrInvalidState = errors.New("durable coroutine was serialized in another build") +) diff --git a/coroutine_durable.go b/coroutine_durable.go index bf73d8c..d1cfa44 100644 --- a/coroutine_durable.go +++ b/coroutine_durable.go @@ -3,6 +3,7 @@ package coroutine import ( + "errors" "runtime" "unsafe" @@ -95,6 +96,9 @@ func (c *Context[R, S]) Unmarshal(b []byte) (int, error) { start := len(b) v, b, err := types.Deserialize(b) if err != nil { + if errors.Is(err, types.ErrBuildIDMismatch) { + return 0, ErrInvalidState + } return 0, err } s := v.(*serializedCoroutine) From 1c881fd58ebd9e260f01f84983d296894170d670 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Thu, 19 Oct 2023 09:49:22 +1000 Subject: [PATCH 10/10] Update the example --- examples/scrape/go.mod | 2 ++ examples/scrape/main.go | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/scrape/go.mod b/examples/scrape/go.mod index d0e001d..d849268 100644 --- a/examples/scrape/go.mod +++ b/examples/scrape/go.mod @@ -3,3 +3,5 @@ module scrape go 1.21.0 require github.com/stealthrocket/coroutine v0.0.0-20230927150141-7c62a3508ce8 + +replace github.com/stealthrocket/coroutine => ../../ diff --git a/examples/scrape/main.go b/examples/scrape/main.go index d72330f..12920ae 100644 --- a/examples/scrape/main.go +++ b/examples/scrape/main.go @@ -28,7 +28,11 @@ func main() { log.Fatal(err) } } else if _, err := coro.Context().Unmarshal(state); err != nil { - log.Fatal(err) + if errors.Is(err, coroutine.ErrInvalidState) { + log.Println("warning: coroutine state is no longer valid. Starting fresh") + } else { + log.Fatal(err) + } } }