github.com/bytedance/sonic@v1.11.7-0.20240517092252-d2edb31b167b/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  }