Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 89 additions & 24 deletions go/extractor/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ type typeParamParentEntry struct {
isFromReceiver bool
}

// typeParamMutex protects typeParamParent and typeParamOrigin.
var typeParamMutex sync.RWMutex

var typeParamParent map[*types.TypeParam]typeParamParentEntry = make(map[*types.TypeParam]typeParamParentEntry)

var typeParamOrigin map[*types.TypeParam]*types.TypeParam = make(map[*types.TypeParam]*types.TypeParam)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. That also applies to the pre-existing typeParamParent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 18beab9


func init() {
// this sets the number of threads that the Go runtime will spawn; this is separate
// from the number of goroutines that the program spawns, which are scheduled into
Expand Down Expand Up @@ -1658,29 +1663,7 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
for i := 0; i < origintp.NumMethods(); i++ {
meth := origintp.Method(i).Origin()
extractMethod(tw, meth)

// Consider a generic struct and a generic method:
//
// type S[P any] struct{}
// func (*S[P]) m[Q any](x Q) {}
//
// If we have a variable 's' of type 'S[int]' and the expression
// 's.m[string]("")', then the type of the selector expression 's.m'
// is ' func(Q)'. The method 'm' here is an instantiation of the
// declaration, which has its own type with type parameter 'Q'.
// As we do not extract method instantiations, 'populateTypeParamParents'
// does not automatically get called for the type parameter 'Q'
// from the instantiation of 'm'. To compensate, we add the type
// parameters here.
//
// As a parent we use the origin method. This suffices, as the name
// and index of the type parameter in the instantiation will be
// identical to those of the uninstantiated method, and as only
// these two properties will be extracted for a type parameter.
if tp.Method(i) != meth {
signature := tp.Method(i).Type().(*types.Signature)
populateTypeParamParents(signature.TypeParams(), meth, false)
}
populateTypeParamParentAndOrigin(tp, i, meth)
}

underlyingInterface, underlyingIsInterface := underlying.(*types.Interface)
Expand All @@ -1704,7 +1687,8 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
case *types.TypeParam:
kind = dbscheme.TypeParamType.Index()
parentlbl, isReceiverChild := getTypeParamParentLabel(tw, tp)
constraintLabel := extractType(tw, tp.Constraint())
constraint := getTypeParamOrigin(tp).Constraint()
constraintLabel := extractType(tw, constraint)
dbscheme.TypeParamTable.Emit(tw, lbl, tp.Obj().Name(), constraintLabel, parentlbl, tp.Index(), isReceiverChild)
case *types.Union:
kind = dbscheme.TypeSetLiteral.Index()
Expand Down Expand Up @@ -2062,7 +2046,10 @@ func getObjectBeingUsed(tw *trap.Writer, ident *ast.Ident) types.Object {
}

func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label, bool) {
typeParamMutex.RLock()
entry, exists := typeParamParent[tp]
typeParamMutex.RUnlock()

if !exists {
log.Fatalf("Parent of type parameter does not exist: %s %s", tp.String(), tp.Constraint().String())
}
Expand All @@ -2074,6 +2061,13 @@ func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label,
}

func setTypeParamParent(tp *types.TypeParam, parent types.Object, isFromReceiver bool) {
typeParamMutex.Lock()
defer typeParamMutex.Unlock()
setTypeParamParentLocked(tp, parent, isFromReceiver)
}

// setTypeParamParentLocked requires typeParamMutex to be held for writing.
func setTypeParamParentLocked(tp *types.TypeParam, parent types.Object, isFromReceiver bool) {
entry, exists := typeParamParent[tp]
newEntry := typeParamParentEntry{parent, isFromReceiver}
if !exists {
Expand Down Expand Up @@ -2121,3 +2115,74 @@ func checkObjectNotSpecialized(obj types.Object) {
}
}
}

// getTypeParamOrigin returns the origin type parameter for a type parameter
// from an instantiated method.
func getTypeParamOrigin(tp *types.TypeParam) *types.TypeParam {
typeParamMutex.RLock()
if origin, exists := typeParamOrigin[tp]; exists {
typeParamMutex.RUnlock()
return origin
}
typeParamMutex.RUnlock()
return tp
}

// populateTypeParamParentAndOrigin records for each type parameter of a method
// the origin parent and type parameter.
//
// Consider a generic struct and a generic method:
//
// type S[P any] struct{}
// func (*S[P]) m[Q ~P](x Q) {}
//
// If we have a variable 's' of type 'S[int]' and the expression 's.m[int](42)',
// then the type of the selector expression 's.m' is 'func[Q ~int](Q)'. The
// method 'm' here is an instantiation of the declaration, which has its own
// type with type parameter 'Q' with constraint 'interface { ~int }'. As we
// do not extract method instantiations, but only their origins, we want to
// match this behavior for the constraints of instantiated type parameter, and
// record their origin. Moreover, not extracting instantiations also means that
// 'populateTypeParamParents' does not automatically get called on their type
// parameters. To compensate, we add the type params here by calling
// `setTypeParamParent`.
//
// As the parent of a type parameter use the origin method. This suffices, as
// the name and index of the type parameter in the instantiation will be
// identical to those of the uninstantiated method, and as only a constraint and
// these two properties will be extracted for a type parameter.
func populateTypeParamParentAndOrigin(tp *types.Named, i int, meth *types.Func) {
if tp.Method(i) == meth {
return
}

instantiatedParams := tp.Method(i).Type().(*types.Signature).TypeParams()
originParams := meth.Type().(*types.Signature).TypeParams()

if instantiatedParams.Len() != originParams.Len() {
log.Fatalf("Method instantiation %s has %d type parameters, origin has %d",
tp.Method(i), instantiatedParams.Len(), originParams.Len())
}

for j := 0; j < instantiatedParams.Len(); j++ {
setTypeParamParentAndOrigin(instantiatedParams.At(j), meth, originParams.At(j))
}
}

