diff --git a/cmd/syft/cli/eventloop/tasks.go b/cmd/syft/cli/eventloop/tasks.go index 5e32b04233b..b610c86cec8 100644 --- a/cmd/syft/cli/eventloop/tasks.go +++ b/cmd/syft/cli/eventloop/tasks.go @@ -46,14 +46,11 @@ func generateCatalogPackagesTask(app *config.Application) (Task, error) { task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) { packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig()) - if err != nil { - return nil, err - } results.PackageCatalog = packageCatalog results.LinuxDistribution = theDistro - return relationships, nil + return relationships, err } return task, nil diff --git a/syft/lib.go b/syft/lib.go index bfed4bb5b5f..0c42213be26 100644 --- a/syft/lib.go +++ b/syft/lib.go @@ -70,13 +70,10 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, [] } catalog, relationships, err := cataloger.Catalog(resolver, release, cfg.Parallelism, catalogers...) - if err != nil { - return nil, nil, nil, err - } relationships = append(relationships, newSourceRelationshipsFromCatalog(src, catalog)...) - return catalog, relationships, release, nil + return catalog, relationships, release, err } func newSourceRelationshipsFromCatalog(src *source.Source, c *pkg.Catalog) []artifact.Relationship { diff --git a/syft/pkg/cataloger/catalog.go b/syft/pkg/cataloger/catalog.go index 859e66e2ba3..323bc0ab55b 100644 --- a/syft/pkg/cataloger/catalog.go +++ b/syft/pkg/cataloger/catalog.go @@ -3,6 +3,7 @@ package cataloger import ( "fmt" "math" + "runtime/debug" "sync" "github.com/hashicorp/go-multierror" @@ -49,8 +50,15 @@ func newMonitor() (*progress.Manual, *progress.Manual) { return &filesProcessed, &packagesDiscovered } -func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (*catalogResult, error) { - catalogerResult := new(catalogResult) +func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (catalogerResult *catalogResult, err error) { + // handle individual cataloger panics + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("%v at:\n%s", e, string(debug.Stack())) + } + }() + + catalogerResult = new(catalogResult) // find packages from the underlying raw data log.WithFields("cataloger", cataloger.Name()).Trace("cataloging started") @@ -88,7 +96,7 @@ func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (*catal } catalogerResult.Relationships = append(catalogerResult.Relationships, relationships...) log.WithFields("cataloger", cataloger.Name()).Trace("cataloging complete") - return catalogerResult, nil + return catalogerResult, err } // Catalog a given source (container image or filesystem) with the given catalogers, returning all discovered packages. @@ -100,7 +108,11 @@ func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (*catal func Catalog(resolver source.FileResolver, release *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Catalog, []artifact.Relationship, error) { catalog := pkg.NewCatalog() var allRelationships []artifact.Relationship + filesProcessed, packagesDiscovered := newMonitor() + defer filesProcessed.SetCompleted() + defer packagesDiscovered.SetCompleted() + // perform analysis, accumulating errors for each failed analysis var errs error @@ -158,7 +170,6 @@ func Catalog(resolver source.FileResolver, release *linux.Release, parallelism i for result := range results { if result.Error != nil { errs = multierror.Append(errs, result.Error) - continue } for _, p := range result.Packages { catalog.Add(p) @@ -168,14 +179,7 @@ func Catalog(resolver source.FileResolver, release *linux.Release, parallelism i allRelationships = append(allRelationships, pkg.NewRelationships(catalog)...) - if errs != nil { - return nil, nil, errs - } - - filesProcessed.SetCompleted() - packagesDiscovered.SetCompleted() - - return catalog, allRelationships, nil + return catalog, allRelationships, errs } func packageFileOwnershipRelationships(p pkg.Package, resolver source.FilePathResolver) ([]artifact.Relationship, error) { diff --git a/syft/pkg/cataloger/catalog_test.go b/syft/pkg/cataloger/catalog_test.go new file mode 100644 index 00000000000..9de59f36d74 --- /dev/null +++ b/syft/pkg/cataloger/catalog_test.go @@ -0,0 +1,67 @@ +package cataloger + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/linux" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/source" +) + +func Test_CatalogPanicHandling(t *testing.T) { + catalog, relationships, err := Catalog( + source.NewMockResolverForPaths(), + &linux.Release{}, + 1, + panickingCataloger{}, + returningCataloger{}, + ) + + require.Error(t, err) + require.Contains(t, err.Error(), "catalog_test.go") + require.Len(t, catalog.Sorted(), 2) + require.Len(t, relationships, 1) +} + +type panickingCataloger struct{} + +func (p panickingCataloger) Name() string { + return "panicking-cataloger" +} + +func (p panickingCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { + panic("something bad happened") +} + +var _ pkg.Cataloger = (*panickingCataloger)(nil) + +type returningCataloger struct{} + +func (p returningCataloger) Name() string { + return "returning-cataloger" +} + +func (p returningCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { + pkg1 := pkg.Package{ + Name: "package-1", + Version: "1.0", + } + pkg1.SetID() + pkg2 := pkg.Package{ + Name: "package-2", + Version: "2.0", + } + pkg2.SetID() + return []pkg.Package{pkg1, pkg2}, []artifact.Relationship{ + { + From: pkg1, + To: pkg2, + Type: artifact.DependencyOfRelationship, + }, + }, nil +} + +var _ pkg.Cataloger = (*returningCataloger)(nil)