diff --git a/packages/go/analysis/analysis.go b/packages/go/analysis/analysis.go index d75254454f..f4eb489ab2 100644 --- a/packages/go/analysis/analysis.go +++ b/packages/go/analysis/analysis.go @@ -111,17 +111,10 @@ func ClearSystemTags(ctx context.Context, db graph.Database) error { func ValidKinds() []graph.Kind { var ( - lenCalc = len(ad.Nodes()) + len(ad.Relationships()) + len(azure.NodeKinds()) + len(azure.Relationships()) - kinds = make([]graph.Kind, 0, lenCalc) + metaKinds = []graph.Kind{metaKind, metaDetailKind} ) - kinds = append(kinds, ad.Nodes()...) - kinds = append(kinds, ad.Relationships()...) - kinds = append(kinds, azure.NodeKinds()...) - kinds = append(kinds, azure.Relationships()...) - kinds = append(kinds, metaKind, metaDetailKind) - - return kinds + return slicesext.Concat(ad.Nodes(), ad.Relationships(), azure.NodeKinds(), azure.Relationships(), metaKinds) } func ParseKind(rawKind string) (graph.Kind, error) { diff --git a/packages/go/analysis/analysis_test.go b/packages/go/analysis/analysis_test.go index 58241faa5f..aad17b156b 100644 --- a/packages/go/analysis/analysis_test.go +++ b/packages/go/analysis/analysis_test.go @@ -23,6 +23,7 @@ import ( "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/graphschema/ad" "github.com/specterops/bloodhound/graphschema/azure" + slicesext "github.com/specterops/bloodhound/slicesext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -46,17 +47,7 @@ func (s kindStr) Is(others ...graph.Kind) bool { } func validKinds() graph.Kinds { - var ( - lenCalc = len(ad.NodeKinds()) + len(ad.Relationships()) + len(azure.NodeKinds()) + len(azure.Relationships()) - kinds = make(graph.Kinds, 0, lenCalc) - ) - - kinds = append(kinds, ad.NodeKinds()...) - kinds = append(kinds, ad.Relationships()...) - kinds = append(kinds, azure.NodeKinds()...) - kinds = append(kinds, azure.Relationships()...) - - return kinds + return slicesext.Concat(ad.NodeKinds(), ad.Relationships(), azure.NodeKinds(), azure.Relationships()) } func validKindStrings() []string { diff --git a/packages/go/slicesext/slicesext.go b/packages/go/slicesext/slicesext.go index cf2ea8708e..1f1595fd36 100644 --- a/packages/go/slicesext/slicesext.go +++ b/packages/go/slicesext/slicesext.go @@ -17,6 +17,8 @@ // Package slicesext extends the standard library slices package with additional slice utilities package slicesext +import "slices" + // Filter applies a predicate function over each element in a given slice and returns a new slice containing only the elements in which the predicate returns true func Filter[T any](slice []T, fn func(T) bool) []T { var out []T @@ -115,3 +117,21 @@ func Last[T any](list []T) T { func Init[T any](list []T) []T { return list[:len(list)-1] } + +// Concat returns a new slice concatenating the passed in slices. +// This was ripped from go1.22 source and should be replaced with the stdlib +// implementation when we move to 1.22 +func Concat[S ~[]E, E any](s ...S) S { + size := 0 + for _, slice := range s { + size += len(slice) + if size < 0 { + panic("len out of range") + } + } + newslice := slices.Grow[S](nil, size) + for _, s := range s { + newslice = append(newslice, s...) + } + return newslice +} diff --git a/packages/go/slicesext/slicesext_test.go b/packages/go/slicesext/slicesext_test.go index 1c49db7eed..48eb7b07d2 100644 --- a/packages/go/slicesext/slicesext_test.go +++ b/packages/go/slicesext/slicesext_test.go @@ -18,6 +18,7 @@ package slicesext_test import ( "fmt" + "math" "slices" "strconv" "strings" @@ -67,11 +68,6 @@ func TestUnique(t *testing.T) { require.Equal(t, []int{1, 2, 3}, slicesext.Unique([]int{1, 1, 2, 2, 3})) } -func TestContains(t *testing.T) { - require.True(t, slices.Contains([]string{"a", "b", "c"}, "c")) - require.False(t, slices.Contains([]string{"a", "b", "c"}, "d")) -} - func BenchmarkHead(b *testing.B) { for i := 10; i < 1000000; i = i * 10 { list := make([]int, i) @@ -126,6 +122,103 @@ func BenchmarkTail(b *testing.B) { } } +// Ripped from go1.22 source, can be removed when we can switch to stdlib +func TestConcat(t *testing.T) { + cases := []struct { + s [][]int + want []int + }{ + { + s: [][]int{nil}, + want: nil, + }, + { + s: [][]int{{1}}, + want: []int{1}, + }, + { + s: [][]int{{1}, {2}}, + want: []int{1, 2}, + }, + { + s: [][]int{{1}, nil, {2}}, + want: []int{1, 2}, + }, + } + for _, tc := range cases { + got := slicesext.Concat(tc.s...) + if !slices.Equal(tc.want, got) { + t.Errorf("Concat(%v) = %v, want %v", tc.s, got, tc.want) + } + var sink []int + allocs := testing.AllocsPerRun(5, func() { + sink = slicesext.Concat(tc.s...) + }) + _ = sink + if allocs > 1 { + errorf := t.Errorf + errorf("Concat(%v) allocated %v times; want 1", tc.s, allocs) + } + } +} + +// Ripped from go1.22 source, can be removed when we can switch to stdlib +func TestConcat_too_large(t *testing.T) { + // Use zero length element to minimize memory in testing + type void struct{} + cases := []struct { + lengths []int + shouldPanic bool + }{ + { + lengths: []int{0, 0}, + shouldPanic: false, + }, + { + lengths: []int{math.MaxInt, 0}, + shouldPanic: false, + }, + { + lengths: []int{0, math.MaxInt}, + shouldPanic: false, + }, + { + lengths: []int{math.MaxInt - 1, 1}, + shouldPanic: false, + }, + { + lengths: []int{math.MaxInt - 1, 1, 1}, + shouldPanic: true, + }, + { + lengths: []int{math.MaxInt, 1}, + shouldPanic: true, + }, + { + lengths: []int{math.MaxInt, math.MaxInt}, + shouldPanic: true, + }, + } + for _, tc := range cases { + var r any + ss := make([][]void, 0, len(tc.lengths)) + for _, l := range tc.lengths { + s := make([]void, l) + ss = append(ss, s) + } + func() { + defer func() { + r = recover() + }() + _ = slicesext.Concat(ss...) + }() + if didPanic := r != nil; didPanic != tc.shouldPanic { + t.Errorf("slices.Concat(lens(%v)) got panic == %v", + tc.lengths, didPanic) + } + } +} + func abs(n int) uint { mask := n >> (strconv.IntSize - 1) return uint((n ^ mask) - mask)