github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/internal/resolver/resolver.go (about)

     1  /*
     2   * Copyright 2021 ByteDance Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package resolver
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  	"sync"
    24  )
    25  
    26  type FieldOpts int
    27  type OffsetType int
    28  
    29  const (
    30  	F_omitempty FieldOpts = 1 << iota
    31  	F_stringize
    32  )
    33  
    34  const (
    35  	F_offset OffsetType = iota
    36  	F_deref
    37  )
    38  
    39  type Offset struct {
    40  	Size uintptr
    41  	Kind OffsetType
    42  	Type reflect.Type
    43  }
    44  
    45  type FieldMeta struct {
    46  	Name string
    47  	Path []Offset
    48  	Opts FieldOpts
    49  	Type reflect.Type
    50  }
    51  
    52  func (self *FieldMeta) String() string {
    53  	var path []string
    54  	var opts []string
    55  
    56  	/* dump the field path */
    57  	for _, off := range self.Path {
    58  		if off.Kind == F_offset {
    59  			path = append(path, fmt.Sprintf("%d", off.Size))
    60  		} else {
    61  			path = append(path, fmt.Sprintf("%d.(*%s)", off.Size, off.Type))
    62  		}
    63  	}
    64  
    65  	/* check for "string" */
    66  	if (self.Opts & F_stringize) != 0 {
    67  		opts = append(opts, "string")
    68  	}
    69  
    70  	/* check for "omitempty" */
    71  	if (self.Opts & F_omitempty) != 0 {
    72  		opts = append(opts, "omitempty")
    73  	}
    74  
    75  	/* format the field */
    76  	return fmt.Sprintf(
    77  		"{Field \"%s\" @ %s, opts=%s, type=%s}",
    78  		self.Name,
    79  		strings.Join(path, "."),
    80  		strings.Join(opts, ","),
    81  		self.Type,
    82  	)
    83  }
    84  
    85  func (self *FieldMeta) optimize() {
    86  	var n int
    87  	var v uintptr
    88  
    89  	/* merge adjacent offsets */
    90  	for _, o := range self.Path {
    91  		if v += o.Size; o.Kind == F_deref {
    92  			self.Path[n].Size = v
    93  			self.Path[n].Type, v = o.Type, 0
    94  			self.Path[n].Kind, n = F_deref, n+1
    95  		}
    96  	}
    97  
    98  	/* last offset value */
    99  	if v != 0 {
   100  		self.Path[n].Size = v
   101  		self.Path[n].Type = nil
   102  		self.Path[n].Kind = F_offset
   103  		n++
   104  	}
   105  
   106  	/* must be at least 1 offset */
   107  	if n != 0 {
   108  		self.Path = self.Path[:n]
   109  	} else {
   110  		self.Path = []Offset{{Kind: F_offset}}
   111  	}
   112  }
   113  
   114  func resolveFields(vt reflect.Type) []FieldMeta {
   115  	tfv := typeFields(vt)
   116  	ret := []FieldMeta(nil)
   117  
   118  	/* convert each field */
   119  	for _, fv := range tfv.list {
   120  		item := vt
   121  		path := []Offset(nil)
   122  		opts := FieldOpts(0)
   123  
   124  		/* check for "string" */
   125  		if fv.quoted {
   126  			opts |= F_stringize
   127  		}
   128  
   129  		/* check for "omitempty" */
   130  		if fv.omitEmpty {
   131  			opts |= F_omitempty
   132  		}
   133  
   134  		/* dump the field path */
   135  		for _, i := range fv.index {
   136  			kind := F_offset
   137  			fval := item.Field(i)
   138  			item = fval.Type
   139  
   140  			/* deref the pointer if needed */
   141  			if item.Kind() == reflect.Ptr {
   142  				kind = F_deref
   143  				item = item.Elem()
   144  			}
   145  
   146  			/* add to path */
   147  			path = append(path, Offset{
   148  				Kind: kind,
   149  				Type: item,
   150  				Size: fval.Offset,
   151  			})
   152  		}
   153  
   154  		/* get the index to the last offset */
   155  		idx := len(path) - 1
   156  		fvt := path[idx].Type
   157  
   158  		/* do not dereference into fields */
   159  		if path[idx].Kind == F_deref {
   160  			fvt = reflect.PtrTo(fvt)
   161  			path[idx].Kind = F_offset
   162  		}
   163  
   164  		/* add to result */
   165  		ret = append(ret, FieldMeta{
   166  			Type: fvt,
   167  			Opts: opts,
   168  			Path: path,
   169  			Name: fv.name,
   170  		})
   171  	}
   172  
   173  	/* optimize the offsets */
   174  	for i := range ret {
   175  		ret[i].optimize()
   176  	}
   177  
   178  	/* all done */
   179  	return ret
   180  }
   181  
   182  var (
   183  	fieldLock  = sync.RWMutex{}
   184  	fieldCache = map[reflect.Type][]FieldMeta{}
   185  )
   186  
   187  func ResolveStruct(vt reflect.Type) []FieldMeta {
   188  	var ok bool
   189  	var fm []FieldMeta
   190  
   191  	/* attempt to read from cache */
   192  	fieldLock.RLock()
   193  	fm, ok = fieldCache[vt]
   194  	fieldLock.RUnlock()
   195  
   196  	/* check if it was cached */
   197  	if ok {
   198  		return fm
   199  	}
   200  
   201  	/* otherwise use write-lock */
   202  	fieldLock.Lock()
   203  	defer fieldLock.Unlock()
   204  
   205  	/* double check */
   206  	if fm, ok = fieldCache[vt]; ok {
   207  		return fm
   208  	}
   209  
   210  	/* resolve the field */
   211  	fm = resolveFields(vt)
   212  	fieldCache[vt] = fm
   213  	return fm
   214  }