Skip to content

Commit 293f687

Browse files
committed
Go: Only output origin constraints for methods
Also mutex protect the two global maps. Go `map`s are not are not thread safe in the case of write/writes and read/writes.
1 parent 17afaf2 commit 293f687

2 files changed

Lines changed: 91 additions & 26 deletions

File tree

go/extractor/extractor.go

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ type typeParamParentEntry struct {
3838
isFromReceiver bool
3939
}
4040

41+
// typeParamMutex protects typeParamParent.
42+
var typeParamParentMutex sync.RWMutex
43+
4144
var typeParamParent map[*types.TypeParam]typeParamParentEntry = make(map[*types.TypeParam]typeParamParentEntry)
4245

46+
// typeParamMutex protects typeParamOrigin.
47+
var typeParamOriginMutex sync.RWMutex
48+
49+
var typeParamOrigin map[*types.TypeParam]*types.TypeParam = make(map[*types.TypeParam]*types.TypeParam)
50+
4351
func init() {
4452
// this sets the number of threads that the Go runtime will spawn; this is separate
4553
// from the number of goroutines that the program spawns, which are scheduled into
@@ -1658,29 +1666,7 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
16581666
for i := 0; i < origintp.NumMethods(); i++ {
16591667
meth := origintp.Method(i).Origin()
16601668
extractMethod(tw, meth)
1661-
1662-
// Consider a generic struct and a generic method:
1663-
//
1664-
// type S[P any] struct{}
1665-
// func (*S[P]) m[Q any](x Q) {}
1666-
//
1667-
// If we have a variable 's' of type 'S[int]' and the expression
1668-
// 's.m[string]("")', then the type of the selector expression 's.m'
1669-
// is ' func(Q)'. The method 'm' here is an instantiation of the
1670-
// declaration, which has its own type with type parameter 'Q'.
1671-
// As we do not extract method instantiations, 'populateTypeParamParents'
1672-
// does not automatically get called for the type parameter 'Q'
1673-
// from the instantiation of 'm'. To compensate, we add the type
1674-
// parameters here.
1675-
//
1676-
// As a parent we use the origin method. This suffices, as the name
1677-
// and index of the type parameter in the instantiation will be
1678-
// identical to those of the uninstantiated method, and as only
1679-
// these two properties will be extracted for a type parameter.
1680-
if tp.Method(i) != meth {
1681-
signature := tp.Method(i).Type().(*types.Signature)
1682-
populateTypeParamParents(signature.TypeParams(), meth, false)
1683-
}
1669+
populateTypeParamParentsAndOrigins(tp.Method(i), meth)
16841670
}
16851671

16861672
underlyingInterface, underlyingIsInterface := underlying.(*types.Interface)
@@ -1704,7 +1690,8 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
17041690
case *types.TypeParam:
17051691
kind = dbscheme.TypeParamType.Index()
17061692
parentlbl, isReceiverChild := getTypeParamParentLabel(tw, tp)
1707-
constraintLabel := extractType(tw, tp.Constraint())
1693+
constraint := getTypeParamOrigin(tp).Constraint()
1694+
constraintLabel := extractType(tw, constraint)
17081695
dbscheme.TypeParamTable.Emit(tw, lbl, tp.Obj().Name(), constraintLabel, parentlbl, tp.Index(), isReceiverChild)
17091696
case *types.Union:
17101697
kind = dbscheme.TypeSetLiteral.Index()
@@ -2062,7 +2049,10 @@ func getObjectBeingUsed(tw *trap.Writer, ident *ast.Ident) types.Object {
20622049
}
20632050

20642051
func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label, bool) {
2052+
typeParamParentMutex.RLock()
20652053
entry, exists := typeParamParent[tp]
2054+
typeParamParentMutex.RUnlock()
2055+
20662056
if !exists {
20672057
log.Fatalf("Parent of type parameter does not exist: %s %s", tp.String(), tp.Constraint().String())
20682058
}
@@ -2073,7 +2063,11 @@ func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label,
20732063
return parentlbl, entry.isFromReceiver
20742064
}
20752065

