Skip to content

Commit

Permalink
Avoid stack overflow in sourcetype.IsSourceType. (#322)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlevesquedion authored Jul 13, 2021
1 parent 4c61727 commit 99e3a7f
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 11 deletions.
35 changes: 24 additions & 11 deletions internal/pkg/sourcetype/sourcetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,41 @@ import (
// - A Struct Type that contains a tagged field
// - A composite type that contains a Source Type
func IsSourceType(c *config.Config, tf fieldtags.ResultType, t types.Type) bool {
deref := utils.Dereference(t)
switch tt := deref.(type) {
seen := map[types.Type]bool{}
return isSourceType(c, tf, t, seen)
}

// isSourceType is a helper method for IsSourceType.
// The set of seen types is kept track of to prevent infinite recursion on
// types such as `type A map[string]A`, which refer to themselves.
func isSourceType(c *config.Config, tf fieldtags.ResultType, t types.Type, seen map[types.Type]bool) bool {
// If a type has been seen, then its status as a Source has already
// been evaluated. Return to avoid infinite recursion.
if seen[t] {
return false
}
seen[t] = true

switch tt := t.(type) {
case *types.Named:
return c.IsSourceType(utils.DecomposeType(tt)) || IsSourceType(c, tf, tt.Underlying())
return c.IsSourceType(utils.DecomposeType(tt)) || isSourceType(c, tf, tt.Underlying(), seen)
case *types.Array:
return IsSourceType(c, tf, tt.Elem())
return isSourceType(c, tf, tt.Elem(), seen)
case *types.Slice:
return IsSourceType(c, tf, tt.Elem())
return isSourceType(c, tf, tt.Elem(), seen)
case *types.Chan:
return IsSourceType(c, tf, tt.Elem())
return isSourceType(c, tf, tt.Elem(), seen)
case *types.Map:
key := IsSourceType(c, tf, tt.Key())
elem := IsSourceType(c, tf, tt.Elem())
key := isSourceType(c, tf, tt.Key(), seen)
elem := isSourceType(c, tf, tt.Elem(), seen)
return key || elem
case *types.Pointer:
return isSourceType(c, tf, tt.Elem(), seen)
case *types.Struct:
return hasTaggedField(tf, tt)
case *types.Basic, *types.Tuple, *types.Interface, *types.Signature:
// These types do not currently represent possible source types
return false
case *types.Pointer:
// This should be unreachable due to the dereference above
return false
default:
// The above should be exhaustive. Reaching this default case is an error.
fmt.Printf("unexpected type received: %T %v; please report this issue\n", tt, tt)
Expand Down
52 changes: 52 additions & 0 deletions internal/pkg/sourcetype/sourcetype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sourcetype

import (
"testing"

"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/fieldtags"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildssa"
)

var Analyzer = &analysis.Analyzer{
Name: "sourcetype_test",
Doc: "This analyzer is used to test the sourcetype package.",
Run: run,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}

func run(pass *analysis.Pass) (interface{}, error) {
ssaInput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)

c := &config.Config{}
tf := make(fieldtags.ResultType)

for _, fn := range ssaInput.SrcFuncs {
for _, p := range fn.Params {
_ = IsSourceType(c, tf, p.Type())
}
}

return nil, nil
}

func TestSourceTypeDoesNotStackOverflow(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, Analyzer, "./...")
}
31 changes: 31 additions & 0 deletions internal/pkg/sourcetype/testdata/test_stackoverflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package test

type SelfReferential map[string]SelfReferential

func selfReferential(s SelfReferential) {
}

type CoReferentialA CoReferentialB
type CoReferentialB *CoReferentialA

func coReferential(c CoReferentialA) {
}

type DeepSelfReferential [1][]chan DeepSelfReferential

func deepSelfReferential(d DeepSelfReferential) {
}

0 comments on commit 99e3a7f

Please sign in to comment.