// setTypeParamParentAndOrigin atomically records the parent and origin of an
// instantiated method type parameter.
func setTypeParamParentAndOrigin(instantiatedParam *types.TypeParam, parent *types.Func, originParam *types.TypeParam) {
typeParamMutex.Lock()
defer typeParamMutex.Unlock()

setTypeParamParentLocked(instantiatedParam, parent, false)

if existing, exists := typeParamOrigin[instantiatedParam]; exists {
if existing != originParam {
log.Fatalf("Origin of type parameter '%s %s' being set to a different value: '%s' vs '%s'",
instantiatedParam.String(), instantiatedParam.Constraint().String(), existing.String(), originParam.String())
}
}

typeParamOrigin[instantiatedParam] = originParam
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ numberOfTypeParameters
| genericMethods.go:5:33:5:46 | GenericMethod1 | 1 |
| genericMethods.go:7:6:7:28 | StructForGenericMethod2 | 1 |
| genericMethods.go:9:37:9:50 | GenericMethod2 | 2 |
| genericMethods.go:21:6:21:29 | StructWithDependentBound | 1 |
| genericMethods.go:23:38:23:68 | GenericMethodWithDependentBound | 2 |
#select
| codeql-go-tests/function.EdgeConstraint | 0 | | Node | interface { } |
| codeql-go-tests/function.Element | 0 | | S | interface { } |
Expand Down Expand Up @@ -51,6 +53,9 @@ numberOfTypeParameters
| codeql-go-tests/function.StructForGenericMethod2 | 0 | | P2 | interface { } |
| codeql-go-tests/function.StructForGenericMethod2.GenericMethod2 | 0 | | P4 | interface { } |
| codeql-go-tests/function.StructForGenericMethod2.GenericMethod2 | 0 | from receiver | P3 | interface { } |
| codeql-go-tests/function.StructWithDependentBound | 0 | | P5 | interface { } |
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | | P7 | interface { ~[]P6 } |
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | from receiver | P6 | interface { } |
| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | | _ | interface { } |
| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | | _ | interface { string } |
| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 2 | | _ | interface { } |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ func generic_methods(s1 StructForGenericMethod1, s2 StructForGenericMethod2[int]
s1.GenericMethod1("hello")
s2.GenericMethod2(42)
}

type StructWithDependentBound[P5 any] struct{}

func (*StructWithDependentBound[P6]) GenericMethodWithDependentBound[P7 ~[]P6](x P7) {}

func genericMethodDependentBounds(t1 StructWithDependentBound[int], t2 StructWithDependentBound[string]) {
t1.GenericMethodWithDependentBound([]int{})
t2.GenericMethodWithDependentBound([]string{})
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
| genericMethods.go:9:37:9:50 | GenericMethod2 | 0 | genericMethods.go:9:60:9:60 | x |
| genericMethods.go:11:6:11:20 | generic_methods | 0 | genericMethods.go:11:22:11:23 | s1 |
| genericMethods.go:11:6:11:20 | generic_methods | 1 | genericMethods.go:11:50:11:51 | s2 |
| genericMethods.go:23:38:23:68 | GenericMethodWithDependentBound | 0 | genericMethods.go:23:80:23:80 | x |
| genericMethods.go:25:6:25:33 | genericMethodDependentBounds | 0 | genericMethods.go:25:35:25:36 | t1 |
| genericMethods.go:25:6:25:33 | genericMethodDependentBounds | 1 | genericMethods.go:25:69:25:70 | t2 |
| main.go:7:6:7:7 | f1 | 0 | main.go:7:9:7:9 | x |
| main.go:9:12:9:13 | f2 | 0 | main.go:9:15:9:15 | x |
| main.go:9:12:9:13 | f2 | 1 | main.go:9:18:9:18 | y |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@
| genericMethods.go:5:1:5:63 | function declaration | MethodDecl | 0 | genericMethods.go:5:48:5:53 | type parameter declaration | 0 | genericMethods.go:5:48:5:49 | P1 | genericMethods.go:5:51:5:53 | any | interface { } |
| genericMethods.go:7:6:7:45 | type declaration specifier | TypeSpec | 0 | genericMethods.go:7:30:7:35 | type parameter declaration | 0 | genericMethods.go:7:30:7:31 | P2 | genericMethods.go:7:33:7:35 | any | interface { } |
| genericMethods.go:9:1:9:67 | function declaration | MethodDecl | 0 | genericMethods.go:9:52:9:57 | type parameter declaration | 0 | genericMethods.go:9:52:9:53 | P4 | genericMethods.go:9:55:9:57 | any | interface { } |
| genericMethods.go:21:6:21:46 | type declaration specifier | TypeSpec | 0 | genericMethods.go:21:31:21:36 | type parameter declaration | 0 | genericMethods.go:21:31:21:32 | P5 | genericMethods.go:21:34:21:36 | any | interface { } |
| genericMethods.go:23:1:23:87 | function declaration | MethodDecl | 0 | genericMethods.go:23:70:23:77 | type parameter declaration | 0 | genericMethods.go:23:70:23:71 | P7 | genericMethods.go:23:73:23:77 | type set literal | interface { ~[]P6 } |
Loading