github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/gocritic.go (about)

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