github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/golinters/gocritic.go (about)

     1  package golinters
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/types"
     8  	"path/filepath"
     9  	"reflect"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/go-critic/go-critic/checkers"
    16  	gocriticlinter "github.com/go-critic/go-critic/linter"
    17  	"golang.org/x/tools/go/analysis"
    18  
    19  	"github.com/chenfeining/golangci-lint/pkg/config"
    20  	"github.com/chenfeining/golangci-lint/pkg/golinters/goanalysis"
    21  	"github.com/chenfeining/golangci-lint/pkg/lint/linter"
    22  	"github.com/chenfeining/golangci-lint/pkg/logutils"
    23  	"github.com/chenfeining/golangci-lint/pkg/result"
    24  )
    25  
    26  const goCriticName = "gocritic"
    27  
    28  var (
    29  	goCriticDebugf  = logutils.Debug(logutils.DebugKeyGoCritic)
    30  	isGoCriticDebug = logutils.HaveDebugTag(logutils.DebugKeyGoCritic)
    31  )
    32  
    33  func NewGoCritic(settings *config.GoCriticSettings, cfg *config.Config) *goanalysis.Linter {
    34  	var mu sync.Mutex
    35  	var resIssues []goanalysis.Issue
    36  
    37  	wrapper := &goCriticWrapper{
    38  		cfg:   cfg,
    39  		sizes: types.SizesFor("gc", runtime.GOARCH),
    40  	}
    41  
    42  	analyzer := &analysis.Analyzer{
    43  		Name: goCriticName,
    44  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    45  		Run: func(pass *analysis.Pass) (any, error) {
    46  			issues, err := wrapper.run(pass)
    47  			if err != nil {
    48  				return nil, err
    49  			}
    50  
    51  			if len(issues) == 0 {
    52  				return nil, nil
    53  			}
    54  
    55  			mu.Lock()
    56  			resIssues = append(resIssues, issues...)
    57  			mu.Unlock()
    58  
    59  			return nil, nil
    60  		},
    61  	}
    62  
    63  	return goanalysis.NewLinter(
    64  		goCriticName,
    65  		`Provides diagnostics that check for bugs, performance and style issues.
    66  Extensible without recompilation through dynamic rules.
    67  Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.`,
    68  		[]*analysis.Analyzer{analyzer},
    69  		nil,
    70  	).
    71  		WithContextSetter(func(context *linter.Context) {
    72  			wrapper.init(settings, context.Log)
    73  		}).
    74  		WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    75  			return resIssues
    76  		}).WithLoadMode(goanalysis.LoadModeTypesInfo)
    77  }
    78  
    79  type goCriticWrapper struct {
    80  	settingsWrapper *goCriticSettingsWrapper
    81  	cfg             *config.Config
    82  	sizes           types.Sizes
    83  	once            sync.Once
    84  }
    85  
    86  func (w *goCriticWrapper) init(settings *config.GoCriticSettings, logger logutils.Log) {
    87  	if settings == nil {
    88  		return
    89  	}
    90  
    91  	w.once.Do(func() {
    92  		err := checkers.InitEmbeddedRules()
    93  		if err != nil {
    94  			logger.Fatalf("%s: %v: setting an explicit GOROOT can fix this problem.", goCriticName, err)
    95  		}
    96  	})
    97  
    98  	settingsWrapper := newGoCriticSettingsWrapper(settings, logger)
    99  
   100  	settingsWrapper.inferEnabledChecks()
   101  
   102  	if err := settingsWrapper.validate(); err != nil {
   103  		logger.Fatalf("%s: invalid settings: %s", goCriticName, err)
   104  	}
   105  
   106  	w.settingsWrapper = settingsWrapper
   107  }
   108  
   109  func (w *goCriticWrapper) run(pass *analysis.Pass) ([]goanalysis.Issue, error) {
   110  	if w.settingsWrapper == nil {
   111  		return nil, fmt.Errorf("the settings wrapper is nil")
   112  	}
   113  
   114  	linterCtx := gocriticlinter.NewContext(pass.Fset, w.sizes)
   115  
   116  	linterCtx.SetGoVersion(w.settingsWrapper.Go)
   117  
   118  	enabledCheckers, err := w.buildEnabledCheckers(linterCtx)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg)
   124  
   125  	pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files)
   126  
   127  	issues := make([]goanalysis.Issue, 0, len(pkgIssues))
   128  	for i := range pkgIssues {
   129  		issues = append(issues, goanalysis.NewIssue(&pkgIssues[i], pass))
   130  	}
   131  
   132  	return issues, nil
   133  }
   134  
   135  func (w *goCriticWrapper) buildEnabledCheckers(linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) {
   136  	allParams := w.settingsWrapper.getLowerCasedParams()
   137  
   138  	var enabledCheckers []*gocriticlinter.Checker
   139  	for _, info := range gocriticlinter.GetCheckersInfo() {
   140  		if !w.settingsWrapper.isCheckEnabled(info.Name) {
   141  			continue
   142  		}
   143  
   144  		if err := w.configureCheckerInfo(info, allParams); err != nil {
   145  			return nil, err
   146  		}
   147  
   148  		c, err := gocriticlinter.NewChecker(linterCtx, info)
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  		enabledCheckers = append(enabledCheckers, c)
   153  	}
   154  
   155  	return enabledCheckers, nil
   156  }
   157  
   158  func runGocriticOnPackage(linterCtx *gocriticlinter.Context, checks []*gocriticlinter.Checker,
   159  	files []*ast.File) []result.Issue {
   160  	var res []result.Issue
   161  	for _, f := range files {
   162  		filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename)
   163  		linterCtx.SetFileInfo(filename, f)
   164  
   165  		issues := runGocriticOnFile(linterCtx, f, checks)
   166  		res = append(res, issues...)
   167  	}
   168  	return res
   169  }
   170  
   171  func runGocriticOnFile(linterCtx *gocriticlinter.Context, f *ast.File, checks []*gocriticlinter.Checker) []result.Issue {
   172  	var res []result.Issue
   173  
   174  	for _, c := range checks {
   175  		// All checkers are expected to use *lint.Context
   176  		// as read-only structure, so no copying is required.
   177  		for _, warn := range c.Check(f) {
   178  			pos := linterCtx.FileSet.Position(warn.Pos)
   179  			issue := result.Issue{
   180  				Pos:        pos,
   181  				Text:       fmt.Sprintf("%s: %s", c.Info.Name, warn.Text),
   182  				FromLinter: goCriticName,
   183  			}
   184  
   185  			if warn.HasQuickFix() {
   186  				issue.Replacement = &result.Replacement{
   187  					Inline: &result.InlineFix{
   188  						StartCol:  pos.Column - 1,
   189  						Length:    int(warn.Suggestion.To - warn.Suggestion.From),
   190  						NewString: string(warn.Suggestion.Replacement),
   191  					},
   192  				}
   193  			}
   194  
   195  			res = append(res, issue)
   196  		}
   197  	}
   198  
   199  	return res
   200  }
   201  
   202  func (w *goCriticWrapper) configureCheckerInfo(info *gocriticlinter.CheckerInfo, allParams map[string]config.GoCriticCheckSettings) error {
   203  	params := allParams[strings.ToLower(info.Name)]
   204  	if params == nil { // no config for this checker
   205  		return nil
   206  	}
   207  
   208  	infoParams := normalizeCheckerInfoParams(info)
   209  	for k, p := range params {
   210  		v, ok := infoParams[k]
   211  		if ok {
   212  			v.Value = w.normalizeCheckerParamsValue(p)
   213  			continue
   214  		}
   215  
   216  		// param `k` isn't supported
   217  		if len(info.Params) == 0 {
   218  			return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params",
   219  				info.Name, k)
   220  		}
   221  
   222  		var supportedKeys []string
   223  		for sk := range info.Params {
   224  			supportedKeys = append(supportedKeys, sk)
   225  		}
   226  		sort.Strings(supportedKeys)
   227  
   228  		return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s",
   229  			info.Name, k, supportedKeys)
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter.CheckerParams {
   236  	// lowercase info param keys here because golangci-lint's config parser lowercases all strings
   237  	ret := gocriticlinter.CheckerParams{}
   238  	for k, v := range info.Params {
   239  		ret[strings.ToLower(k)] = v
   240  	}
   241  
   242  	return ret
   243  }
   244  
   245  // normalizeCheckerParamsValue normalizes value types.
   246  // go-critic asserts that CheckerParam.Value has some specific types,
   247  // but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type.
   248  // then we have to convert value types into the expected value types.
   249  // Maybe in the future, this kind of conversion will be done in go-critic itself.
   250  func (w *goCriticWrapper) normalizeCheckerParamsValue(p any) any {
   251  	rv := reflect.ValueOf(p)
   252  	switch rv.Type().Kind() {
   253  	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
   254  		return int(rv.Int())
   255  	case reflect.Bool:
   256  		return rv.Bool()
   257  	case reflect.String:
   258  		// Perform variable substitution.
   259  		return strings.ReplaceAll(rv.String(), "${configDir}", w.cfg.GetConfigDir())
   260  	default:
   261  		return p
   262  	}
   263  }
   264  
   265  // TODO(ldez): rewrite and simplify goCriticSettingsWrapper.
   266  
   267  type goCriticSettingsWrapper struct {
   268  	*config.GoCriticSettings
   269  
   270  	logger logutils.Log
   271  
   272  	allCheckers   []*gocriticlinter.CheckerInfo
   273  	allCheckerMap map[string]*gocriticlinter.CheckerInfo
   274  
   275  	inferredEnabledChecks map[string]bool
   276  }
   277  
   278  func newGoCriticSettingsWrapper(settings *config.GoCriticSettings, logger logutils.Log) *goCriticSettingsWrapper {
   279  	allCheckers := gocriticlinter.GetCheckersInfo()
   280  
   281  	allCheckerMap := make(map[string]*gocriticlinter.CheckerInfo)
   282  	for _, checkInfo := range allCheckers {
   283  		allCheckerMap[checkInfo.Name] = checkInfo
   284  	}
   285  
   286  	return &goCriticSettingsWrapper{
   287  		GoCriticSettings:      settings,
   288  		logger:                logger,
   289  		allCheckers:           allCheckers,
   290  		allCheckerMap:         allCheckerMap,
   291  		inferredEnabledChecks: map[string]bool{},
   292  	}
   293  }
   294  
   295  func (s *goCriticSettingsWrapper) buildTagToCheckersMap() map[string][]string {
   296  	tagToCheckers := map[string][]string{}
   297  
   298  	for _, checker := range s.allCheckers {
   299  		for _, tag := range checker.Tags {
   300  			tagToCheckers[tag] = append(tagToCheckers[tag], checker.Name)
   301  		}
   302  	}
   303  
   304  	return tagToCheckers
   305  }
   306  
   307  func (s *goCriticSettingsWrapper) checkerTagsDebugf() {
   308  	if !isGoCriticDebug {
   309  		return
   310  	}
   311  
   312  	tagToCheckers := s.buildTagToCheckersMap()
   313  
   314  	allTags := make([]string, 0, len(tagToCheckers))
   315  	for tag := range tagToCheckers {
   316  		allTags = append(allTags, tag)
   317  	}
   318  
   319  	sort.Strings(allTags)
   320  
   321  	goCriticDebugf("All gocritic existing tags and checks:")
   322  	for _, tag := range allTags {
   323  		debugChecksListf(tagToCheckers[tag], "  tag %q", tag)
   324  	}
   325  }
   326  
   327  func (s *goCriticSettingsWrapper) disabledCheckersDebugf() {
   328  	if !isGoCriticDebug {
   329  		return
   330  	}
   331  
   332  	var disabledCheckers []string
   333  	for _, checker := range s.allCheckers {
   334  		if s.inferredEnabledChecks[strings.ToLower(checker.Name)] {
   335  			continue
   336  		}
   337  
   338  		disabledCheckers = append(disabledCheckers, checker.Name)
   339  	}
   340  
   341  	if len(disabledCheckers) == 0 {
   342  		goCriticDebugf("All checks are enabled")
   343  	} else {
   344  		debugChecksListf(disabledCheckers, "Final not used")
   345  	}
   346  }
   347  
   348  func (s *goCriticSettingsWrapper) inferEnabledChecks() {
   349  	s.checkerTagsDebugf()
   350  
   351  	enabledByDefaultChecks := s.getDefaultEnabledCheckersNames()
   352  	debugChecksListf(enabledByDefaultChecks, "Enabled by default")
   353  
   354  	disabledByDefaultChecks := s.getDefaultDisabledCheckersNames()
   355  	debugChecksListf(disabledByDefaultChecks, "Disabled by default")
   356  
   357  	enabledChecks := make([]string, 0, len(s.EnabledTags)+len(enabledByDefaultChecks))
   358  
   359  	// EnabledTags
   360  	if len(s.EnabledTags) != 0 {
   361  		tagToCheckers := s.buildTagToCheckersMap()
   362  		for _, tag := range s.EnabledTags {
   363  			enabledChecks = append(enabledChecks, tagToCheckers[tag]...)
   364  		}
   365  
   366  		debugChecksListf(enabledChecks, "Enabled by config tags %s", sprintStrings(s.EnabledTags))
   367  	}
   368  
   369  	if !(len(s.EnabledTags) == 0 && len(s.EnabledChecks) != 0) {
   370  		// don't use default checks only if we have no enabled tags and enable some checks manually
   371  		enabledChecks = append(enabledChecks, enabledByDefaultChecks...)
   372  	}
   373  
   374  	// DisabledTags
   375  	if len(s.DisabledTags) != 0 {
   376  		enabledChecks = s.filterByDisableTags(enabledChecks, s.DisabledTags)
   377  	}
   378  
   379  	// EnabledChecks
   380  	if len(s.EnabledChecks) != 0 {
   381  		debugChecksListf(s.EnabledChecks, "Enabled by config")
   382  
   383  		alreadyEnabledChecksSet := stringsSliceToSet(enabledChecks)
   384  		for _, enabledCheck := range s.EnabledChecks {
   385  			if alreadyEnabledChecksSet[enabledCheck] {
   386  				s.logger.Warnf("%s: no need to enable check %q: it's already enabled", goCriticName, enabledCheck)
   387  				continue
   388  			}
   389  			enabledChecks = append(enabledChecks, enabledCheck)
   390  		}
   391  	}
   392  
   393  	// DisabledChecks
   394  	if len(s.DisabledChecks) != 0 {
   395  		debugChecksListf(s.DisabledChecks, "Disabled by config")
   396  
   397  		enabledChecksSet := stringsSliceToSet(enabledChecks)
   398  		for _, disabledCheck := range s.DisabledChecks {
   399  			if !enabledChecksSet[disabledCheck] {
   400  				s.logger.Warnf("%s: check %q was explicitly disabled via config. However, as this check "+
   401  					"is disabled by default, there is no need to explicitly disable it via config.", goCriticName, disabledCheck)
   402  				continue
   403  			}
   404  			delete(enabledChecksSet, disabledCheck)
   405  		}
   406  
   407  		enabledChecks = nil
   408  		for enabledCheck := range enabledChecksSet {
   409  			enabledChecks = append(enabledChecks, enabledCheck)
   410  		}
   411  	}
   412  
   413  	s.inferredEnabledChecks = map[string]bool{}
   414  	for _, check := range enabledChecks {
   415  		s.inferredEnabledChecks[strings.ToLower(check)] = true
   416  	}
   417  
   418  	debugChecksListf(enabledChecks, "Final used")
   419  
   420  	s.disabledCheckersDebugf()
   421  }
   422  
   423  func (s *goCriticSettingsWrapper) validate() error {
   424  	if len(s.EnabledTags) == 0 {
   425  		if len(s.EnabledChecks) != 0 && len(s.DisabledChecks) != 0 {
   426  			return errors.New("both enabled and disabled check aren't allowed for gocritic")
   427  		}
   428  	} else {
   429  		if err := validateStringsUniq(s.EnabledTags); err != nil {
   430  			return fmt.Errorf("validate enabled tags: %w", err)
   431  		}
   432  
   433  		tagToCheckers := s.buildTagToCheckersMap()
   434  
   435  		for _, tag := range s.EnabledTags {
   436  			if _, ok := tagToCheckers[tag]; !ok {
   437  				return fmt.Errorf("gocritic [enabled]tag %q doesn't exist", tag)
   438  			}
   439  		}
   440  	}
   441  
   442  	if len(s.DisabledTags) > 0 {
   443  		tagToCheckers := s.buildTagToCheckersMap()
   444  		for _, tag := range s.EnabledTags {
   445  			if _, ok := tagToCheckers[tag]; !ok {
   446  				return fmt.Errorf("gocritic [disabled]tag %q doesn't exist", tag)
   447  			}
   448  		}
   449  	}
   450  
   451  	if err := validateStringsUniq(s.EnabledChecks); err != nil {
   452  		return fmt.Errorf("validate enabled checks: %w", err)
   453  	}
   454  
   455  	if err := validateStringsUniq(s.DisabledChecks); err != nil {
   456  		return fmt.Errorf("validate disabled checks: %w", err)
   457  	}
   458  
   459  	if err := s.validateCheckerNames(); err != nil {
   460  		return fmt.Errorf("validation failed: %w", err)
   461  	}
   462  
   463  	return nil
   464  }
   465  
   466  func (s *goCriticSettingsWrapper) isCheckEnabled(name string) bool {
   467  	return s.inferredEnabledChecks[strings.ToLower(name)]
   468  }
   469  
   470  // getAllCheckerNames returns a map containing all checker names supported by gocritic.
   471  func (s *goCriticSettingsWrapper) getAllCheckerNames() map[string]bool {
   472  	allCheckerNames := make(map[string]bool, len(s.allCheckers))
   473  
   474  	for _, checker := range s.allCheckers {
   475  		allCheckerNames[strings.ToLower(checker.Name)] = true
   476  	}
   477  
   478  	return allCheckerNames
   479  }
   480  
   481  func (s *goCriticSettingsWrapper) getDefaultEnabledCheckersNames() []string {
   482  	var enabled []string
   483  
   484  	for _, info := range s.allCheckers {
   485  		enable := s.isEnabledByDefaultCheck(info)
   486  		if enable {
   487  			enabled = append(enabled, info.Name)
   488  		}
   489  	}
   490  
   491  	return enabled
   492  }
   493  
   494  func (s *goCriticSettingsWrapper) getDefaultDisabledCheckersNames() []string {
   495  	var disabled []string
   496  
   497  	for _, info := range s.allCheckers {
   498  		enable := s.isEnabledByDefaultCheck(info)
   499  		if !enable {
   500  			disabled = append(disabled, info.Name)
   501  		}
   502  	}
   503  
   504  	return disabled
   505  }
   506  
   507  func (s *goCriticSettingsWrapper) validateCheckerNames() error {
   508  	allowedNames := s.getAllCheckerNames()
   509  
   510  	for _, name := range s.EnabledChecks {
   511  		if !allowedNames[strings.ToLower(name)] {
   512  			return fmt.Errorf("enabled checker %s doesn't exist, all existing checkers: %s",
   513  				name, sprintAllowedCheckerNames(allowedNames))
   514  		}
   515  	}
   516  
   517  	for _, name := range s.DisabledChecks {
   518  		if !allowedNames[strings.ToLower(name)] {
   519  			return fmt.Errorf("disabled checker %s doesn't exist, all existing checkers: %s",
   520  				name, sprintAllowedCheckerNames(allowedNames))
   521  		}
   522  	}
   523  
   524  	for checkName := range s.SettingsPerCheck {
   525  		if _, ok := allowedNames[checkName]; !ok {
   526  			return fmt.Errorf("invalid setting, checker %s doesn't exist, all existing checkers: %s",
   527  				checkName, sprintAllowedCheckerNames(allowedNames))
   528  		}
   529  
   530  		if !s.isCheckEnabled(checkName) {
   531  			s.logger.Warnf("%s: settings were provided for not enabled check %q", goCriticName, checkName)
   532  		}
   533  	}
   534  
   535  	return nil
   536  }
   537  
   538  func (s *goCriticSettingsWrapper) getLowerCasedParams() map[string]config.GoCriticCheckSettings {
   539  	ret := make(map[string]config.GoCriticCheckSettings, len(s.SettingsPerCheck))
   540  
   541  	for checker, params := range s.SettingsPerCheck {
   542  		ret[strings.ToLower(checker)] = params
   543  	}
   544  
   545  	return ret
   546  }
   547  
   548  func (s *goCriticSettingsWrapper) filterByDisableTags(enabledChecks, disableTags []string) []string {
   549  	enabledChecksSet := stringsSliceToSet(enabledChecks)
   550  
   551  	for _, enabledCheck := range enabledChecks {
   552  		checkInfo, checkInfoExists := s.allCheckerMap[enabledCheck]
   553  		if !checkInfoExists {
   554  			s.logger.Warnf("%s: check %q was not exists via filtering disabled tags", goCriticName, enabledCheck)
   555  			continue
   556  		}
   557  
   558  		hitTags := intersectStringSlice(checkInfo.Tags, disableTags)
   559  		if len(hitTags) != 0 {
   560  			delete(enabledChecksSet, enabledCheck)
   561  		}
   562  	}
   563  
   564  	debugChecksListf(enabledChecks, "Disabled by config tags %s", sprintStrings(disableTags))
   565  
   566  	enabledChecks = nil
   567  	for enabledCheck := range enabledChecksSet {
   568  		enabledChecks = append(enabledChecks, enabledCheck)
   569  	}
   570  
   571  	return enabledChecks
   572  }
   573  
   574  func (s *goCriticSettingsWrapper) isEnabledByDefaultCheck(info *gocriticlinter.CheckerInfo) bool {
   575  	return !info.HasTag("experimental") &&
   576  		!info.HasTag("opinionated") &&
   577  		!info.HasTag("performance")
   578  }
   579  
   580  func validateStringsUniq(ss []string) error {
   581  	set := map[string]bool{}
   582  
   583  	for _, s := range ss {
   584  		_, ok := set[s]
   585  		if ok {
   586  			return fmt.Errorf("%q occurs multiple times in list", s)
   587  		}
   588  		set[s] = true
   589  	}
   590  
   591  	return nil
   592  }
   593  
   594  func intersectStringSlice(s1, s2 []string) []string {
   595  	s1Map := make(map[string]struct{}, len(s1))
   596  
   597  	for _, s := range s1 {
   598  		s1Map[s] = struct{}{}
   599  	}
   600  
   601  	results := make([]string, 0)
   602  	for _, s := range s2 {
   603  		if _, exists := s1Map[s]; exists {
   604  			results = append(results, s)
   605  		}
   606  	}
   607  
   608  	return results
   609  }
   610  
   611  func sprintAllowedCheckerNames(allowedNames map[string]bool) string {
   612  	namesSlice := make([]string, 0, len(allowedNames))
   613  
   614  	for name := range allowedNames {
   615  		namesSlice = append(namesSlice, name)
   616  	}
   617  
   618  	return sprintStrings(namesSlice)
   619  }
   620  
   621  func sprintStrings(ss []string) string {
   622  	sort.Strings(ss)
   623  	return fmt.Sprint(ss)
   624  }
   625  
   626  func debugChecksListf(checks []string, format string, args ...any) {
   627  	if !isGoCriticDebug {
   628  		return
   629  	}
   630  
   631  	goCriticDebugf("%s checks (%d): %s", fmt.Sprintf(format, args...), len(checks), sprintStrings(checks))
   632  }
   633  
   634  func stringsSliceToSet(ss []string) map[string]bool {
   635  	ret := make(map[string]bool, len(ss))
   636  	for _, s := range ss {
   637  		ret[s] = true
   638  	}
   639  
   640  	return ret
   641  }