2066+
// setTypeParamParentLocked requires typeParamMutex to be held for writing.
20762067
func setTypeParamParent(tp *types.TypeParam, parent types.Object, isFromReceiver bool) {
2068+
typeParamParentMutex.Lock()
2069+
defer typeParamParentMutex.Unlock()
2070+
20772071
entry, exists := typeParamParent[tp]
20782072
newEntry := typeParamParentEntry{parent, isFromReceiver}
20792073
if !exists {
@@ -2121,3 +2115,76 @@ func checkObjectNotSpecialized(obj types.Object) {
21212115
}
21222116
}
21232117
}
2118+
2119+
// getTypeParamOrigin returns the origin type parameter for a type parameter
2120+
// from an instantiated method.
2121+
func getTypeParamOrigin(tp *types.TypeParam) *types.TypeParam {
2122+
typeParamOriginMutex.RLock()
2123+
origin, exists := typeParamOrigin[tp]
2124+
typeParamOriginMutex.RUnlock()
2125+
2126+
if exists {
2127+
return origin
2128+
} else {
2129+
return tp
2130+
}
2131+
}
2132+
2133+
// populateTypeParamParentsAndOrigins records for each type parameter of a method
2134+
// the origin parent and type parameter.
2135+
//
2136+
// Consider a generic struct and a generic method:
2137+
//
2138+
// type S[P any] struct{}
2139+
// func (*S[P]) m[Q ~P](x Q) {}
2140+
//
2141+
// If we have a variable 's' of type 'S[int]' and the expression 's.m[int](42)',
2142+
// then the type of the selector expression 's.m' is 'func[Q ~int](Q)'. The
2143+
// method 'm' here is an instantiation of the declaration, which has its own
2144+
// type with type parameter 'Q' with constraint 'interface { ~int }'. As we
2145+
// do not extract method instantiations, but only their origins, we want to
2146+
// match this behavior for the constraints of instantiated type parameter, and
2147+
// record their origin. Moreover, not extracting instantiations also means that
2148+
// 'populateTypeParamParents' does not automatically get called on their type
2149+
// parameters. To compensate, we add the type params here by calling
2150+
// `setTypeParamParent`.
2151+
//
2152+
// As the parent of a type parameter use the origin method. This suffices, as
2153+
// the name and index of the type parameter in the instantiation will be
2154+
// identical to those of the uninstantiated method, and as only a constraint and
2155+
// these two properties will be extracted for a type parameter.
2156+
func populateTypeParamParentsAndOrigins(meth *types.Func, originmeth *types.Func) {
2157+
if meth == originmeth {
2158+
return
2159+
}
2160+
2161+
typeparams := meth.Type().(*types.Signature).TypeParams()
2162+
populateTypeParamParents(typeparams, originmeth, false)
2163+
2164+
origintypeparams := originmeth.Type().(*types.Signature).TypeParams()
2165+
populateTypeParamOrigins(meth, typeparams, origintypeparams)
2166+
}
2167+
2168+
func populateTypeParamOrigins(meth *types.Func, typeparams *types.TypeParamList, origintypeparams *types.TypeParamList) {
2169+
if typeparams.Len() != origintypeparams.Len() {
2170+
log.Fatalf("Method instantiation %s has %d type parameters, origin has %d",
2171+
meth, typeparams.Len(), origintypeparams.Len())
2172+
}
2173+
2174+
for j := 0; j < typeparams.Len(); j++ {
2175+
setTypeParamOrigin(typeparams.At(j), origintypeparams.At(j))
2176+
}
2177+
}
2178+
2179+
func setTypeParamOrigin(typeparam *types.TypeParam, origintypeparam *types.TypeParam) {
2180+
typeParamOriginMutex.Lock()
2181+
defer typeParamOriginMutex.Unlock()
2182+
2183+
entry, exists := typeParamOrigin[typeparam]
2184+
if !exists {
2185+
typeParamOrigin[typeparam] = origintypeparam
2186+
} else if entry != origintypeparam {
2187+
log.Fatalf("Origin of type parameter '%s %s' being set to a different value: '%s' vs '%s'",
2188+
typeparam.String(), typeparam.Constraint().String(), entry.String(), origintypeparam.String())
2189+
}
2190+
}

go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ numberOfTypeParameters
5555
| codeql-go-tests/function.StructForGenericMethod2.GenericMethod2 | 0 | from receiver | P3 | interface { } |
5656
| codeql-go-tests/function.StructWithDependentBound | 0 | | P5 | interface { } |
5757
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | | P7 | interface { ~[]P6 } |
58-
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | | P7 | interface { ~[]int } |
59-
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | | P7 | interface { ~[]string } |
6058
| codeql-go-tests/function.StructWithDependentBound.GenericMethodWithDependentBound | 0 | from receiver | P6 | interface { } |
6159
| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | | _ | interface { } |
6260
| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | | _ | interface { string } |

0 commit comments

Comments
 (0)