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  }