Skip to content
Merged
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
13 changes: 8 additions & 5 deletions syft/pkg/cataloger/dotnet/deps_binary_cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,12 @@ func isRuntimePackageLocation(loc file.Location) (string, bool) {
func partitionPEs(depJsons []logicalDepsJSON, peFiles []logicalPE) ([]logicalDepsJSON, []logicalPE, []logicalDepsJSON) {
// sort deps.json paths from longest to shortest. This is so we are processing the most specific match first.
sort.Slice(depJsons, func(i, j int) bool {
return len(depJsons[i].Location.RealPath) > len(depJsons[j].Location.RealPath)
return depJsons[i].Location.RealPath > depJsons[j].Location.RealPath
})

// we should be processing PE files in a stable order
sort.Slice(peFiles, func(i, j int) bool {
return peFiles[i].Location.RealPath > peFiles[j].Location.RealPath
})

peFilesByPath := make(map[file.Coordinates][]logicalPE)
Expand Down Expand Up @@ -321,8 +326,7 @@ func relationshipsFromLogicalDepsJSON(doc logicalDepsJSON, pkgMap map[string]pkg
if lp.Targets == nil {
continue
}
for depName, depVersion := range lp.Targets.Dependencies {
depNameVersion := createNameAndVersion(depName, depVersion)
for _, depNameVersion := range lp.dependencyNameVersions() {
thisPkg, ok := pkgMap[lp.NameVersion]
if !ok {
continue
Expand Down Expand Up @@ -371,8 +375,7 @@ func findNearestDependencyPackages(skippedDep logicalDepsJSONPackage, pkgMap map

processed.Add(skippedDep.NameVersion)

for depName, depVersion := range skippedDep.Targets.Dependencies {
depNameVersion := createNameAndVersion(depName, depVersion)
for _, depNameVersion := range skippedDep.dependencyNameVersions() {
depPkg, ok := pkgMap[depNameVersion]
if !ok {
skippedDepPkg, ok := skipped[depNameVersion]
Expand Down
72 changes: 50 additions & 22 deletions syft/pkg/cataloger/dotnet/deps_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"

"github.com/scylladb/go-set/strset"
Expand Down Expand Up @@ -66,7 +67,11 @@ func (t depsTarget) resourcePaths() map[string]string {
func (t depsTarget) runtimePaths() map[string]string {
result := make(map[string]string)
for path := range t.Runtime {
result[trimLibPrefix(path)] = path
trimmedPath := trimLibPrefix(path)
if _, exists := result[trimmedPath]; exists {
continue
}
result[trimmedPath] = path
}
return result
}
Expand All @@ -82,14 +87,14 @@ type depsLibrary struct {
// Note: this is not a real construct of the deps.json, just a useful reorganization of the data for downstream processing.
type logicalDepsJSONPackage struct {
NameVersion string
Targets *depsTarget
Targets []depsTarget
Library *depsLibrary

// anyChildClaimsDLLs is a flag that indicates if any of the children of this package claim a DLL associated with them in the deps.json.
anyChildClaimsDLLs bool
// AnyChildClaimsDLLs is a flag that indicates if any of the children of this package claim a DLL associated with them in the deps.json.
AnyChildClaimsDLLs bool

// anyChildHasDLLs is a flag that indicates if any of the children of this package have a DLL associated with them (found on disk).
anyChildHasDLLs bool
// AnyChildHasDLLs is a flag that indicates if any of the children of this package have a DLL associated with them (found on disk).
AnyChildHasDLLs bool

// RuntimePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file
// to the target path as described in the deps.json target entry under "runtime".
Expand All @@ -103,7 +108,7 @@ type logicalDepsJSONPackage struct {
// to the target path as described in the deps.json target entry under "compile".
CompilePathsByRelativeDLLPath map[string]string

// NativePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file
// NativePaths is a map of the relative path to the DLL relative to the deps.json file
// to the target path as described in the deps.json target entry under "native". These should not have
// any runtime references to trim from the front of the path.
NativePaths *strset.Set
Expand All @@ -118,11 +123,15 @@ func (l *logicalDepsJSONPackage) dependencyNameVersions() []string {
if l.Targets == nil {
return nil
}
var results []string
for name, version := range l.Targets.Dependencies {
results = append(results, createNameAndVersion(name, version))
results := strset.New()
for _, t := range l.Targets {
for name, version := range t.Dependencies {
results.Add(createNameAndVersion(name, version))
}
}
return results
r := results.List()
sort.Strings(r)
return r
}

// ClaimsDLLs indicates if this package has any DLLs associated with it (directly or indirectly with a dependency).
Expand All @@ -131,15 +140,15 @@ func (l *logicalDepsJSONPackage) ClaimsDLLs(includeChildren bool) bool {
if !includeChildren {
return selfClaim
}
return selfClaim || l.anyChildClaimsDLLs
return selfClaim || l.AnyChildClaimsDLLs
}

func (l *logicalDepsJSONPackage) FoundDLLs(includeChildren bool) bool {
selfClaim := len(l.Executables) > 0
if !includeChildren {
return selfClaim
}
return selfClaim || l.anyChildHasDLLs
return selfClaim || l.AnyChildHasDLLs
}

type logicalDepsJSON struct {
Expand Down Expand Up @@ -206,6 +215,14 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
for libName, target := range targets {
_, exists := packageMap[libName]
if exists {
// merge this with existing targets (multiple targets can exist for the same library)
p := packageMap[libName]
p.Targets = append(p.Targets, target)
p.RuntimePathsByRelativeDLLPath = mergeMaps(p.RuntimePathsByRelativeDLLPath, target.runtimePaths())
p.ResourcePathsByRelativeDLLPath = mergeMaps(p.ResourcePathsByRelativeDLLPath, target.resourcePaths())
p.CompilePathsByRelativeDLLPath = mergeMaps(p.CompilePathsByRelativeDLLPath, target.compilePaths())
p.NativePaths = mergeSets(p.NativePaths, target.nativePaths())

continue
}

Expand All @@ -218,7 +235,7 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
p := &logicalDepsJSONPackage{
NameVersion: libName,
Library: lib,
Targets: &target,
Targets: []depsTarget{target},
RuntimePathsByRelativeDLLPath: target.runtimePaths(),
ResourcePathsByRelativeDLLPath: target.resourcePaths(),
CompilePathsByRelativeDLLPath: target.compilePaths(),
Expand All @@ -235,8 +252,8 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
if !bundlingDetected && knownBundlers.Has(name) {
bundlingDetected = true
}
p.anyChildClaimsDLLs = searchForDLLClaims(packageMap, p.dependencyNameVersions()...)
p.anyChildHasDLLs = searchForDLLEvidence(packageMap, p.dependencyNameVersions()...)
p.AnyChildClaimsDLLs = searchForDLLClaims(packageMap, p.dependencyNameVersions()...)
p.AnyChildHasDLLs = searchForDLLEvidence(packageMap, p.dependencyNameVersions()...)
packages[p.NameVersion] = *p
}

Expand All @@ -250,6 +267,22 @@ func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
}
}

func mergeMaps(m1, m2 map[string]string) map[string]string {
if m1 == nil {
m1 = make(map[string]string)
}
for k, v := range m2 {
if _, exists := m1[k]; !exists {
m1[k] = v
}
}
return m1
}

func mergeSets(s1, s2 *strset.Set) *strset.Set {
return strset.Union(s1, s2)
}

type visitorFunc func(p *logicalDepsJSONPackage) bool

// searchForDLLEvidence recursively searches for executables found for any of the given nameVersions and children recursively.
Expand Down Expand Up @@ -277,12 +310,7 @@ func traverseDependencies(packageMap map[string]*logicalDepsJSONPackage, visitor
return true
}

var children []string
for name, version := range p.Targets.Dependencies {
children = append(children, createNameAndVersion(name, version))
}

if traverseDependencies(packageMap, visitor, children...) {
if traverseDependencies(packageMap, visitor, p.dependencyNameVersions()...) {
return true
}
}
Expand Down
143 changes: 143 additions & 0 deletions syft/pkg/cataloger/dotnet/deps_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package dotnet

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/file"
)

func TestTrimLibPrefix(t *testing.T) {
Expand Down Expand Up @@ -71,3 +77,140 @@ func TestTrimLibPrefix(t *testing.T) {
})
}
}

func TestGetLogicalDepsJSON_MergeTargets(t *testing.T) {
deps := depsJSON{
Location: file.NewLocation("/path/to/deps.json"),
RuntimeTarget: runtimeTarget{
Name: ".NETCoreApp,Version=v6.0",
},
// note: for this test we have two targets with the same name, which will be merged when creating a logical deps
Targets: map[string]map[string]depsTarget{
".NETCoreApp,Version=v6.0": {
"Microsoft.CodeAnalysis.CSharp/4.0.0": {
Dependencies: map[string]string{
"Microsoft.CodeAnalysis.Common": "4.0.0",
},
Runtime: map[string]map[string]string{
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.0.21.51404",
},
},
Resources: map[string]map[string]string{
"lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "cs",
},
"lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "de",
},
"lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "es",
},
"lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "fr",
},
"lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "it",
},
"lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ja",
},
"lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ko",
},
"lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "pl",
},
"lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "pt-BR",
},
"lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "ru",
},
"lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "tr",
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "zh-Hans",
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": {
"locale": "zh-Hant",
},
},
},
},
"net6.0": {
"Microsoft.CodeAnalysis.CSharp/4.0.0": {
Dependencies: map[string]string{
"Microsoft.CodeAnalysis.Common": "4.0.0",
},
Compile: map[string]map[string]string{
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll": {},
},
},
},
},
Libraries: map[string]depsLibrary{
"Microsoft.CodeAnalysis.CSharp/4.0.0": {
Type: "package",
Path: "microsoft.codeanalysis.csharp/4.0.0",
Sha512: "sha512-example-hash",
HashPath: "microsoft.codeanalysis.csharp.4.0.0.nupkg.sha512",
},
},
}

result := getLogicalDepsJSON(deps, &libmanJSON{})

assert.Equal(t, "/path/to/deps.json", result.Location.RealPath)
assert.Equal(t, ".NETCoreApp,Version=v6.0", result.RuntimeTarget.Name)

libPackage, exists := result.PackagesByNameVersion["Microsoft.CodeAnalysis.CSharp/4.0.0"]
require.True(t, exists, "Expected to find the merged package")

assert.NotNil(t, libPackage.Library)
assert.Equal(t, "package", libPackage.Library.Type)
assert.Equal(t, "microsoft.codeanalysis.csharp/4.0.0", libPackage.Library.Path)
assert.Equal(t, "sha512-example-hash", libPackage.Library.Sha512)
assert.Equal(t, "microsoft.codeanalysis.csharp.4.0.0.nupkg.sha512", libPackage.Library.HashPath)

require.Equal(t, 2, len(libPackage.Targets), "Expected 2 targets to be merged")

expectedRuntimePaths := map[string]string{
"Microsoft.CodeAnalysis.CSharp.dll": "lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll",
}
if diff := cmp.Diff(expectedRuntimePaths, libPackage.RuntimePathsByRelativeDLLPath); diff != "" {
t.Errorf("RuntimePathsByRelativeDLLPath mismatch (-expected +actual):\n%s", diff)
}

expectedResourcePaths := map[string]string{
"cs/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.CSharp.resources.dll",
"de/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.CSharp.resources.dll",
"es/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.CSharp.resources.dll",
"fr/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.CSharp.resources.dll",
"it/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.CSharp.resources.dll",
"ja/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.CSharp.resources.dll",
"ko/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.CSharp.resources.dll",
"pl/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.CSharp.resources.dll",
"pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll",
"ru/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.CSharp.resources.dll",
"tr/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.CSharp.resources.dll",
"zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll",
"zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": "lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll",
}
if diff := cmp.Diff(expectedResourcePaths, libPackage.ResourcePathsByRelativeDLLPath); diff != "" {
t.Errorf("ResourcePathsByRelativeDLLPath mismatch (-expected +actual):\n%s", diff)
}

expectedCompilePaths := map[string]string{
"Microsoft.CodeAnalysis.CSharp.dll": "lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll",
}
if diff := cmp.Diff(expectedCompilePaths, libPackage.CompilePathsByRelativeDLLPath); diff != "" {
t.Errorf("CompilePathsByRelativeDLLPath mismatch (-expected +actual):\n%s", diff)
}

assert.Equal(t, 0, libPackage.NativePaths.Size(), "Expected no native paths")

assert.True(t, result.PackageNameVersions.Has("Microsoft.CodeAnalysis.CSharp/4.0.0"))
}
Loading