Source file src/cmd/vendor/github.com/google/pprof/profile/encode.go

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package profile
    16  
    17  import (
    18  	"errors"
    19  	"sort"
    20  	"strings"
    21  )
    22  
    23  func (p *Profile) decoder() []decoder {
    24  	return profileDecoder
    25  }
    26  
    27  // preEncode populates the unexported fields to be used by encode
    28  // (with suffix X) from the corresponding exported fields. The
    29  // exported fields are cleared up to facilitate testing.
    30  func (p *Profile) preEncode() {
    31  	strings := make(map[string]int)
    32  	addString(strings, "")
    33  
    34  	for _, st := range p.SampleType {
    35  		st.typeX = addString(strings, st.Type)
    36  		st.unitX = addString(strings, st.Unit)
    37  	}
    38  
    39  	for _, s := range p.Sample {
    40  		s.labelX = nil
    41  		var keys []string
    42  		for k := range s.Label {
    43  			keys = append(keys, k)
    44  		}
    45  		sort.Strings(keys)
    46  		for _, k := range keys {
    47  			vs := s.Label[k]
    48  			for _, v := range vs {
    49  				s.labelX = append(s.labelX,
    50  					label{
    51  						keyX: addString(strings, k),
    52  						strX: addString(strings, v),
    53  					},
    54  				)
    55  			}
    56  		}
    57  		var numKeys []string
    58  		for k := range s.NumLabel {
    59  			numKeys = append(numKeys, k)
    60  		}
    61  		sort.Strings(numKeys)
    62  		for _, k := range numKeys {
    63  			keyX := addString(strings, k)
    64  			vs := s.NumLabel[k]
    65  			units := s.NumUnit[k]
    66  			for i, v := range vs {
    67  				var unitX int64
    68  				if len(units) != 0 {
    69  					unitX = addString(strings, units[i])
    70  				}
    71  				s.labelX = append(s.labelX,
    72  					label{
    73  						keyX:  keyX,
    74  						numX:  v,
    75  						unitX: unitX,
    76  					},
    77  				)
    78  			}
    79  		}
    80  		s.locationIDX = make([]uint64, len(s.Location))
    81  		for i, loc := range s.Location {
    82  			s.locationIDX[i] = loc.ID
    83  		}
    84  	}
    85  
    86  	for _, m := range p.Mapping {
    87  		m.fileX = addString(strings, m.File)
    88  		m.buildIDX = addString(strings, m.BuildID)
    89  	}
    90  
    91  	for _, l := range p.Location {
    92  		for i, ln := range l.Line {
    93  			if ln.Function != nil {
    94  				l.Line[i].functionIDX = ln.Function.ID
    95  			} else {
    96  				l.Line[i].functionIDX = 0
    97  			}
    98  		}
    99  		if l.Mapping != nil {
   100  			l.mappingIDX = l.Mapping.ID
   101  		} else {
   102  			l.mappingIDX = 0
   103  		}
   104  	}
   105  	for _, f := range p.Function {
   106  		f.nameX = addString(strings, f.Name)
   107  		f.systemNameX = addString(strings, f.SystemName)
   108  		f.filenameX = addString(strings, f.Filename)
   109  	}
   110  
   111  	p.dropFramesX = addString(strings, p.DropFrames)
   112  	p.keepFramesX = addString(strings, p.KeepFrames)
   113  
   114  	if pt := p.PeriodType; pt != nil {
   115  		pt.typeX = addString(strings, pt.Type)
   116  		pt.unitX = addString(strings, pt.Unit)
   117  	}
   118  
   119  	p.commentX = nil
   120  	for _, c := range p.Comments {
   121  		p.commentX = append(p.commentX, addString(strings, c))
   122  	}
   123  
   124  	p.defaultSampleTypeX = addString(strings, p.DefaultSampleType)
   125  
   126  	p.stringTable = make([]string, len(strings))
   127  	for s, i := range strings {
   128  		p.stringTable[i] = s
   129  	}
   130  }
   131  
   132  func (p *Profile) encode(b *buffer) {
   133  	for _, x := range p.SampleType {
   134  		encodeMessage(b, 1, x)
   135  	}
   136  	for _, x := range p.Sample {
   137  		encodeMessage(b, 2, x)
   138  	}
   139  	for _, x := range p.Mapping {
   140  		encodeMessage(b, 3, x)
   141  	}
   142  	for _, x := range p.Location {
   143  		encodeMessage(b, 4, x)
   144  	}
   145  	for _, x := range p.Function {
   146  		encodeMessage(b, 5, x)
   147  	}
   148  	encodeStrings(b, 6, p.stringTable)
   149  	encodeInt64Opt(b, 7, p.dropFramesX)
   150  	encodeInt64Opt(b, 8, p.keepFramesX)
   151  	encodeInt64Opt(b, 9, p.TimeNanos)
   152  	encodeInt64Opt(b, 10, p.DurationNanos)
   153  	if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {
   154  		encodeMessage(b, 11, p.PeriodType)
   155  	}
   156  	encodeInt64Opt(b, 12, p.Period)
   157  	encodeInt64s(b, 13, p.commentX)
   158  	encodeInt64(b, 14, p.defaultSampleTypeX)
   159  }
   160  
   161  var profileDecoder = []decoder{
   162  	nil, // 0
   163  	// repeated ValueType sample_type = 1
   164  	func(b *buffer, m message) error {
   165  		x := new(ValueType)
   166  		pp := m.(*Profile)
   167  		pp.SampleType = append(pp.SampleType, x)
   168  		return decodeMessage(b, x)
   169  	},
   170  	// repeated Sample sample = 2
   171  	func(b *buffer, m message) error {
   172  		x := new(Sample)
   173  		pp := m.(*Profile)
   174  		pp.Sample = append(pp.Sample, x)
   175  		return decodeMessage(b, x)
   176  	},
   177  	// repeated Mapping mapping = 3
   178  	func(b *buffer, m message) error {
   179  		x := new(Mapping)
   180  		pp := m.(*Profile)
   181  		pp.Mapping = append(pp.Mapping, x)
   182  		return decodeMessage(b, x)
   183  	},
   184  	// repeated Location location = 4
   185  	func(b *buffer, m message) error {
   186  		x := new(Location)
   187  		x.Line = b.tmpLines[:0] // Use shared space temporarily
   188  		pp := m.(*Profile)
   189  		pp.Location = append(pp.Location, x)
   190  		err := decodeMessage(b, x)
   191  		b.tmpLines = x.Line[:0]
   192  		// Copy to shrink size and detach from shared space.
   193  		x.Line = append([]Line(nil), x.Line...)
   194  		return err
   195  	},
   196  	// repeated Function function = 5
   197  	func(b *buffer, m message) error {
   198  		x := new(Function)
   199  		pp := m.(*Profile)
   200  		pp.Function = append(pp.Function, x)
   201  		return decodeMessage(b, x)
   202  	},
   203  	// repeated string string_table = 6
   204  	func(b *buffer, m message) error {
   205  		err := decodeStrings(b, &m.(*Profile).stringTable)
   206  		if err != nil {
   207  			return err
   208  		}
   209  		if m.(*Profile).stringTable[0] != "" {
   210  			return errors.New("string_table[0] must be ''")
   211  		}
   212  		return nil
   213  	},
   214  	// int64 drop_frames = 7
   215  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },
   216  	// int64 keep_frames = 8
   217  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },
   218  	// int64 time_nanos = 9
   219  	func(b *buffer, m message) error {
   220  		if m.(*Profile).TimeNanos != 0 {
   221  			return errConcatProfile
   222  		}
   223  		return decodeInt64(b, &m.(*Profile).TimeNanos)
   224  	},
   225  	// int64 duration_nanos = 10
   226  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },
   227  	// ValueType period_type = 11
   228  	func(b *buffer, m message) error {
   229  		x := new(ValueType)
   230  		pp := m.(*Profile)
   231  		pp.PeriodType = x
   232  		return decodeMessage(b, x)
   233  	},
   234  	// int64 period = 12
   235  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },
   236  	// repeated int64 comment = 13
   237  	func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) },
   238  	// int64 defaultSampleType = 14
   239  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).defaultSampleTypeX) },
   240  }
   241  
   242  // postDecode takes the unexported fields populated by decode (with
   243  // suffix X) and populates the corresponding exported fields.
   244  // The unexported fields are cleared up to facilitate testing.
   245  func (p *Profile) postDecode() error {
   246  	var err error
   247  	mappings := make(map[uint64]*Mapping, len(p.Mapping))
   248  	mappingIds := make([]*Mapping, len(p.Mapping)+1)
   249  	for _, m := range p.Mapping {
   250  		m.File, err = getString(p.stringTable, &m.fileX, err)
   251  		m.BuildID, err = getString(p.stringTable, &m.buildIDX, err)
   252  		if m.ID < uint64(len(mappingIds)) {
   253  			mappingIds[m.ID] = m
   254  		} else {
   255  			mappings[m.ID] = m
   256  		}
   257  
   258  		// If this a main linux kernel mapping with a relocation symbol suffix
   259  		// ("[kernel.kallsyms]_text"), extract said suffix.
   260  		// It is fairly hacky to handle at this level, but the alternatives appear even worse.
   261  		const prefix = "[kernel.kallsyms]"
   262  		if strings.HasPrefix(m.File, prefix) {
   263  			m.KernelRelocationSymbol = m.File[len(prefix):]
   264  		}
   265  	}
   266  
   267  	functions := make(map[uint64]*Function, len(p.Function))
   268  	functionIds := make([]*Function, len(p.Function)+1)
   269  	for _, f := range p.Function {
   270  		f.Name, err = getString(p.stringTable, &f.nameX, err)
   271  		f.SystemName, err = getString(p.stringTable, &f.systemNameX, err)
   272  		f.Filename, err = getString(p.stringTable, &f.filenameX, err)
   273  		if f.ID < uint64(len(functionIds)) {
   274  			functionIds[f.ID] = f
   275  		} else {
   276  			functions[f.ID] = f
   277  		}
   278  	}
   279  
   280  	locations := make(map[uint64]*Location, len(p.Location))
   281  	locationIds := make([]*Location, len(p.Location)+1)
   282  	for _, l := range p.Location {
   283  		if id := l.mappingIDX; id < uint64(len(mappingIds)) {
   284  			l.Mapping = mappingIds[id]
   285  		} else {
   286  			l.Mapping = mappings[id]
   287  		}
   288  		l.mappingIDX = 0
   289  		for i, ln := range l.Line {
   290  			if id := ln.functionIDX; id != 0 {
   291  				l.Line[i].functionIDX = 0
   292  				if id < uint64(len(functionIds)) {
   293  					l.Line[i].Function = functionIds[id]
   294  				} else {
   295  					l.Line[i].Function = functions[id]
   296  				}
   297  			}
   298  		}
   299  		if l.ID < uint64(len(locationIds)) {
   300  			locationIds[l.ID] = l
   301  		} else {
   302  			locations[l.ID] = l
   303  		}
   304  	}
   305  
   306  	for _, st := range p.SampleType {
   307  		st.Type, err = getString(p.stringTable, &st.typeX, err)
   308  		st.Unit, err = getString(p.stringTable, &st.unitX, err)
   309  	}
   310  
   311  	// Pre-allocate space for all locations.
   312  	numLocations := 0
   313  	for _, s := range p.Sample {
   314  		numLocations += len(s.locationIDX)
   315  	}
   316  	locBuffer := make([]*Location, numLocations)
   317  
   318  	for _, s := range p.Sample {
   319  		if len(s.labelX) > 0 {
   320  			labels := make(map[string][]string, len(s.labelX))
   321  			numLabels := make(map[string][]int64, len(s.labelX))
   322  			numUnits := make(map[string][]string, len(s.labelX))
   323  			for _, l := range s.labelX {
   324  				var key, value string
   325  				key, err = getString(p.stringTable, &l.keyX, err)
   326  				if l.strX != 0 {
   327  					value, err = getString(p.stringTable, &l.strX, err)
   328  					labels[key] = append(labels[key], value)
   329  				} else if l.numX != 0 || l.unitX != 0 {
   330  					numValues := numLabels[key]
   331  					units := numUnits[key]
   332  					if l.unitX != 0 {
   333  						var unit string
   334  						unit, err = getString(p.stringTable, &l.unitX, err)
   335  						units = padStringArray(units, len(numValues))
   336  						numUnits[key] = append(units, unit)
   337  					}
   338  					numLabels[key] = append(numLabels[key], l.numX)
   339  				}
   340  			}
   341  			if len(labels) > 0 {
   342  				s.Label = labels
   343  			}
   344  			if len(numLabels) > 0 {
   345  				s.NumLabel = numLabels
   346  				for key, units := range numUnits {
   347  					if len(units) > 0 {
   348  						numUnits[key] = padStringArray(units, len(numLabels[key]))
   349  					}
   350  				}
   351  				s.NumUnit = numUnits
   352  			}
   353  		}
   354  
   355  		s.Location = locBuffer[:len(s.locationIDX)]
   356  		locBuffer = locBuffer[len(s.locationIDX):]
   357  		for i, lid := range s.locationIDX {
   358  			if lid < uint64(len(locationIds)) {
   359  				s.Location[i] = locationIds[lid]
   360  			} else {
   361  				s.Location[i] = locations[lid]
   362  			}
   363  		}
   364  		s.locationIDX = nil
   365  	}
   366  
   367  	p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)
   368  	p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)
   369  
   370  	if pt := p.PeriodType; pt == nil {
   371  		p.PeriodType = &ValueType{}
   372  	}
   373  
   374  	if pt := p.PeriodType; pt != nil {
   375  		pt.Type, err = getString(p.stringTable, &pt.typeX, err)
   376  		pt.Unit, err = getString(p.stringTable, &pt.unitX, err)
   377  	}
   378  
   379  	for _, i := range p.commentX {
   380  		var c string
   381  		c, err = getString(p.stringTable, &i, err)
   382  		p.Comments = append(p.Comments, c)
   383  	}
   384  
   385  	p.commentX = nil
   386  	p.DefaultSampleType, err = getString(p.stringTable, &p.defaultSampleTypeX, err)
   387  	p.stringTable = nil
   388  	return err
   389  }
   390  
   391  // padStringArray pads arr with enough empty strings to make arr
   392  // length l when arr's length is less than l.
   393  func padStringArray(arr []string, l int) []string {
   394  	if l <= len(arr) {
   395  		return arr
   396  	}
   397  	return append(arr, make([]string, l-len(arr))...)
   398  }
   399  
   400  func (p *ValueType) decoder() []decoder {
   401  	return valueTypeDecoder
   402  }
   403  
   404  func (p *ValueType) encode(b *buffer) {
   405  	encodeInt64Opt(b, 1, p.typeX)
   406  	encodeInt64Opt(b, 2, p.unitX)
   407  }
   408  
   409  var valueTypeDecoder = []decoder{
   410  	nil, // 0
   411  	// optional int64 type = 1
   412  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },
   413  	// optional int64 unit = 2
   414  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },
   415  }
   416  
   417  func (p *Sample) decoder() []decoder {
   418  	return sampleDecoder
   419  }
   420  
   421  func (p *Sample) encode(b *buffer) {
   422  	encodeUint64s(b, 1, p.locationIDX)
   423  	encodeInt64s(b, 2, p.Value)
   424  	for _, x := range p.labelX {
   425  		encodeMessage(b, 3, x)
   426  	}
   427  }
   428  
   429  var sampleDecoder = []decoder{
   430  	nil, // 0
   431  	// repeated uint64 location = 1
   432  	func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },
   433  	// repeated int64 value = 2
   434  	func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },
   435  	// repeated Label label = 3
   436  	func(b *buffer, m message) error {
   437  		s := m.(*Sample)
   438  		n := len(s.labelX)
   439  		s.labelX = append(s.labelX, label{})
   440  		return decodeMessage(b, &s.labelX[n])
   441  	},
   442  }
   443  
   444  func (p label) decoder() []decoder {
   445  	return labelDecoder
   446  }
   447  
   448  func (p label) encode(b *buffer) {
   449  	encodeInt64Opt(b, 1, p.keyX)
   450  	encodeInt64Opt(b, 2, p.strX)
   451  	encodeInt64Opt(b, 3, p.numX)
   452  	encodeInt64Opt(b, 4, p.unitX)
   453  }
   454  
   455  var labelDecoder = []decoder{
   456  	nil, // 0
   457  	// optional int64 key = 1
   458  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).keyX) },
   459  	// optional int64 str = 2
   460  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).strX) },
   461  	// optional int64 num = 3
   462  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).numX) },
   463  	// optional int64 num = 4
   464  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).unitX) },
   465  }
   466  
   467  func (p *Mapping) decoder() []decoder {
   468  	return mappingDecoder
   469  }
   470  
   471  func (p *Mapping) encode(b *buffer) {
   472  	encodeUint64Opt(b, 1, p.ID)
   473  	encodeUint64Opt(b, 2, p.Start)
   474  	encodeUint64Opt(b, 3, p.Limit)
   475  	encodeUint64Opt(b, 4, p.Offset)
   476  	encodeInt64Opt(b, 5, p.fileX)
   477  	encodeInt64Opt(b, 6, p.buildIDX)
   478  	encodeBoolOpt(b, 7, p.HasFunctions)
   479  	encodeBoolOpt(b, 8, p.HasFilenames)
   480  	encodeBoolOpt(b, 9, p.HasLineNumbers)
   481  	encodeBoolOpt(b, 10, p.HasInlineFrames)
   482  }
   483  
   484  var mappingDecoder = []decoder{
   485  	nil, // 0
   486  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) },            // optional uint64 id = 1
   487  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) },         // optional uint64 memory_offset = 2
   488  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) },         // optional uint64 memory_limit = 3
   489  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) },        // optional uint64 file_offset = 4
   490  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) },          // optional int64 filename = 5
   491  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) },       // optional int64 build_id = 6
   492  	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) },    // optional bool has_functions = 7
   493  	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) },    // optional bool has_filenames = 8
   494  	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) },  // optional bool has_line_numbers = 9
   495  	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10
   496  }
   497  
   498  func (p *Location) decoder() []decoder {
   499  	return locationDecoder
   500  }
   501  
   502  func (p *Location) encode(b *buffer) {
   503  	encodeUint64Opt(b, 1, p.ID)
   504  	encodeUint64Opt(b, 2, p.mappingIDX)
   505  	encodeUint64Opt(b, 3, p.Address)
   506  	for i := range p.Line {
   507  		encodeMessage(b, 4, &p.Line[i])
   508  	}
   509  	encodeBoolOpt(b, 5, p.IsFolded)
   510  }
   511  
   512  var locationDecoder = []decoder{
   513  	nil, // 0
   514  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) },         // optional uint64 id = 1;
   515  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;
   516  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) },    // optional uint64 address = 3;
   517  	func(b *buffer, m message) error { // repeated Line line = 4
   518  		pp := m.(*Location)
   519  		n := len(pp.Line)
   520  		pp.Line = append(pp.Line, Line{})
   521  		return decodeMessage(b, &pp.Line[n])
   522  	},
   523  	func(b *buffer, m message) error { return decodeBool(b, &m.(*Location).IsFolded) }, // optional bool is_folded = 5;
   524  }
   525  
   526  func (p *Line) decoder() []decoder {
   527  	return lineDecoder
   528  }
   529  
   530  func (p *Line) encode(b *buffer) {
   531  	encodeUint64Opt(b, 1, p.functionIDX)
   532  	encodeInt64Opt(b, 2, p.Line)
   533  }
   534  
   535  var lineDecoder = []decoder{
   536  	nil, // 0
   537  	// optional uint64 function_id = 1
   538  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
   539  	// optional int64 line = 2
   540  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
   541  }
   542  
   543  func (p *Function) decoder() []decoder {
   544  	return functionDecoder
   545  }
   546  
   547  func (p *Function) encode(b *buffer) {
   548  	encodeUint64Opt(b, 1, p.ID)
   549  	encodeInt64Opt(b, 2, p.nameX)
   550  	encodeInt64Opt(b, 3, p.systemNameX)
   551  	encodeInt64Opt(b, 4, p.filenameX)
   552  	encodeInt64Opt(b, 5, p.StartLine)
   553  }
   554  
   555  var functionDecoder = []decoder{
   556  	nil, // 0
   557  	// optional uint64 id = 1
   558  	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },
   559  	// optional int64 function_name = 2
   560  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },
   561  	// optional int64 function_system_name = 3
   562  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },
   563  	// repeated int64 filename = 4
   564  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },
   565  	// optional int64 start_line = 5
   566  	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },
   567  }
   568  
   569  func addString(strings map[string]int, s string) int64 {
   570  	i, ok := strings[s]
   571  	if !ok {
   572  		i = len(strings)
   573  		strings[s] = i
   574  	}
   575  	return int64(i)
   576  }
   577  
   578  func getString(strings []string, strng *int64, err error) (string, error) {
   579  	if err != nil {
   580  		return "", err
   581  	}
   582  	s := int(*strng)
   583  	if s < 0 || s >= len(strings) {
   584  		return "", errMalformed
   585  	}
   586  	*strng = 0
   587  	return strings[s], nil
   588  }
   589  

View as plain text