From 4355ae308be638e9a076fc07448155a3fab2015a Mon Sep 17 00:00:00 2001 From: kamran Date: Sat, 13 Jun 2026 01:17:28 +0500 Subject: [PATCH 1/2] Add struct tag recovery tests and TaggedStruct for issue #37 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TaggedStruct with exported, unexported, and untagged fields to testproject - Add comprehensive tag test suite (positive + negative cases) - Positive tests intentionally fail — implementation not yet written --- main_test.go | 31 +++++++++++++++++++++++++++++++ testproject/main.go | 22 +++++++++++++++------- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/main_test.go b/main_test.go index bf5bbe7..6a05047 100644 --- a/main_test.go +++ b/main_test.go @@ -134,6 +134,37 @@ func TestAllVersions(t *testing.T) { } } + // --- Struct field Tag recovery tests --- + if v != "15" && v != "16" { + found_tagged := false + for _, typ := range data.Types { + if typ.Str == "main.TaggedStruct" && typ.Kind == "Struct" { + found_tagged = true + if !strings.Contains(typ.Reconstructed, `json:"id"`) { + t.Errorf("Go %s exported field tag json:id missing in TaggedStruct", v) + } + if !strings.Contains(typ.Reconstructed, `db:"user_id"`) { + t.Errorf("Go %s multi-tag db:user_id missing in TaggedStruct", v) + } + if !strings.Contains(typ.Reconstructed, `json:"name"`) { + t.Errorf("Go %s exported field tag json:name missing in TaggedStruct", v) + } + if !strings.Contains(typ.Reconstructed, `json:"password"`) { + t.Errorf("Go %s unexported field tag json:password missing in TaggedStruct", v) + } + if strings.Contains(typ.Reconstructed, "Active`") || strings.Contains(typ.Reconstructed, "`Active") { + t.Errorf("Go %s Active field has spurious backtick in TaggedStruct", v) + } + if strings.Contains(typ.Reconstructed, "``") { + t.Errorf("Go %s empty backtick pair in TaggedStruct Reconstructed", v) + } + } + } + if !found_tagged { + t.Errorf("Go %s TaggedStruct not found in types", v) + } + } + if len(data.StdFunctions) == 0 { t.Errorf("Go %s std functions failed on %s: %s", v, file, err) } diff --git a/testproject/main.go b/testproject/main.go index 8571d4b..494cb40 100644 --- a/testproject/main.go +++ b/testproject/main.go @@ -1,10 +1,15 @@ /*Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.*/ package main -import "fmt" - -type structurea struct { - test string +import ( + "fmt" +) + +type TaggedStruct struct { + ID uint64 `json:"id" db:"user_id"` + Name string `json:"name"` + password string `json:"password"` + Active bool } func sum(s []int, c chan int) { @@ -16,10 +21,13 @@ func sum(s []int, c chan int) { } func main() { - var structa structurea - structa.test = "hi" + var Ts TaggedStruct + Ts.ID = 1234 + Ts.Name = "test" + Ts.password = "password" + Ts.Active = true - fmt.Println(structa) + fmt.Println(Ts) c := make(chan int) s := []int{7, 2, 8, 9} From 58910f0c2d7f50475b7be1ec0c88aeb1e326ab88 Mon Sep 17 00:00:00 2001 From: kamran Date: Mon, 15 Jun 2026 17:13:38 +0500 Subject: [PATCH 2/2] Implement struct field tag extraction for issue #37 - Extend readRTypeName to return (name, tag, err) - Go 1.7-1.16: read flag byte from name_len_raw[0], extract tag after name bytes - Go 1.17+: read flag byte separately, use distinct nameVarintLen/tagVarintLen to avoid shadowing - Fix flag check: flag&(1<<1)!=0 instead of ==3, catches unexported fields with tags - Wire tag into struct Reconstructed output in ParseType_impl --- objfile/objfile.go | 80 +++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/objfile/objfile.go b/objfile/objfile.go index 8de5fa0..193869a 100644 --- a/objfile/objfile.go +++ b/objfile/objfile.go @@ -424,7 +424,7 @@ func (e *Entry) readVarint(address uint64) (int, int, error) { } } -func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr uint64, is64bit bool, littleendian bool) (name string, err error) { +func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr uint64, is64bit bool, littleendian bool) (name string, tag string, err error) { // name str (for <= 1.16 encodes length like this, beyond it uses a varint encoding) // The first byte is a bit field containing: // @@ -457,23 +457,23 @@ func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr ui case "1.5": fallthrough case "1.6": - // pointer to GoString + // pointer to GoString — tag is a separate pointer in structField, not inline here nameLen, err := e.ReadPointerSizeMem(namePtr+ptrSize, is64bit, littleendian) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } deref, err := e.ReadPointerSizeMem(namePtr, is64bit, littleendian) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } name_raw, err := e.raw.read_memory(deref, nameLen) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } - return string(name_raw), nil + return string(name_raw), "", nil case "1.7": // types flags exists >= 1.7 fallthrough case "1.8": // type flag tflagExtraStart exists >= 1.8 @@ -495,21 +495,32 @@ func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr ui case "1.16": name_len_raw, err := e.raw.read_memory(namePtr, 3) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } + flag := name_len_raw[0] name_len := uint16(uint16(name_len_raw[1])<<8 | uint16(name_len_raw[2])) name_raw, err := e.raw.read_memory(namePtr+3, uint64(name_len)) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } name := string(name_raw) if typeFlags&tflagExtraStar != 0 { - return name[1:], nil - } else { - return name, nil + name = name[1:] + } + + if flag&(1<<1) != 0 { + tag_len_raw, err := e.raw.read_memory(namePtr+3+uint64(name_len), 2) + if err == nil { + tag_len := uint16(uint16(tag_len_raw[0])<<8 | uint16(tag_len_raw[1])) + tag_raw, err := e.raw.read_memory(namePtr+3+uint64(name_len)+2, uint64(tag_len)) + if err == nil { + return name, string(tag_raw), nil + } + } } + return name, "", nil case "1.17": fallthrough case "1.18": @@ -529,24 +540,39 @@ func (e *Entry) readRTypeName(runtimeVersion string, typeFlags tflag, namePtr ui case "1.25": fallthrough case "1.26": - varint_len, namelen, err := e.readVarint(namePtr + 1) + flag_raw, err := e.raw.read_memory(namePtr, 1) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } + flag := flag_raw[0] - name_raw, err := e.raw.read_memory(namePtr+1+uint64(varint_len), uint64(namelen)) + nameVarintLen, namelen, err := e.readVarint(namePtr + 1) if err != nil { - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") + } + + name_raw, err := e.raw.read_memory(namePtr+1+uint64(nameVarintLen), uint64(namelen)) + if err != nil { + return "", "", fmt.Errorf("Failed to read name") } name := string(name_raw) if typeFlags&tflagExtraStar != 0 { - return name[1:], nil - } else { - return name, nil + name = name[1:] } + + if flag&(1<<1) != 0 { + tagVarintLen, taglen, err := e.readVarint(namePtr + 1 + uint64(nameVarintLen) + uint64(namelen)) + if err == nil && taglen > 0 { + tag_raw, err := e.raw.read_memory(namePtr+1+uint64(nameVarintLen)+uint64(namelen)+uint64(tagVarintLen), uint64(taglen)) + if err == nil { + return name, string(tag_raw), nil + } + } + } + return name, "", nil } - return "", fmt.Errorf("Failed to read name") + return "", "", fmt.Errorf("Failed to read name") } func decodePtrSizeBytes(data []byte, is64bit bool, littleendian bool) (result uint64) { @@ -677,7 +703,7 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty namePtr = moduleData.Types + rtype.Str } - name, err = e.readRTypeName(runtimeVersion, rtype.Tflag, namePtr, is64bit, littleendian) + name, _, err = e.readRTypeName(runtimeVersion, rtype.Tflag, namePtr, is64bit, littleendian) if err != nil { return parsedTypesIn, fmt.Errorf("Failed to read type name") } @@ -1078,7 +1104,7 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn) name_ptr := decodePtrSizeBytes(imethoddata[0:ptrSize], is64bit, littleendian) - name, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian) + name, _, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian) if err != nil { continue } @@ -1182,7 +1208,7 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty parsedTypesIn, _ = e.ParseType_impl(runtimeVersion, moduleData, typeAddr, is64bit, littleendian, parsedTypesIn) name_ptr := moduleData.Types + uint64(methodNameOff) - name, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian) + name, _, err := e.readRTypeName(runtimeVersion, 0, name_ptr, is64bit, littleendian) if err != nil { continue } @@ -1253,7 +1279,7 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty field, found := parsedTypesIn.Get(typeAddr) if found { typeNameAddr := decodePtrSizeBytes(data[0:ptrSize], is64bit, littleendian) - typeName, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian) + typeName, _, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian) if err == nil { structDef += fmt.Sprintf("\n %-10s %s", typeName, field.(Type).Str) cstructDef += fmt.Sprintf(" %-10s %s;\n", field.(Type).CStr, replace_cpp_keywords(typeName)) @@ -1358,9 +1384,13 @@ func (e *Entry) ParseType_impl(runtimeVersion string, moduleData *ModuleData, ty field, found := parsedTypesIn.Get(typeAddr) if found { typeNameAddr := decodePtrSizeBytes(data[0:ptrSize], is64bit, littleendian) - typeName, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian) + typeName, tag, err := e.readRTypeName(runtimeVersion, 0, typeNameAddr, is64bit, littleendian) if err == nil { - structDef += fmt.Sprintf("\n %-10s %s", typeName, field.(Type).Str) + if tag != "" { + structDef += fmt.Sprintf("\n %-10s %s `%s`", typeName, field.(Type).Str, tag) + } else { + structDef += fmt.Sprintf("\n %-10s %s", typeName, field.(Type).Str) + } cstructDef += fmt.Sprintf(" %-10s %s;\n", field.(Type).CStr, replace_cpp_keywords(typeName)) } }