github.com/trim21/go-phpserialize@v0.0.22-0.20240301204449-2fca0319b3f0/internal/encoder/struct.go (about) 1 package encoder 2 3 import ( 4 "fmt" 5 "reflect" 6 "time" 7 8 "github.com/trim21/go-phpserialize/internal/runtime" 9 ) 10 11 type structEncoder struct { 12 offset uintptr 13 // a direct value handler, like `encodeInt` 14 // struct encoder should de-ref pointers and pass real address to encoder. 15 // address of map, slice, array may still be 0, bug theirs encoder will handle that at null. 16 encode encoder 17 fieldName string // field fieldName 18 zero emptyFunc 19 indirect bool 20 ptr bool 21 ptrDepth int 22 } 23 24 var timeType = runtime.Type2RType(reflect.TypeOf((*time.Time)(nil)).Elem()) 25 26 type seenMap = map[*runtime.Type]*structRecEncoder 27 28 type structRecEncoder struct { 29 enc encoder 30 } 31 32 func (s *structRecEncoder) Encode(ctx *Ctx, b []byte, p uintptr) ([]byte, error) { 33 return s.enc(ctx, b, p) 34 } 35 36 func compileStruct(rt *runtime.Type, seen seenMap) (encoder, error) { 37 recursiveEnc, hasSeen := seen[rt] 38 39 if hasSeen { 40 return recursiveEnc.Encode, nil 41 } else { 42 seen[rt] = &structRecEncoder{} 43 } 44 45 hasOmitEmpty, err := hasOmitEmptyField(runtime.RType2Type(rt)) 46 if err != nil { 47 return nil, err 48 } 49 50 var enc encoder 51 if !hasOmitEmpty { 52 enc, err = compileStructNoOmitEmptyFastPath(rt, seen) 53 } else { 54 enc, err = compileStructBufferSlowPath(rt, seen) 55 } 56 if err != nil { 57 return nil, err 58 } 59 60 recursiveEnc, recursiveStruct := seen[rt] 61 if recursiveStruct { 62 if recursiveEnc.enc == nil { 63 recursiveEnc.enc = enc 64 return recursiveEnc.Encode, nil 65 } 66 } 67 68 return enc, nil 69 } 70 71 func hasOmitEmptyField(rt reflect.Type) (bool, error) { 72 for i := 0; i < rt.NumField(); i++ { 73 field := rt.Field(i) 74 cfg := runtime.StructTagFromField(field) 75 if field.Type.Kind() == reflect.Struct { 76 if cfg.IsOmitEmpty { 77 return false, fmt.Errorf("can't use 'omitempty' config with struct field: %s{}.%s", rt.String(), field.Name) 78 } 79 80 v, err := hasOmitEmptyField(field.Type) 81 if err != nil { 82 return false, err 83 } 84 if v { 85 return true, nil 86 } 87 } 88 if cfg.IsOmitEmpty { 89 return true, nil 90 } 91 } 92 93 return false, nil 94 } 95 96 // struct don't have `omitempty` tag, fast path 97 func compileStructNoOmitEmptyFastPath(rt *runtime.Type, seen seenMap) (encoder, error) { 98 fields, err := compileStructFieldsEncoders(rt, 0, seen) 99 if err != nil { 100 return nil, err 101 } 102 103 var fieldCount int64 = int64(len(fields)) 104 return func(ctx *Ctx, b []byte, p uintptr) ([]byte, error) { 105 b = appendArrayBegin(b, fieldCount) 106 107 var err error 108 109 FIELD: 110 for _, field := range fields { 111 b = appendPhpStringVariable(ctx, b, field.fieldName) 112 113 fp := field.offset + p 114 115 if field.ptr { 116 if field.indirect { 117 fp = PtrDeRef(fp) 118 } 119 120 if fp == 0 { 121 b = appendNull(b) 122 continue 123 } 124 125 for i := 0; i < field.ptrDepth; i++ { 126 fp = PtrDeRef(fp) 127 if fp == 0 { 128 b = appendNull(b) 129 continue FIELD 130 } 131 } 132 } 133 134 b, err = field.encode(ctx, b, fp) 135 if err != nil { 136 return b, err 137 } 138 } 139 140 return append(b, '}'), nil 141 }, nil 142 } 143 144 func compileStructBufferSlowPath(rt *runtime.Type, seen seenMap) (encoder, error) { 145 encoders, err := compileStructFieldsEncoders(rt, 0, seen) 146 if err != nil { 147 return nil, err 148 } 149 150 return func(ctx *Ctx, b []byte, p uintptr) ([]byte, error) { 151 buf := newBuffer() 152 defer freeBuffer(buf) 153 structBuffer := buf.b 154 155 var err error 156 var writtenField int64 157 158 FIELD: 159 for _, field := range encoders { 160 fp := field.offset + p 161 162 if field.ptr { 163 if field.indirect { 164 fp = PtrDeRef(fp) 165 } 166 167 if fp == 0 { 168 if field.zero != nil { 169 continue FIELD 170 } 171 172 structBuffer = appendPhpStringVariable(ctx, structBuffer, field.fieldName) 173 writtenField++ 174 structBuffer = appendNull(structBuffer) 175 continue 176 } 177 178 for i := 0; i < field.ptrDepth; i++ { 179 fp = PtrDeRef(fp) 180 if fp == 0 { 181 if field.zero != nil { 182 continue FIELD 183 } 184 185 structBuffer = appendPhpStringVariable(ctx, structBuffer, field.fieldName) 186 structBuffer = appendNull(structBuffer) 187 writtenField++ 188 continue FIELD 189 } 190 } 191 } 192 193 if field.zero != nil { 194 empty, err := field.zero(ctx, fp) 195 if err != nil { 196 return nil, err 197 } 198 if empty { 199 continue 200 } 201 } 202 203 structBuffer = appendPhpStringVariable(ctx, structBuffer, field.fieldName) 204 structBuffer, err = field.encode(ctx, structBuffer, fp) 205 if err != nil { 206 return b, err 207 } 208 209 writtenField++ 210 } 211 212 b = appendArrayBegin(b, writtenField) 213 b = append(b, structBuffer...) 214 buf.b = structBuffer 215 216 return append(b, '}'), nil 217 }, nil 218 } 219 220 func compileStructFieldsEncoders(rt *runtime.Type, baseOffset uintptr, seen seenMap) (encoders []structEncoder, err error) { 221 indirect := runtime.IfaceIndir(rt) 222 223 for i := 0; i < rt.NumField(); i++ { 224 field := rt.Field(i) 225 cfg := runtime.StructTagFromField(field) 226 if cfg.Key == "-" || !cfg.Field.IsExported() { 227 continue 228 } 229 offset := field.Offset + baseOffset 230 231 var ptrDepth = 0 232 233 var isEmpty emptyFunc 234 var fieldEncoder encoder 235 var err error 236 237 var isPtrField = field.Type.Kind() == reflect.Ptr 238 239 if field.Type.Kind() == reflect.Ptr { 240 isEmpty = EmptyPtr 241 242 switch field.Type.Elem().Kind() { 243 case reflect.Ptr: 244 return nil, fmt.Errorf("encoding nested ptr is not supported %s", field.Type.String()) 245 246 case reflect.Map: 247 ptrDepth++ 248 fallthrough 249 default: 250 fieldEncoder, err = compile(runtime.Type2RType(field.Type.Elem()), seen) 251 if err != nil { 252 return nil, err 253 } 254 } 255 } 256 257 if fieldEncoder == nil { 258 if field.Type.Kind() == reflect.Struct && field.Anonymous { 259 enc, err := compileStructFieldsEncoders(runtime.Type2RType(field.Type), offset, seen) 260 if err != nil { 261 return nil, err 262 } 263 264 encoders = append(encoders, enc...) 265 continue 266 } 267 268 fieldEncoder, err = compile(runtime.Type2RType(field.Type), seen) 269 if err != nil { 270 return nil, err 271 } 272 } 273 274 var enc encoder 275 if cfg.IsString { 276 if field.Type.Kind() == reflect.Ptr { 277 enc, err = compileAsString(runtime.Type2RType(field.Type.Elem())) 278 fieldEncoder = func(ctx *Ctx, b []byte, p uintptr) ([]byte, error) { 279 // fmt.Println(p) 280 // fmt.Println(**(**bool)(unsafe.Pointer(&p))) 281 // fmt.Println(PtrDeRef(p)) 282 // fmt.Println(PtrDeRef(PtrDeRef(p))) 283 return enc(ctx, b, p) 284 } 285 // if !indirect && field.Type.Elem().Kind() != reflect.Bool { 286 // ptrDepth++ 287 // } 288 } else { 289 fieldEncoder, err = compileAsString(runtime.Type2RType(field.Type)) 290 } 291 if err != nil { 292 return nil, err 293 } 294 } else { 295 if indirect && (field.Type.Kind() == reflect.Map) { 296 isPtrField = true 297 } 298 } 299 300 if cfg.IsOmitEmpty && isEmpty == nil { 301 isEmpty, err = compileEmptyFunc(runtime.Type2RType(field.Type)) 302 if err != nil { 303 return nil, err 304 } 305 } 306 307 encoders = append(encoders, structEncoder{ 308 offset: offset, 309 encode: fieldEncoder, 310 fieldName: cfg.Name(), 311 zero: isEmpty, 312 indirect: indirect, 313 ptrDepth: ptrDepth, 314 ptr: isPtrField, 315 }) 316 } 317 318 return encoders, nil 319 }