package toml import ( "encoding" "errors" "fmt" "io" "math" "reflect" "strconv" "strings" "sync" "time" "github.com/pelletier/go-toml/v2/internal/tracker" "github.com/pelletier/go-toml/v2/unstable" ) // decoderPool recycles decoders (and their internal buffers: parser arena, // seen-tracker entries, scratch buffers) across calls to Unmarshal and // Decode. var decoderPool = sync.Pool{ New: func() interface{} { return &decoder{} }, } func getDecoder(strictMode, unmarshalerInterface bool) *decoder { d := decoderPool.Get().(*decoder) d.reset() d.strict.Enabled = strictMode d.unmarshalerInterface = unmarshalerInterface return d } func putDecoder(d *decoder) { decoderPool.Put(d) } // reset clears the per-document state of the decoder, keeping the allocated // buffers for reuse. func (d *decoder) reset() { d.seen.Reset() d.tableKey = d.tableKey[:0] d.skipUntilTable = false d.path = d.path[:0] d.captures = d.captures[:0] d.captureIdx = -1 d.segIdx = d.segIdx[:0] // Reuse the array-table counter slots across documents instead of // deleting them: a zeroed slot is indistinguishable from an absent one, // and keeping it alive means setArrayCount does not have to allocate a new // *int every time the same path reappears. A safety valve bounds the table // for adversarial inputs that introduce unboundedly many distinct paths. if len(d.arrayCounts) > 1<<14 { d.arrayCounts = nil } else { for _, p := range d.arrayCounts { *p = 0 } } d.tableTarget = reflect.Value{} d.tableTargetValid = false d.tableFlush = d.tableFlush[:0] d.tableParentSlot = slotWriter{} d.keyParts = d.keyParts[:0] d.strict.Reset() } // Unmarshal deserializes a TOML document into a Go value. // // It is a shortcut for Decoder.Decode() with the default options. func Unmarshal(data []byte, v interface{}) error { d := getDecoder(false, false) err := d.unmarshal(data, v) putDecoder(d) return err } // Decoder reads and decode a TOML document from an input stream. type Decoder struct { // input r io.Reader // global settings strict bool // toggles unmarshaler interface unmarshalerInterface bool } // NewDecoder creates a new Decoder that will read from r. func NewDecoder(r io.Reader) *Decoder { return &Decoder{r: r} } // DisallowUnknownFields causes the Decoder to return an error when the // destination is a struct and the input contains a key that does not match a // non-ignored field. // // In that case, the Decoder returns a StrictMissingError that can be used to // retrieve the individual errors as well as generate a human readable // description of the missing fields. func (d *Decoder) DisallowUnknownFields() *Decoder { d.strict = true return d } // EnableUnmarshalerInterface allows to enable unmarshaler interface. // // With this feature enabled, types implementing the unstable.Unmarshaler // interface can be decoded from any structure of the document. It allows types // that don't have a straightforward TOML representation to provide their own // decoding logic. // // The UnmarshalTOML method receives raw TOML bytes: // - For single values: the raw value bytes (e.g., `"hello"` for a string) // - For tables: all key-value lines belonging to that table // - For inline tables/arrays: the raw bytes of the inline structure // // The unstable.RawMessage type can be used to capture raw TOML bytes for // later processing, similar to json.RawMessage. // // *Unstable:* This method does not follow the compatibility guarantees of // semver. It can be changed or removed without a new major version being // issued. func (d *Decoder) EnableUnmarshalerInterface() *Decoder { d.unmarshalerInterface = true return d } // Decode the whole content of r into v. // // By default, values in the document that don't exist in the target Go value // are ignored. See Decoder.DisallowUnknownFields() to change this behavior. // // When a TOML local date, time, or date-time is decoded into a time.Time, its // value is represented in time.Local timezone. Otherwise the appropriate Local* // structure is used. For time values, precision up to the nanosecond is // supported by truncating extra digits. // // Empty tables decoded in an interface{} create an empty initialized // map[string]interface{}. // // Types implementing the encoding.TextUnmarshaler interface are decoded from a // TOML string. // // When decoding a number, go-toml will return an error if the number is out of // bounds for the target type (which includes negative numbers when decoding // into an unsigned int). // // If an error occurs while decoding the content of the document, this function // returns a toml.DecodeError, providing context about the issue. When using // strict mode and a field is missing, a `toml.StrictMissingError` is // returned. In any other case, this function returns a standard Go error. // // # Type mapping // // List of supported TOML types and their associated accepted Go types: // // String -> string // Integer -> uint*, int*, depending on size // Float -> float*, depending on size // Boolean -> bool // Offset Date-Time -> time.Time // Local Date-time -> LocalDateTime, time.Time // Local Date -> LocalDate, time.Time // Local Time -> LocalTime, time.Time // Array -> slice and array, depending on elements types // Table -> map and struct // Inline Table -> same as Table // Array of Tables -> same as Array and Table func (d *Decoder) Decode(v interface{}) error { b, err := io.ReadAll(d.r) if err != nil { return fmt.Errorf("toml: %w", err) } dec := getDecoder(d.strict, d.unmarshalerInterface) err = dec.unmarshal(b, v) putDecoder(dec) return err } // pathPart is one part of the key path leading to a value. Parts that come // from the current table header only carry a name; parts that come from the // key of the current key-value expression also carry the AST node, and their // name is materialized lazily to avoid allocations. type pathPart struct { name string node *unstable.Node } // bytes returns the raw bytes of the key part. func (p *pathPart) bytes() []byte { if p.node != nil { return p.node.Data } return []byte(p.name) } // str returns the key part as a string, possibly allocating. func (p *pathPart) str() string { if p.node != nil { return string(p.node.Data) } return p.name } // rawCapture accumulates the raw bytes fed to a type implementing // unstable.Unmarshaler for a table target. The target is identified by the // parts of its key and the array-table indexes in effect when the capture // was created, so that it can be located again once the whole document has // been processed (the address of the target may change as slices grow). type rawCapture struct { names []string // indexes[i] is the index to use when reaching a slice or array right // before consuming names[i]. indexes[len(names)] is the index of the // element when the target is an element of an array table. -1 when not // relevant. indexes []int buf []byte } type decoder struct { p unstable.Parser // strict mode strict strict // toggles unmarshaler interface unmarshalerInterface bool // tracks the duplicate and type consistency of the keys seen tracker.SeenTracker // path of the current table header, as copied strings tableKey []string // true when the expressions under the current table header cannot be // stored anywhere and should be skipped skipUntilTable bool // scratch buffer for the key path of the current expression path []pathPart // raw captures for the unmarshaler interface, in order of first // appearance. captureIdx is the index of the capture the current table // belongs to, or -1. captures []rawCapture captureIdx int // segIdx[i] records the array element index used when traversing a // slice or array right before consuming the i-th part of the current // table key. Reset for each table expression. segIdx []int // arrayCounts tracks the number of elements appended to fixed-size // arrays used as array tables, keyed by the NUL-joined key parts. // Values are pointer slots so that updating an existing path does not // allocate a new key string. arrayCounts map[string]*int // Cached target of the current table, so that key-values do not need to // walk the document structure from the root for every expression. // tableFlush holds the write-backs to perform when leaving the table // (for targets reached through map values, which are copies). // tableParentSlot stores a replacement of the target itself (e.g. a nil // map that was allocated) into its parent. tableTarget reflect.Value tableTargetValid bool tableFlush []flushOp tableParentSlot slotWriter // strKey is a reusable string value used as map key, so that map // operations with string keys do not need to allocate a boxed key for // every access. It must be refreshed with stringMapKey immediately // before each use: any recursive call may overwrite it. strKey reflect.Value // interned de-duplicates key strings: documents repeat the same keys // over and over, and the table survives pooling, so repeated decodes // of similar documents stop allocating key strings altogether. interned map[string]string // pathScratch is the buffer used by joinPath. pathScratch []byte // keyParts is the reusable buffer holding the decoded parts of the key of // the current expression in the fused generic decode path. keyParts [][]byte } // slotWriter remembers how to store a value at some location of the target // structure. Implemented as a struct instead of a closure to avoid // allocations. type slotWriter struct { kind uint8 // 0: none, 1: slot.Set, 2: m.SetMapIndex(k, ...), 3: m.SetMapIndex(string key ks, ...) slot reflect.Value m reflect.Value k reflect.Value ks string } func (d *decoder) storeSlot(s *slotWriter, nv reflect.Value) { switch s.kind { case 1: if s.slot.CanSet() { s.slot.Set(nv) } case 2: s.m.SetMapIndex(s.k, nv) case 3: s.m.SetMapIndex(d.stringMapKey(s.ks), nv) } } // flushOp stores val using w when the table is flushed. type flushOp struct { w slotWriter val reflect.Value } // flushTable performs the pending write-backs of the cached table target, in // reverse order so that inner copies land before their parents are stored. func (d *decoder) flushTable() { for i := len(d.tableFlush) - 1; i >= 0; i-- { d.storeSlot(&d.tableFlush[i].w, d.tableFlush[i].val) } d.tableFlush = d.tableFlush[:0] d.tableTargetValid = false d.tableParentSlot = slotWriter{} d.tableTarget = reflect.Value{} } // intern returns the string corresponding to the given bytes, reusing a // previous allocation when the same key has been seen before. func (d *decoder) intern(b []byte) string { if s, ok := d.interned[string(b)]; ok { // does not allocate return s } if d.interned == nil { d.interned = make(map[string]string, 64) } else if len(d.interned) >= 1<<14 { // Safety valve for adversarial inputs: do not let the table grow // without bounds. for k := range d.interned { delete(d.interned, k) } } s := string(b) d.interned[s] = s return s } // partString returns the name of a path part, interning it when it comes // from the document. func (d *decoder) partString(p *pathPart) string { if p.node != nil { return d.intern(p.node.Data) } return p.name } // stringMapKey returns a reflect.Value holding the given string, reusing the // same allocation every time. The result must be used (the map operation // performed) before any recursive call, which may overwrite the buffer. func (d *decoder) stringMapKey(s string) reflect.Value { if !d.strKey.IsValid() { d.strKey = reflect.New(stringType).Elem() } d.strKey.SetString(s) return d.strKey } // joinPath builds the NUL-joined representation of a key path in the // decoder's scratch buffer. The result is only valid until the next call. func (d *decoder) joinPath(parts []string) []byte { d.pathScratch = d.pathScratch[:0] for i, p := range parts { if i > 0 { d.pathScratch = append(d.pathScratch, 0) } d.pathScratch = append(d.pathScratch, p...) } return d.pathScratch } // arrayCount returns the number of elements appended so far to the array // table at the given path. func (d *decoder) arrayCount(key []byte) int { if d.arrayCounts == nil { return 0 } if p := d.arrayCounts[string(key)]; p != nil { // does not allocate return *p } return 0 } func (d *decoder) setArrayCount(key []byte, n int) { if d.arrayCounts == nil { d.arrayCounts = map[string]*int{} } if p := d.arrayCounts[string(key)]; p != nil { // does not allocate *p = n return } v := n d.arrayCounts[string(key)] = &v } // resetChildArrayCounts forgets the counts of all the array tables under // the given path, so that a new element starts fresh. func (d *decoder) resetChildArrayCounts(key []byte) { if len(d.arrayCounts) == 0 { return } for k, p := range d.arrayCounts { // Prefix match without building the prefix string: same bytes as // key, followed by the NUL separator. if len(k) > len(key) && k[len(key)] == 0 && k[:len(key)] == string(key) { // Zero instead of delete: the next element of the parent table // will reuse the slot without allocating a new key. *p = 0 } } } func (d *decoder) typeMismatchError(toml string, target reflect.Type, highlight []byte) error { return &typeMismatchError{ toml: toml, target: target, highlight: highlight, } } type typeMismatchError struct { toml string target reflect.Type highlight []byte // key is the TOML key being processed when the mismatch occurred. It is // populated lazily as the error propagates back up to the key-value // handler (see contextualizeError). key Key } func (e *typeMismatchError) Error() string { return fmt.Sprintf("cannot decode TOML %s into %s", e.toml, e.target) } // contextualizeError attaches the TOML key currently being processed to errors // raised while decoding a key-value expression, so that DecodeError.Key() // reports the offending key (e.g. on type mismatch errors). The current key is // reconstructed from d.path; when the table target is cached, d.path holds only // the key-value parts, so the table key prefix is prepended. This only runs on // the error path and adds no cost to successful decodes. func (d *decoder) contextualizeError(err error, withTableKey bool) error { var mm *typeMismatchError if errors.As(err, &mm) { if mm.key == nil { mm.key = d.currentKey(withTableKey) } return err } var perr *unstable.ParserError if errors.As(err, &perr) { if perr.Key == nil { perr.Key = d.currentKey(withTableKey) } } return err } // currentKey reconstructs the full TOML key being processed from the decoder's // path. When withTableKey is true, d.path contains only the key-value parts // (the table target is cached) and the table key is prepended. func (d *decoder) currentKey(withTableKey bool) Key { n := len(d.path) if withTableKey { n += len(d.tableKey) } key := make(Key, 0, n) if withTableKey { key = append(key, d.tableKey...) } for i := range d.path { key = append(key, d.path[i].str()) } return key } func (d *decoder) unmarshal(data []byte, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) } if r.IsNil() { return errors.New("toml: decoding pointer target cannot be nil") } root := r.Elem() d.captureIdx = -1 d.p.Reset(data) // Fully generic targets (interface{} or map[string]interface{}) are // decoded straight into native Go maps and slices, with no reflection on // the document structure at all. This covers the common "decode arbitrary // TOML into a map" case, including every standard benchmark dataset. if !d.unmarshalerInterface { if k := root.Kind(); k == reflect.Interface || (k == reflect.Map && root.Type() == mapStringInterfaceType) { return d.unmarshalFused(root, data) } } // When the root target itself implements the Unmarshaler interface, the // whole document decodes into it. Open a capture spanning the entire // document up front: the top-level key-values then flow through the // existing capture branch and any tables attach to it via resumeCapture, // so UnmarshalTOML receives the assembled document exactly once. if d.unmarshalerInterface && hasUnmarshaler(root) { d.startRootCapture() } for d.p.NextExpression() { err := d.handleRootExpression(d.p.Expression(), root) if err != nil { return d.wrapError(data, err) } } if err := d.p.Error(); err != nil { var perr *unstable.ParserError if errors.As(err, &perr) { return wrapDecodeError(data, perr) } return err } d.flushTable() // Deliver the accumulated raw documents to the unmarshaler-interface // targets. for i := range d.captures { nv, err := d.resolveCapture(root, &d.captures[i], 0, false) if err != nil { return err } if nv.IsValid() { root.Set(nv) } } // An empty document into a generic target still initializes it. switch root.Kind() { case reflect.Map: if root.IsNil() { root.Set(reflect.MakeMap(root.Type())) } case reflect.Interface: if root.IsNil() { root.Set(reflect.ValueOf(map[string]interface{}{})) } default: } return d.strict.Error(data) } // setAnyKey assigns the value of a key-value into the native map m, following // the (possibly dotted) key and creating intermediate maps as needed. func (d *decoder) setAnyKey(m map[string]interface{}, key unstable.Iterator, value *unstable.Node) error { cur := m for key.Next() { name := d.intern(key.Node().Data) if key.IsLast() { av, err := d.decodeAny(value) if err != nil { return err } cur[name] = av return nil } cur = d.anyChildTable(cur, name) } return nil } // anyChildTable returns the child table at name within cur, creating it if // absent and descending into the current (last) element when an array table // occupies the slot. A non-container in the slot cannot occur for a document // the seen-tracker has accepted. func (d *decoder) anyChildTable(cur map[string]interface{}, name string) map[string]interface{} { switch v := cur[name].(type) { case map[string]interface{}: return v case []interface{}: if len(v) > 0 { if last, ok := v[len(v)-1].(map[string]interface{}); ok { return last } } } nm := map[string]interface{}{} cur[name] = nm return nm } // wrapError gives document context to errors generated while processing an // expression. func (d *decoder) wrapError(data []byte, err error) error { var perr *unstable.ParserError if errors.As(err, &perr) { return wrapDecodeError(data, perr) } var mm *typeMismatchError if errors.As(err, &mm) { return wrapDecodeError(data, &unstable.ParserError{ Highlight: mm.highlight, Message: mm.Error(), Key: mm.key, }) } return err } // keyedError turns a bare error raised while processing an expression into a // ParserError carrying the position and key of that expression, so that it is // reported as a DecodeError with context. It is used for the errors returned // by SeenTracker.CheckExpression (redefinitions and duplicate keys, see issue // #668) and for table placement errors raised by walkTable (see issue #806). // // The highlight spans the expression's key. Unlike Node.Raw, key nodes always // carry a Raw range, so this works for tables and array tables too (whose own // Raw range is not set by the parser). For a duplicate detected inside an // inline table, node is the enclosing key-value expression, so the error // points at that expression's key. func (d *decoder) keyedError(node *unstable.Node, err error) error { if err == nil { return nil } var key Key var start, end unstable.Range it := node.Key() for it.Next() { n := it.Node() key = append(key, string(n.Data)) if len(key) == 1 { start = n.Raw } end = n.Raw } var highlight []byte if len(key) > 0 { highlight = d.p.Raw(unstable.Range{ Offset: start.Offset, Length: end.Offset + end.Length - start.Offset, }) } return &unstable.ParserError{ Highlight: highlight, Message: strings.TrimPrefix(err.Error(), "toml: "), Key: key, } } func (d *decoder) handleRootExpression(expr *unstable.Node, root reflect.Value) error { first, err := d.seen.CheckExpression(expr) if err != nil { return d.keyedError(expr, err) } switch expr.Kind { case unstable.KeyValue: if d.skipUntilTable { return nil } if d.captureIdx >= 0 { d.captureKeyValue(expr) return nil } return d.handleKeyValueExpression(expr, root) case unstable.Table: d.flushTable() d.skipUntilTable = false d.captureIdx = -1 d.strict.EnterTable(expr) return d.handleTableExpression(expr, root, false, first) case unstable.ArrayTable: d.flushTable() d.skipUntilTable = false d.captureIdx = -1 d.strict.EnterTable(expr) return d.handleTableExpression(expr, root, true, first) default: return unstable.NewParserError(expr.Data, "unsupported expression kind %s", expr.Kind) } } // updateTableKey copies the parts of the key of a table expression into // tableKey. func (d *decoder) updateTableKey(expr *unstable.Node) { d.tableKey = d.tableKey[:0] it := expr.Key() for it.Next() { d.tableKey = append(d.tableKey, d.intern(it.Node().Data)) } } func (d *decoder) handleTableExpression(expr *unstable.Node, root reflect.Value, isArrayTable bool, first bool) error { d.updateTableKey(expr) // Check whether this table belongs to an exisiting raw capture (split // tables, or children of a table assigned to an Unmarshaler). if d.unmarshalerInterface { if d.resumeCapture(expr) { return nil } } // Reset the per-segment array indexes. d.segIdx = d.segIdx[:0] for i := 0; i <= len(d.tableKey); i++ { d.segIdx = append(d.segIdx, -1) } return d.walkTable(root, expr, isArrayTable, first) } // newContainerElem returns a fresh element for a slice of the given element // type. Plain interface elements start out as an empty table. func newContainerElem(et reflect.Type) reflect.Value { if et == interfaceType { return reflect.ValueOf(map[string]interface{}{}) } return reflect.New(et).Elem() } // walkTable processes a [table] or [[array table]] header: it creates the // intermediate containers, appends array-table elements, applies the strict // policy, registers unmarshaler-interface captures, and caches the target // container so that the key-values that follow are stored directly. // // Map values are not addressable: when one needs in-place mutations (struct // or array values), a copy is made and registered to be stored back when the // table changes (see flushTable). Maps and slices are references and are // traversed without copies. func (d *decoder) walkTable(root reflect.Value, expr *unstable.Node, isArrayTable bool, first bool) error { v := root pf := slotWriter{kind: 1, slot: root} idx := 0 walk: for { // Dereference pointers in place. for v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } elem := v.Elem() pf = slotWriter{kind: 1, slot: elem} v = elem } // Tables assigned to a type implementing the unmarshaler interface // are captured as raw bytes, delivered once the document is read. if d.unmarshalerInterface && hasUnmarshaler(v) { d.startCapture(idx, expr) return nil } if idx >= len(d.tableKey) { break walk } name := d.tableKey[idx] switch v.Kind() { case reflect.Interface: if !v.IsNil() { c := v.Elem() if k := c.Kind(); k == reflect.Map || k == reflect.Slice { // Reference types: mutations are visible through the // existing interface value. v = c continue } } // Anything else is replaced by a fresh generic map. if !mapStringInterfaceType.AssignableTo(v.Type()) { return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Type())) } fresh := reflect.ValueOf(map[string]interface{}{}) d.storeSlot(&pf, fresh) v = fresh case reflect.Slice: if v.Len() == 0 { // Implicit creation of the first element: the array table // that would create it has not been seen yet (issue 995). if v.IsNil() { v = reflect.MakeSlice(v.Type(), 0, 4) } v = reflect.Append(v, newContainerElem(v.Type().Elem())) d.storeSlot(&pf, v) } n := v.Len() - 1 d.segIdx[idx] = n elem := v.Index(n) pf = slotWriter{kind: 1, slot: elem} v = elem case reflect.Array: key := d.joinPath(d.tableKey[:idx]) cnt := d.arrayCount(key) if cnt == 0 { cnt = 1 d.setArrayCount(key, 1) } if cnt > v.Len() { return d.keyedError(expr, fmt.Errorf("cannot reach element %d of array of size %d", cnt-1, v.Len())) } d.segIdx[idx] = cnt - 1 elem := v.Index(cnt - 1) pf = slotWriter{kind: 1, slot: elem} v = elem case reflect.Map: if v.IsNil() { nm := reflect.MakeMap(v.Type()) d.storeSlot(&pf, nm) v = nm } var key reflect.Value var w slotWriter if v.Type().Key() == stringType { key = d.stringMapKey(name) w = slotWriter{kind: 3, m: v, ks: name} } else { k, err := makeMapKey(v.Type().Key(), name) if err != nil { return err } key = k w = slotWriter{kind: 2, m: v, k: k} } elem := v.MapIndex(key) // The last part of an array table is finalized as a slice // container: do not materialize a table for it. if isArrayTable && idx == len(d.tableKey)-1 { et := v.Type().Elem() switch et.Kind() { case reflect.Interface, reflect.Slice, reflect.Array: if elem.IsValid() { v = elem } else { v = reflect.Zero(et) } pf = w idx++ continue default: } } if elem.IsValid() { ce := elem ceIface := false if ce.Kind() == reflect.Interface { ceIface = true if !ce.IsNil() { ce = ce.Elem() } } switch ce.Kind() { case reflect.Map, reflect.Slice: pf = w v = ce case reflect.Ptr: if ce.IsNil() { np := reflect.New(ce.Type().Elem()) d.storeSlot(&w, np) ce = np } pf = w v = ce case reflect.Struct, reflect.Array: if ceIface { // Interface-held non-generic content is replaced. fresh := reflect.ValueOf(map[string]interface{}{}) d.storeSlot(&w, fresh) pf = w v = fresh } else { tmp := reflect.New(elem.Type()).Elem() tmp.Set(elem) d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp}) pf = slotWriter{kind: 1, slot: tmp} v = tmp } default: if !ceIface { return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", ce.Type())) } fresh := reflect.ValueOf(map[string]interface{}{}) d.storeSlot(&w, fresh) pf = w v = fresh } } else { et := v.Type().Elem() switch et.Kind() { case reflect.Interface: if !mapStringInterfaceType.AssignableTo(et) { return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", et)) } fresh := reflect.ValueOf(map[string]interface{}{}) d.storeSlot(&w, fresh) pf = w v = fresh case reflect.Map: nm := reflect.MakeMap(et) d.storeSlot(&w, nm) pf = w v = nm case reflect.Ptr: np := reflect.New(et.Elem()) d.storeSlot(&w, np) pf = w v = np case reflect.Struct, reflect.Array, reflect.Slice: tmp := reflect.New(et).Elem() d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp}) pf = slotWriter{kind: 1, slot: tmp} v = tmp default: return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", et)) } } idx++ case reflect.Struct: plan := planForType(v.Type()) f, found := plan.lookup(name) if !found { d.strict.MissingTable(expr) d.skipUntilTable = true return nil } fv, err := fieldByIndexAlloc(v, f.index) if err != nil { return err } pf = slotWriter{kind: 1, slot: fv} v = fv idx++ default: return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Kind())) } } if isArrayTable { akey := d.joinPath(d.tableKey) d.resetChildArrayCounts(akey) // Unwrap an interface container. if v.Kind() == reflect.Interface { var slice []interface{} if !v.IsNil() { if s, ok := v.Elem().Interface().([]interface{}); ok { slice = s } } if first { slice = slice[:0] } m := map[string]interface{}{} slice = append(slice, m) sv := reflect.ValueOf(slice) d.storeSlot(&pf, sv) d.setArrayCount(akey, len(slice)) d.segIdx[len(d.tableKey)] = len(slice) - 1 d.tableTarget = reflect.ValueOf(m) d.tableParentSlot = slotWriter{kind: 1, slot: sv.Index(len(slice) - 1)} d.tableTargetValid = true return nil } switch v.Kind() { case reflect.Slice: if v.IsNil() { v = reflect.MakeSlice(v.Type(), 0, 4) } else if first { v = v.Slice(0, 0) } v = reflect.Append(v, newContainerElem(v.Type().Elem())) d.storeSlot(&pf, v) n := v.Len() - 1 d.setArrayCount(akey, n+1) d.segIdx[len(d.tableKey)] = n elem := v.Index(n) if d.unmarshalerInterface && hasUnmarshaler(elem) { d.startCapture(len(d.tableKey), expr) return nil } pf = slotWriter{kind: 1, slot: elem} v = elem case reflect.Array: cnt := d.arrayCount(akey) if first { cnt = 0 } if cnt >= v.Len() { return d.keyedError(expr, fmt.Errorf("array of size %d is too small to store this array table", v.Len())) } v.Index(cnt).Set(reflect.Zero(v.Type().Elem())) d.setArrayCount(akey, cnt+1) d.segIdx[len(d.tableKey)] = cnt elem := v.Index(cnt) if d.unmarshalerInterface && hasUnmarshaler(elem) { d.startCapture(len(d.tableKey), expr) return nil } pf = slotWriter{kind: 1, slot: elem} v = elem default: return d.keyedError(expr, fmt.Errorf("cannot store an array table in a %s", v.Kind())) } } // Settle on the concrete container for the key-values that follow. for { switch v.Kind() { case reflect.Ptr: if v.IsNil() { if !v.CanSet() { return nil } v.Set(reflect.New(v.Type().Elem())) } elem := v.Elem() pf = slotWriter{kind: 1, slot: elem} v = elem continue case reflect.Interface: if !v.IsNil() { c := v.Elem() if c.Type() == mapStringInterfaceType || c.Type() == sliceInterfaceType { v = c continue } } if !mapStringInterfaceType.AssignableTo(v.Type()) { return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Type())) } fresh := reflect.ValueOf(map[string]interface{}{}) d.storeSlot(&pf, fresh) v = fresh continue case reflect.Slice: if v.Len() == 0 { if v.IsNil() { v = reflect.MakeSlice(v.Type(), 0, 4) } v = reflect.Append(v, newContainerElem(v.Type().Elem())) d.storeSlot(&pf, v) } n := v.Len() - 1 d.segIdx[len(d.tableKey)] = n elem := v.Index(n) pf = slotWriter{kind: 1, slot: elem} v = elem continue case reflect.Map, reflect.Struct: d.tableTarget = v d.tableParentSlot = pf d.tableTargetValid = true return nil default: return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Kind())) } } } // resumeCapture looks for an existing capture this table expression belongs // to. It returns true if the expression was consumed. func (d *decoder) resumeCapture(expr *unstable.Node) bool { // Iterate in reverse, so that tables attach to the latest element of // array tables. for i := len(d.captures) - 1; i >= 0; i-- { c := &d.captures[i] if len(d.tableKey) < len(c.names) { continue } if expr.Kind == unstable.ArrayTable && len(d.tableKey) == len(c.names) { // A new element of an array table is not part of the capture of // the previous element. continue } match := true for j, p := range c.names { if d.tableKey[j] != p { match = false break } } if !match { continue } d.captureIdx = i if len(d.tableKey) > len(c.names) { d.appendCaptureHeader(c, expr, len(c.names)) } return true } return false } // appendCaptureHeader writes the table header of expr in the capture buffer, // adjusted to be relative to the capture root. func (d *decoder) appendCaptureHeader(c *rawCapture, expr *unstable.Node, skip int) { c.buf = append(c.buf, '[') if expr.Kind == unstable.ArrayTable { c.buf = append(c.buf, '[') } c.buf = append(c.buf, d.rawKeySuffix(expr, skip)...) c.buf = append(c.buf, ']') if expr.Kind == unstable.ArrayTable { c.buf = append(c.buf, ']') } c.buf = append(c.buf, '\n') } // rawKeySuffix returns the raw bytes of the key of the expression, skipping // the first n parts. func (d *decoder) rawKeySuffix(expr *unstable.Node, n int) []byte { it := expr.Key() idx := 0 var start, end unstable.Range for it.Next() { if idx >= n { r := it.Node().Raw if start.Length == 0 && start.Offset == 0 && idx == n { start = r } end = r } idx++ } return d.p.Data()[start.Offset : end.Offset+end.Length] } // startCapture registers a new capture for the table at the given path // (prefix of tableKey). func (d *decoder) startCapture(pathLen int, expr *unstable.Node) { names := make([]string, pathLen) copy(names, d.tableKey[:pathLen]) indexes := make([]int, pathLen+1) copy(indexes, d.segIdx[:pathLen+1]) d.captures = append(d.captures, rawCapture{ names: names, indexes: indexes, }) d.captureIdx = len(d.captures) - 1 if pathLen < len(d.tableKey) { d.appendCaptureHeader(&d.captures[d.captureIdx], expr, pathLen) } } // startRootCapture opens a capture covering the entire document, used when the // root target itself implements the Unmarshaler interface. It is opened before // reading any expression: top-level key-values are then accumulated through the // regular capture branch, and its empty name path matches every table in // resumeCapture, so the whole document is handed to UnmarshalTOML once. The // single index slot is -1 because the root is never reached through an array // table. func (d *decoder) startRootCapture() { d.captures = append(d.captures, rawCapture{indexes: []int{-1}}) d.captureIdx = len(d.captures) - 1 } // resolveCapture walks back to the target of a capture and delivers the // accumulated raw bytes to its UnmarshalTOML implementation. func (d *decoder) resolveCapture(v reflect.Value, c *rawCapture, idx int, indexed bool) (reflect.Value, error) { if v.Kind() == reflect.Ptr { if v.Type().Implements(unmarshalerType) && idx == len(c.names) { u, _ := unmarshalerOf(v) return v, u.UnmarshalTOML(c.buf) } if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } nv, err := d.resolveCapture(v.Elem(), c, idx, indexed) if err != nil { return reflect.Value{}, err } if nv.IsValid() { v.Elem().Set(nv) } return v, nil } if !indexed && (v.Kind() == reflect.Slice || v.Kind() == reflect.Array) && c.indexes[idx] >= 0 { i := c.indexes[idx] if i >= v.Len() { return reflect.Value{}, errors.New("toml: internal error: capture index out of range") } elem := v.Index(i) nv, err := d.resolveCapture(elem, c, idx, true) if err != nil { return reflect.Value{}, err } if nv.IsValid() { elem.Set(nv) } return v, nil } if idx == len(c.names) { u, ok := unmarshalerOf(v) if !ok { return reflect.Value{}, errors.New("toml: internal error: capture target does not implement UnmarshalTOML") } return v, u.UnmarshalTOML(c.buf) } name := c.names[idx] switch v.Kind() { case reflect.Struct: plan := planForType(v.Type()) f, found := plan.lookup(name) if !found { return v, nil } fv, err := fieldByIndexAlloc(v, f.index) if err != nil { return reflect.Value{}, err } nv, err := d.resolveCapture(fv, c, idx+1, false) if err != nil { return reflect.Value{}, err } if nv.IsValid() && fv.CanSet() { fv.Set(nv) } return v, nil case reflect.Map: key, err := makeMapKey(v.Type().Key(), name) if err != nil { return reflect.Value{}, err } if v.IsNil() { v = reflect.MakeMap(v.Type()) } elem := reflect.New(v.Type().Elem()).Elem() if existing := v.MapIndex(key); existing.IsValid() { elem.Set(existing) } nv, err := d.resolveCapture(elem, c, idx+1, false) if err != nil { return reflect.Value{}, err } if nv.IsValid() { v.SetMapIndex(key, nv) } return v, nil case reflect.Interface: elem := elemOrNewMap(v) nv, err := d.resolveCapture(elem, c, idx, indexed) if err != nil || !nv.IsValid() { return reflect.Value{}, err } return nv, nil default: return reflect.Value{}, fmt.Errorf("toml: internal error: cannot resolve capture target through %s", v.Kind()) } } // captureKeyValue appends the raw bytes of a key-value expression to the // current capture. func (d *decoder) captureKeyValue(expr *unstable.Node) { c := &d.captures[d.captureIdx] c.buf = append(c.buf, d.p.Raw(expr.Raw)...) c.buf = append(c.buf, '\n') } // hasUnmarshaler reports whether v can provide an unstable.Unmarshaler, // without allocating anything. func hasUnmarshaler(v reflect.Value) bool { t := v.Type() return t.Implements(unmarshalerType) || (v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType)) } // makeMapKey converts a TOML key into a value usable as the given map key // type. func makeMapKey(kt reflect.Type, name string) (reflect.Value, error) { switch kt.Kind() { case reflect.String: return reflect.ValueOf(name).Convert(kt), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := strconv.ParseInt(name, 10, 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) } k := reflect.New(kt).Elem() if k.OverflowInt(i) { return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt) } k.SetInt(i) return k, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: u, err := strconv.ParseUint(name, 10, 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) } k := reflect.New(kt).Elem() if k.OverflowUint(u) { return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt) } k.SetUint(u) return k, nil case reflect.Float32, reflect.Float64: f, err := strconv.ParseFloat(name, 64) if err != nil { return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err) } k := reflect.New(kt).Elem() k.SetFloat(f) return k, nil case reflect.Ptr: if kt.Implements(textUnmarshalerType) { k := reflect.New(kt.Elem()) err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name)) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err) } return k, nil } default: if reflect.PtrTo(kt).Implements(textUnmarshalerType) { k := reflect.New(kt) err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name)) if err != nil { return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err) } return k.Elem(), nil } } return reflect.Value{}, fmt.Errorf("toml: cannot decode a key into a map with key type %s", kt) } // elemOrNewMap unwraps an interface value to descend into it. Contents that // can hold a table (generic maps and slices) are kept; anything else is // replaced by a fresh map[string]interface{}. Maps and slices are reference // types: they are returned directly, not copied. func elemOrNewMap(v reflect.Value) reflect.Value { if !v.IsNil() { concrete := v.Elem() t := concrete.Type() if t == mapStringInterfaceType || t == sliceInterfaceType { return concrete } } return reflect.ValueOf(map[string]interface{}{}) } // handleKeyValueExpression stores the value of a top-level key-value // expression, relative to the current table. func (d *decoder) handleKeyValueExpression(expr *unstable.Node, root reflect.Value) error { d.path = d.path[:0] target := root useCache := d.tableTargetValid && len(d.tableKey) > 0 if useCache { target = d.tableTarget } else { for _, name := range d.tableKey { d.path = append(d.path, pathPart{name: name}) } } it := expr.Key() for it.Next() { d.path = append(d.path, pathPart{node: it.Node()}) } nv, err := d.descend(target, d.path, 0, expr, expr.Value()) if err != nil { return d.contextualizeError(err, useCache) } if !nv.IsValid() { return nil } if useCache { // The target may have been replaced (e.g. a nil map allocated): // re-link it into its parent. if nv.Kind() == reflect.Map && nv.Pointer() != d.tableTarget.Pointer() { d.storeSlot(&d.tableParentSlot, nv) d.tableTarget = nv } } else { if root.CanSet() { root.Set(nv) } } return nil } // descend walks the given key path into v, and assigns the value at the // end. It returns the value to store back at this level. An invalid value // means nothing should be stored (e.g. unknown field). func (d *decoder) descend(v reflect.Value, path []pathPart, idx int, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { if idx == len(path) { return d.assignValue(v, expr, value) } if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } nv, err := d.descend(v.Elem(), path, idx, expr, value) if err != nil || !nv.IsValid() { return reflect.Value{}, err } v.Elem().Set(nv) return v, nil } // A target implementing the unmarshaler interface consumes the value, // whatever the remaining parts of the key are. if d.unmarshalerInterface { if u, ok := unmarshalerOf(v); ok { return v, u.UnmarshalTOML(d.rawValue(expr, value)) } } part := path[idx] switch v.Kind() { case reflect.Map: // Native fast path for the most common generic target: walk the // remaining dotted-key path with plain Go map operations and decode // the value directly, skipping the reflect.Value round-trips // (stringMapKey, MapIndex, New, SetMapIndex) entirely. if !d.unmarshalerInterface && v.Type() == mapStringInterfaceType { return d.descendStrMap(v, path, idx, value) } var name string var key reflect.Value var err error fastKey := v.Type().Key() == stringType if fastKey { name = d.partString(&part) key = d.stringMapKey(name) } else { key, err = makeMapKey(v.Type().Key(), d.partString(&part)) if err != nil { return reflect.Value{}, err } } if v.IsNil() { v = reflect.MakeMap(v.Type()) } elemType := v.Type().Elem() existing := v.MapIndex(key) var elem reflect.Value switch { case existing.IsValid(): elem = reflect.New(elemType).Elem() elem.Set(existing) case idx+1 == len(path) && elemType.Kind() == reflect.Interface: // Fast path: a fresh interface element does not need to be // materialized, the assigned value is stored directly. elem = reflect.Zero(elemType) default: elem = reflect.New(elemType).Elem() } nv, err := d.descend(elem, path, idx+1, expr, value) if err != nil { return reflect.Value{}, err } if nv.IsValid() { if fastKey { // The recursion may have overwritten the key buffer. key = d.stringMapKey(name) } v.SetMapIndex(key, nv) } return v, nil case reflect.Struct: plan := planForType(v.Type()) f, found := plan.lookupBytes(part.bytes()) if !found { if part.node != nil { d.strict.MissingField(expr) } return v, nil } fv, err := fieldByIndexAlloc(v, f.index) if err != nil { return reflect.Value{}, err } var nv reflect.Value if idx+1 == len(path) { // Leaf field: assign directly. descend's first action for a // fully-consumed path is exactly this call, so skipping the extra // frame is equivalent and avoids a call per scalar field. nv, err = d.assignValue(fv, expr, value) } else { nv, err = d.descend(fv, path, idx+1, expr, value) } if err != nil { var mm *typeMismatchError if errors.As(err, &mm) { err = &unstable.ParserError{ Highlight: mm.highlight, Message: fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", mm.toml, v.Type(), f.fieldName, mm.target), } } return reflect.Value{}, err } if nv.IsValid() && fv.CanSet() { fv.Set(nv) } return v, nil case reflect.Interface: elem := elemOrNewMap(v) nv, err := d.descend(elem, path, idx, expr, value) if err != nil || !nv.IsValid() { return reflect.Value{}, err } return nv, nil case reflect.Slice: if v.Len() == 0 { if v.IsNil() { v = reflect.MakeSlice(v.Type(), 0, 4) } v = reflect.Append(v, reflect.New(v.Type().Elem()).Elem()) } elem := v.Index(v.Len() - 1) nv, err := d.descend(elem, path, idx, expr, value) if err != nil { return reflect.Value{}, err } if nv.IsValid() { elem.Set(nv) } return v, nil case reflect.Array: names := make([]string, idx) for i := range names { names[i] = path[i].str() } cnt := d.arrayCount(d.joinPath(names)) if cnt == 0 { cnt = 1 } elemIdx := cnt - 1 if elemIdx >= v.Len() { return reflect.Value{}, unstable.NewParserError(keyHighlight(d.p.Data(), part.node), "cannot reach element %d of array of size %d", elemIdx, v.Len()) } elem := v.Index(elemIdx) nv, err := d.descend(elem, path, idx, expr, value) if err != nil { return reflect.Value{}, err } if nv.IsValid() { elem.Set(nv) } return v, nil default: return reflect.Value{}, d.typeMismatchError("table", v.Type(), keyHighlight(d.p.Data(), part.node)) } } // descendStrMap assigns into a native map[string]interface{} target, following // the remaining dotted-key parts with plain Go map operations and decoding the // value with decodeAny. It returns the map to store back at this level: a new // map when v was nil, otherwise v unchanged, since maps are reference types and // are mutated in place. func (d *decoder) descendStrMap(v reflect.Value, path []pathPart, idx int, value *unstable.Node) (reflect.Value, error) { var m map[string]interface{} if v.IsNil() { m = make(map[string]interface{}) v = reflect.ValueOf(m) } else { m = v.Interface().(map[string]interface{}) } // Walk intermediate parts, creating or reusing nested generic maps. A // non-map value at an intermediate key can only occur in a document the // seen-tracker has already rejected; replacing it mirrors the reflect // path (elemOrNewMap). for ; idx < len(path)-1; idx++ { name := d.partString(&path[idx]) child, _ := m[name].(map[string]interface{}) if child == nil { child = make(map[string]interface{}) m[name] = child } m = child } av, err := d.decodeAny(value) if err != nil { return reflect.Value{}, err } m[d.partString(&path[idx])] = av return v, nil } // keyHighlight returns a highlight for the given key part node, falling back // to the start of the document. func keyHighlight(doc []byte, node *unstable.Node) []byte { if node == nil { return doc[0:0] } return doc[node.Raw.Offset : node.Raw.Offset+node.Raw.Length] } // rawValue returns the raw bytes of the value of a key-value expression. func (d *decoder) rawValue(expr *unstable.Node, value *unstable.Node) []byte { if value.Kind != unstable.InlineTable && value.Kind != unstable.Array { return d.p.Raw(value.Raw) } if expr == nil || expr.Kind != unstable.KeyValue { // Inline container nested in another container: best effort. return d.p.Raw(value.Raw) } // Reconstruct the span of the value: it starts after the equal sign // following the last part of the key, and stops at the end of the // expression. var last unstable.Range it := expr.Key() for it.Next() { last = it.Node().Raw } doc := d.p.Data() i := int(last.Offset + last.Length) for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') { i++ } i++ // equal sign for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') { i++ } end := int(expr.Raw.Offset + expr.Raw.Length) return doc[i:end] } // unmarshalerOf returns the unstable.Unmarshaler implementation of v, if // any. It allocates intermediate pointers as needed. func unmarshalerOf(v reflect.Value) (unstable.Unmarshaler, bool) { t := v.Type() if t.Implements(unmarshalerType) { if v.Kind() == reflect.Ptr && v.IsNil() { v.Set(reflect.New(t.Elem())) } return v.Interface().(unstable.Unmarshaler), true } if v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType) { return v.Addr().Interface().(unstable.Unmarshaler), true } return nil, false } var unmarshalerType = reflect.TypeOf(new(unstable.Unmarshaler)).Elem() // assignValue stores the TOML value carried by the node into v. func (d *decoder) assignValue(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { if v.Kind() == reflect.Ptr { if d.unmarshalerInterface { if u, ok := unmarshalerOf(v); ok { return v, u.UnmarshalTOML(d.rawValue(expr, value)) } } if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } nv, err := d.assignValue(v.Elem(), expr, value) if err != nil || !nv.IsValid() { return reflect.Value{}, err } v.Elem().Set(nv) return v, nil } if d.unmarshalerInterface { if u, ok := unmarshalerOf(v); ok { return v, u.UnmarshalTOML(d.rawValue(expr, value)) } } switch value.Kind { case unstable.String: return d.assignString(v, value) case unstable.Integer: return d.assignInteger(v, value) case unstable.Float: return d.assignFloat(v, value) case unstable.Bool: return d.assignBool(v, value) case unstable.DateTime: return d.assignDateTime(v, value) case unstable.LocalDateTime: return d.assignLocalDateTime(v, value) case unstable.LocalDate: return d.assignLocalDate(v, value) case unstable.LocalTime: return d.assignLocalTime(v, value) case unstable.Array: return d.assignArray(v, expr, value) case unstable.InlineTable: return d.assignInlineTable(v, expr, value) default: return reflect.Value{}, unstable.NewParserError(value.Data, "unsupported value kind %s", value.Kind) } } func (d *decoder) assignString(v reflect.Value, value *unstable.Node) (reflect.Value, error) { switch v.Kind() { case reflect.String: v.SetString(string(value.Data)) return v, nil case reflect.Interface: return boxInto(v, reflect.ValueOf(string(value.Data))) default: } if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(value.Data) if err != nil { return reflect.Value{}, unstable.NewParserError(d.p.Raw(value.Raw), "%s", err) } return v, nil } return reflect.Value{}, d.typeMismatchError("string", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignInteger(v reflect.Value, value *unstable.Node) (reflect.Value, error) { // Integer values targeting a float field are parsed as floats: they can // represent (approximately) numbers beyond the int64 range. if k := v.Kind(); k == reflect.Float32 || k == reflect.Float64 { return d.assignFloat(v, value) } i, err := parseInteger(value.Data) if err != nil { return reflect.Value{}, err } switch v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if v.OverflowInt(i) { return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type()) } v.SetInt(i) return v, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: if i < 0 { return reflect.Value{}, unstable.NewParserError(value.Data, "negative integer value %d cannot be stored in %s", i, v.Type()) } if v.OverflowUint(uint64(i)) { return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type()) } v.SetUint(uint64(i)) return v, nil case reflect.Interface: return boxInto(v, reflect.ValueOf(i)) default: } if ok, err := tryTextUnmarshaler(v, value.Data); ok { return v, err } return reflect.Value{}, d.typeMismatchError("integer", v.Type(), d.p.Raw(value.Raw)) } // tryTextUnmarshaler attempts to deliver the raw text of a value to a target // implementing encoding.TextUnmarshaler. func tryTextUnmarshaler(v reflect.Value, text []byte) (bool, error) { if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(text) } return false, nil } func (d *decoder) assignFloat(v reflect.Value, value *unstable.Node) (reflect.Value, error) { f, err := parseFloat(value.Data) if err != nil { return reflect.Value{}, err } switch v.Kind() { case reflect.Float64: v.SetFloat(f) return v, nil case reflect.Float32: if !math.IsInf(f, 0) && math.Abs(f) > math.MaxFloat32 { return reflect.Value{}, unstable.NewParserError(value.Data, "float value %f cannot be stored in float32", f) } v.SetFloat(f) return v, nil case reflect.Interface: return boxInto(v, reflect.ValueOf(f)) default: } if ok, err := tryTextUnmarshaler(v, value.Data); ok { return v, err } return reflect.Value{}, d.typeMismatchError("float", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignBool(v reflect.Value, value *unstable.Node) (reflect.Value, error) { b := value.Data[0] == 't' switch v.Kind() { case reflect.Bool: v.SetBool(b) return v, nil case reflect.Interface: return boxInto(v, reflect.ValueOf(b)) default: } if ok, err := tryTextUnmarshaler(v, value.Data); ok { return v, err } return reflect.Value{}, d.typeMismatchError("boolean", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { t, err := parseDateTime(value.Data) if err != nil { return reflect.Value{}, err } if v.Type() == timeType { setConcrete(v, t) return v, nil } if v.Kind() == reflect.Interface { return boxInto(v, reflect.ValueOf(t)) } return reflect.Value{}, d.typeMismatchError("datetime", v.Type(), d.p.Raw(value.Raw)) } // setConcrete assigns x to v, which must be a settable value of x's concrete // type. Writing through the address avoids the heap allocation that // v.Set(reflect.ValueOf(x)) incurs from boxing x into an interface. Settable // implies addressable: the decoder assigns to struct fields, slice elements, // and reflect.New-allocated temporaries (map elements are decoded into such // temporaries), all of which are addressable. func setConcrete[T any](v reflect.Value, x T) { *(v.Addr().Interface().(*T)) = x } func (d *decoder) assignLocalDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { dt, rest, err := parseLocalDateTime(value.Data) if err != nil { return reflect.Value{}, err } if len(rest) > 0 { return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local date time") } switch v.Type() { case localDateTimeType: setConcrete(v, dt) return v, nil case timeType: setConcrete(v, dt.AsTime(time.Local)) return v, nil } if v.Kind() == reflect.Interface { return boxInto(v, reflect.ValueOf(dt)) } return reflect.Value{}, d.typeMismatchError("local datetime", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignLocalDate(v reflect.Value, value *unstable.Node) (reflect.Value, error) { date, err := parseLocalDate(value.Data) if err != nil { return reflect.Value{}, err } switch v.Type() { case localDateType: setConcrete(v, date) return v, nil case timeType: setConcrete(v, date.AsTime(time.Local)) return v, nil } if v.Kind() == reflect.Interface { return boxInto(v, reflect.ValueOf(date)) } return reflect.Value{}, d.typeMismatchError("local date", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignLocalTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) { t, rest, err := parseLocalTime(value.Data) if err != nil { return reflect.Value{}, err } if len(rest) > 0 { return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local time") } switch v.Type() { case localTimeType: setConcrete(v, t) return v, nil case timeType: setConcrete(v, time.Date(0, 1, 1, t.Hour, t.Minute, t.Second, t.Nanosecond, time.Local)) return v, nil } if v.Kind() == reflect.Interface { return boxInto(v, reflect.ValueOf(t)) } return reflect.Value{}, d.typeMismatchError("local time", v.Type(), d.p.Raw(value.Raw)) } func (d *decoder) assignArray(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { // Count the elements to allocate the target in one go. count := 0 cit := value.Children() for cit.Next() { if cit.Node().Kind != unstable.Comment { count++ } } switch v.Kind() { case reflect.Slice: // Allocate the backing array once at its final length and assign each // element in place. This avoids a reflect.New allocation per element // and the repeated growth checks of reflect.Append. slice := reflect.MakeSlice(v.Type(), count, count) i := 0 it := value.Children() for it.Next() { n := it.Node() if n.Kind == unstable.Comment { continue } elem := slice.Index(i) nv, err := d.assignValue(elem, nil, n) if err != nil { return reflect.Value{}, err } if nv.IsValid() { elem.Set(nv) } i++ } return slice, nil case reflect.Array: it := value.Children() i := 0 for it.Next() { n := it.Node() if n.Kind == unstable.Comment { continue } if i >= v.Len() { // Extra elements are dropped when the target array is too // small. break } elem := v.Index(i) nv, err := d.assignValue(elem, nil, n) if err != nil { return reflect.Value{}, err } elem.Set(nv) i++ } return v, nil case reflect.Interface: // Build the []interface{} natively: each element is decoded straight // into a Go value with no intermediate addressable reflect.Value and // no reflect round-trip, and nested arrays recurse the same way. slice := make([]interface{}, 0, count) it := value.Children() for it.Next() { n := it.Node() if n.Kind == unstable.Comment { continue } ev, err := d.decodeAny(n) if err != nil { return reflect.Value{}, err } slice = append(slice, ev) } return boxInto(v, reflect.ValueOf(slice)) default: } return reflect.Value{}, d.typeMismatchError("array", v.Type(), d.rawValue(expr, value)) } // decodeAny decodes a value node into a native Go value (the representation // used for interface{} targets), without going through reflect. Scalars and // arrays are handled directly; inline tables still defer to the reflect-based // path so that their dotted-key merge semantics remain identical. func (d *decoder) decodeAny(n *unstable.Node) (interface{}, error) { switch n.Kind { case unstable.String: return string(n.Data), nil case unstable.Integer: i, err := parseInteger(n.Data) return i, err case unstable.Float: f, err := parseFloat(n.Data) return f, err case unstable.Bool: return n.Data[0] == 't', nil case unstable.Array: count := 0 cit := n.Children() for cit.Next() { if cit.Node().Kind != unstable.Comment { count++ } } slice := make([]interface{}, 0, count) it := n.Children() for it.Next() { c := it.Node() if c.Kind == unstable.Comment { continue } ev, err := d.decodeAny(c) if err != nil { return nil, err } slice = append(slice, ev) } return slice, nil case unstable.InlineTable: // Build the map natively: navigate each (possibly dotted) key with // plain Go map operations and decode each value with decodeAny. The // seen-tracker has already rejected duplicate or conflicting keys, so // intermediate parts can be created/merged without revalidation. count := 0 cit := n.Children() for cit.Next() { count++ } m := make(map[string]interface{}, count) it := n.Children() for it.Next() { kv := it.Node() if err := d.setAnyKey(m, kv.Key(), kv.Value()); err != nil { return nil, err } } return m, nil case unstable.DateTime: t, err := parseDateTime(n.Data) return t, err case unstable.LocalDateTime: dt, rest, err := parseLocalDateTime(n.Data) if err != nil { return nil, err } if len(rest) > 0 { return nil, unstable.NewParserError(rest, "extra characters at the end of a local date time") } return dt, nil case unstable.LocalDate: date, err := parseLocalDate(n.Data) return date, err case unstable.LocalTime: t, rest, err := parseLocalTime(n.Data) if err != nil { return nil, err } if len(rest) > 0 { return nil, unstable.NewParserError(rest, "extra characters at the end of a local time") } return t, nil default: return nil, unstable.NewParserError(n.Data, "unsupported value kind %s", n.Kind) } } func (d *decoder) assignInlineTable(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) { switch v.Kind() { case reflect.Map: // Inline tables are self-contained: they fully replace the target. v = reflect.MakeMap(v.Type()) case reflect.Struct: // fields are set in place case reflect.Interface: elem := reflect.ValueOf(map[string]interface{}{}) nv, err := d.assignInlineTable(elem, expr, value) if err != nil { return reflect.Value{}, err } return boxInto(v, nv) default: return reflect.Value{}, d.typeMismatchError("inline table", v.Type(), d.rawValue(expr, value)) } it := value.Children() for it.Next() { kv := it.Node() // Build the path from the key of this key-value. Keys of inline // tables rarely have more than a few parts. var pathBuf [4]pathPart path := pathBuf[:0] kit := kv.Key() for kit.Next() { path = append(path, pathPart{node: kit.Node()}) } nv, err := d.descend(v, path, 0, kv, kv.Value()) if err != nil { return reflect.Value{}, err } if nv.IsValid() { v = nv } } return v, nil } // boxInto returns the value to store in place of the interface value v. The // caller stores the result in the slot v was found in, which performs the // interface conversion, so the concrete value can be returned as-is. func boxInto(v reflect.Value, c reflect.Value) (reflect.Value, error) { if !c.Type().AssignableTo(v.Type()) { return reflect.Value{}, fmt.Errorf("toml: cannot store %s into %s", c.Type(), v.Type()) } return c, nil } var ( interfaceType = reflect.TypeOf(new(interface{})).Elem() localDateType = reflect.TypeOf(LocalDate{}) localTimeType = reflect.TypeOf(LocalTime{}) localDateTimeType = reflect.TypeOf(LocalDateTime{}) ) // structPlan caches the mapping between TOML keys and the fields of a struct // type. byFold, keyed by the lowercased name, resolves any key on its own when // no two fields fold to the same name (the overwhelmingly common case, marked // by hasCollision == false): TOML keys are usually lowercase and never match // the exact (capitalized) Go field names, so the byName probe was always a // wasted lookup. byName (the exact names) is only consulted, first, when // fields do collide under folding, to preserve the exact-match-wins tiebreak. type structPlan struct { byName map[string]structField byFold map[string]structField hasCollision bool } type structField struct { index []int fieldName string } // foldBufSize bounds the stack buffer used to lowercase keys without // allocating. Keys longer than this (extremely rare) take the strings.ToLower // fallback. const foldBufSize = 68 // lookup and lookupBytes keep the hot path to a single inlinable byFold lookup. // byFold is indexed by both the exact field/tag names and their lowercased // forms, so that lookup resolves the two common cases — a lowercase key, or a // key matching the field's own casing — directly. byName is consulted first // only for types whose fields collide under case-folding, to preserve the // exact-match-wins tiebreak. The buffer-fold for other casings lives // out-of-line so it does not bloat the hot path. func (p *structPlan) lookup(name string) (structField, bool) { if p.hasCollision { if f, ok := p.byName[name]; ok { return f, true } } if f, ok := p.byFold[name]; ok { return f, true } return p.lookupFoldStr(name) } func (p *structPlan) lookupBytes(name []byte) (structField, bool) { if p.hasCollision { if f, ok := p.byName[string(name)]; ok { // does not allocate return f, true } } if f, ok := p.byFold[string(name)]; ok { // does not allocate return f, true } return p.lookupFold(name) } // lookupFold resolves keys whose casing matches neither the exact nor the // lowercased index: it folds to lowercase (in a stack buffer for ASCII, so no // allocation) and retries; only non-ASCII or oversized keys hit strings.ToLower. func (p *structPlan) lookupFold(name []byte) (structField, bool) { if len(name) <= foldBufSize { // Fold into a stack buffer: len(name) <= cap(buf), so the append // never reallocates and nothing escapes to the heap. var buf [foldBufSize]byte b := buf[:0] ascii := true for _, c := range name { if c >= 0x80 { ascii = false break } if c >= 'A' && c <= 'Z' { c += 'a' - 'A' } b = append(b, c) } if ascii { f, ok := p.byFold[string(b)] // does not allocate return f, ok } } f, ok := p.byFold[strings.ToLower(string(name))] return f, ok } func (p *structPlan) lookupFoldStr(name string) (structField, bool) { if len(name) <= foldBufSize { // Fold into a stack buffer: len(name) <= cap(buf), so the append // never reallocates and nothing escapes to the heap. var buf [foldBufSize]byte b := buf[:0] ascii := true for i := 0; i < len(name); i++ { c := name[i] if c >= 0x80 { ascii = false break } if c >= 'A' && c <= 'Z' { c += 'a' - 'A' } b = append(b, c) } if ascii { f, ok := p.byFold[string(b)] // does not allocate return f, ok } } f, ok := p.byFold[strings.ToLower(name)] return f, ok } var structPlans sync.Map // reflect.Type -> *structPlan func planForType(t reflect.Type) *structPlan { if plan, ok := structPlans.Load(t); ok { return plan.(*structPlan) } plan := buildPlan(t) structPlans.Store(t, plan) return plan } func buildPlan(t reflect.Type) *structPlan { plan := &structPlan{ byName: map[string]structField{}, byFold: map[string]structField{}, } addFields(plan, t, nil, map[reflect.Type]bool{}) return plan } // addFields flattens the fields of t into the plan. visited holds the types // being flattened on the current branch: a struct type that embeds itself // (directly or through other types) is not descended into again, mirroring // buildEncPlan. Without this guard a recursive embedding would recurse // forever; the repeated fields are unreachable by flattening anyway. func addFields(plan *structPlan, t reflect.Type, prefix []int, visited map[reflect.Type]bool) { if visited[t] { return } visited[t] = true defer delete(visited, t) var embedded []reflect.StructField for i := 0; i < t.NumField(); i++ { f := t.Field(i) tag, tagged := f.Tag.Lookup("toml") name := f.Name explicitName := "" if tagged { // A tag of exactly "-" drops the field. "-," names it "-". if tag == "-" { continue } parts := strings.SplitN(tag, ",", 2) explicitName = parts[0] if explicitName != "" { name = explicitName } } if f.Anonymous { ft := f.Type if ft.Kind() == reflect.Ptr { ft = ft.Elem() } if ft.Kind() != reflect.Struct { // Embedded non-struct fields are not decoded into. continue } if explicitName == "" { // Embedded structs without an explicit tag name are flattened, // even when their type is unexported: only their own exported // fields are reachable. A tag that only sets options (e.g. // `,inline`) still flattens, matching encoding/json and the // encoder's behavior. embedded = append(embedded, f) continue } // An embedded struct given an explicit tag name acts as a regular // named field. } else if f.PkgPath != "" { // unexported continue } index := make([]int, 0, len(prefix)+1) index = append(index, prefix...) index = append(index, i) sf := structField{index: index, fieldName: f.Name} if _, ok := plan.byName[name]; !ok { plan.byName[name] = sf } lower := strings.ToLower(name) if _, ok := plan.byFold[lower]; !ok { plan.byFold[lower] = sf } else { // Two distinct fields fold to the same name: case-insensitive // matching is ambiguous, so lookups must consult byName first to // keep the exact-match-wins tiebreak deterministic. plan.hasCollision = true } // Index the exact (cased) name as well, so a key written with the // field's own casing resolves in a single byFold lookup. Only fields // whose name is not already lowercase need this extra entry. Any name // that would conflict here also collides under folding (handled // above), so byName-first preserves the exact tiebreak in that case. if name != lower { if _, ok := plan.byFold[name]; !ok { plan.byFold[name] = sf } } } // Embedded structs are flattened after the regular fields, so that // shallower fields win. for _, f := range embedded { ft := f.Type if ft.Kind() == reflect.Ptr { ft = ft.Elem() } index := make([]int, 0, len(prefix)+1) index = append(index, prefix...) idx := f.Index[0] index = append(index, idx) addFields(plan, ft, index, visited) } } // fieldByIndexAlloc returns the field of v at the given index path, // allocating intermediate embedded pointers as needed. func fieldByIndexAlloc(v reflect.Value, index []int) (reflect.Value, error) { // Fast path for non-embedded fields, which have a single-element index: // no intermediate pointer dereferencing is possible. if len(index) == 1 { return v.Field(index[0]), nil } for i, x := range index { if i > 0 { for v.Kind() == reflect.Ptr { if v.IsNil() { if !v.CanSet() { // A nil embedded pointer of unexported type cannot be // allocated: reflect forbids setting it. Match // encoding/json and report it instead of panicking. return reflect.Value{}, fmt.Errorf("toml: cannot set embedded pointer to unexported struct: %s", v.Type().Elem()) } v.Set(reflect.New(v.Type().Elem())) } v = v.Elem() } } v = v.Field(x) } return v, nil }